Adi Shavit Adi Shavit - 1 month ago 9
C++ Question

Changing Calling Convention

I have a 3rd party C API that expects a

__stdcall
callback function.

My code has an externally-provided
__cdecl
callback function.

I cannot pass my function pointer to the C-API as they are considered different types.

Bypassing the type system and using
reinterpret_cast<>
naturally results in a runtime error.

Here's an example from here:

// C-API
// the stdcall function pointer type:
typedef CTMuint(__stdcall *CTMwritefn)(const void *aBuf, CTMuint aCount, void *aUserData);

// A function needing the callback:
CTMEXPORT void __stdcall ctmSaveCustom(CTMcontext aContext, CTMwritefn aWriteFn, void *aUserData, int *newvertexindex);
^^^^^^^^^^^^^^^^^^^

//////////////////////////////////////////////////////////////////////////////

// C++
CTMuint __cdecl my_func(const void *aBuf, CTMuint aCount, void *aUserData);

// I want to call here:
ctmSaveCustom(context, my_func, &my_data, nullptr);
// ^^^^^^^


Is there a way to safely convert and/or wrap a function with one calling convention into another?

I did find a way to do it by passing a casted captureless-lambda that calls a second capturing lambda. The first is passed as the callback, the second through the
void* user_data
. This works and is type-safe. But it is quite convoluted for something that seems so simple.

Answer

You can make a wrapper for translation between different calling conventions:

template<typename Func, Func* callback>
auto make_callback()
{
    return &detail::callback_maker<Func, callback>::call;
}

with callback_maker defined as

template<typename T, T*>
struct callback_maker;

template<typename R, typename... Params, R(*Func)(Params...)>
struct callback_maker<R(Params...), Func>
{
    static R __stdcall call(Params... ps)
    {
        return Func(std::forward<Params>(ps)...);
    }
};

This is aimed to be a fairly general solution, allowing you to specify the function prototype. You can use it as follows:

//  external_api(&not_stdcall_func); // error
external_api(make_callback<void(int,int), &not_stdcall_func>());

demo


If the pointer is to be determined at runtime, you could keep the callback in the user data. You'd have to manage the lifetime of that correctly, but it's likely that you already need to to that. Again, attempting a generic solution. Make a callback and tell it which argument is the user data pointer:

template<typename Callback, size_t N>
auto make_callback()
{
    using callback_maker = detail::callback_maker<Callback, N>;
    return &callback_maker::call;
}

With callback_maker defined as

template<typename T, size_t N>
struct callback_maker;

template<typename R, typename... Params, size_t N>
struct callback_maker<R(*)(Params...), N>
{
    using function_type = R(Params...);

    static R __stdcall call(Params... ps)
    {
        void const* userData = get_nth_element<N>(ps...);
        auto p = static_cast<pair<function_type*, void*> const*>(userData);
        return p->first(ps...);
    }
};

and get_nth_element as

template<size_t N, typename First, typename... Ts>
decltype(auto) get_nth_element_impl(false_type, First&& f, Ts&&...);

template<size_t N, typename First, typename... Ts>
decltype(auto) get_nth_element_impl(true_type, First&&, Ts&&... ts)
{
    return get_nth_element_impl<N-1>(integral_constant<bool, (N > 1)>{}, forward<Ts>(ts)...);
}

template<size_t N, typename First, typename... Ts>
decltype(auto) get_nth_element_impl(false_type, First&& f, Ts&&...)
{
    return forward<First>(f);
}

template<size_t N, typename... Ts>
decltype(auto) get_nth_element(Ts&&... ts)
{
    return get_nth_element_impl<N>(integral_constant<bool, (N > 0)>{}, forward<Ts>(ts)...);
}

Now, on the call site

using callback_t = CTMuint(*)(const void *aBuf, CTMuint aCount, void *aUserData);
auto runtime_ptr = &not_stdcall_func;

pair<callback_t, void*> data;
data.first = runtime_ptr;
data.second = nullptr; // actual user data you wanted

auto callback = make_callback<callback_t, 2>();

ctmSaveCustom({}, callback, &data, nullptr);

demo


As per Andrey Turkin's suggestion, you can replace the user data pointer in the parameter list. Along with forward_as_tuple, it obviates the need for get_nth_element. The upgraded call function:

static R __stdcall call(Params... ps)
{
    auto params_tuple = forward_as_tuple(ps...);
    void const* userData = get<N>(params_tuple);
    auto p = static_cast<pair<function_type*, void*> const*>(userData);
    get<N>(params_tuple) = p->second;
    return apply(p->first, move(params_tuple));
}

and here's a simplistic implementation of C++17's apply:

template<typename Func, typename T, size_t... Is>
decltype(auto) apply_impl(Func f, T&& t, index_sequence<Is...>)
{
    return f(get<Is>(t)...);
}

template<typename Func, typename... Ts>
decltype(auto) apply(Func f, tuple<Ts...>&& tup)
{
    return apply_impl(f, move(tup), index_sequence_for<Ts...>{});
}

demo