Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 845
Adi Shavit Avatar asked Oct 30 '16 06:10

Adi Shavit


2 Answers

In the case of visual c++ (starting from VC11), state-less lambdas implement a conversion operator to function pointers of all calling conventions.

So this, can work just as well

#include <iostream>
using namespace std;

int __cdecl foo()
{
    return 2;
}

void bar (int (__stdcall *pFunc)() )
{
    cout << pFunc()*2;
}

int main() {

    bar([](){ return foo(); });

    return 0;
}
like image 33
StoryTeller - Unslander Monica Avatar answered Oct 04 '22 17:10

StoryTeller - Unslander Monica


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

like image 183
krzaq Avatar answered Oct 04 '22 15:10

krzaq