改变呼叫公约

时间:2016-10-30 06:57:41

标签: c++ function-pointers calling-convention stdcall cdecl

我有第三方C API ,需要__stdcall回调功能。
我的代码有一个外部提供的 __cdecl回调函数。

我无法将我的函数指针传递给C-API,因为它们被认为是不同的类型 绕过类型系统并使用reinterpret_cast<>自然会导致运行时错误。

以下是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);
//                     ^^^^^^^

有没有办法安全地将一个调用约定的函数转换和/或包装到另一个调用约定中?

我确实通过传递一个调用 second 捕获lambda的casted captureless-lambda来找到一种方法。第一个作为回调传递,第二个作为void* user_data传递。这是有效的,并且是类型安全的。但对于看起来如此简单的事情来说,这是非常令人费解的。

4 个答案:

答案 0 :(得分:11)

您可以创建一个包装器,以便在不同的调用约定之间进行转换:

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

callback_maker定义为

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)...);
    }
};

这是一个相当通用的解决方案,允许您指定函数原型。您可以按如下方式使用它:

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

demo

如果要在运行时确定指针,则可以将回调保留在用户数据中。您必须正确管理其生命周期,但您可能已经需要这样做。再次,尝试通用的解决方案。进行回调并告诉它哪个参数是用户数据指针:

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

callback_maker定义为

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...);
    }
};

get_nth_element

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)...);
}

现在,在通话网站上

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

根据Andrey Turkin的建议,您可以替换参数列表中的用户数据指针。与forward_as_tuple一起,它消除了对get_nth_element的需求。升级的通话功能:

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));
}

这是C ++ 17 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

答案 1 :(得分:4)

对于visual c ++(从VC11开始),无状态lambdas实现了一个转换运算符来处理所有调用约定的指针。

所以this,也可以正常工作

#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;
}

答案 2 :(得分:4)

如果在编译时未知回调,则可以使用以下选项:

  • 使用单个包装函数并在user_data中传递目标回调。亲 - 相当容易使用; con - 需要user_data供自己使用;需要非常相似的功能签名
  • 使用包装器类,分配类的实例并在this中传递user_data。 Pro - 更为通用,因为它可以捕获每个实例中的一些数据(例如,它可以存储user_data用于目标回调或将其他数据传递给目标回调); con - 需要管理包装器实例生存期
  • 为每个不同的目标回调构建单独的thunk。专业版 - 不需要使用user_data; con - 相当低级别且非常不可移植(在OS中的两个编译器中);可能很难做到;如果不采用汇编,在C ++中很难做到。

第一个选项看起来像那样(无耻地扯掉@krzaq):

template<typename T> struct callback_maker;
template<typename R, typename... Params> struct callback_maker<R(Params...)> {
    static R __stdcall call_with_userdata_as_last_parameter(Params... ps, void* userData) {
        R(__cdecl *Func)(Params...) = reinterpret_cast<R(__cdecl *)(Params...)>(userData);
        return Func(std::forward<Params>(ps)...);
    }
};
template<typename Func> constexpr auto make_callback() {
    return &callback_maker<Func>::call_with_userdata_as_last_parameter;
}

...
extern void external_api(void(__stdcall*)(int,int,void*), void* userdata);
extern void __cdecl not_stdcall_func(int,int);
external_api(make_callback<void(int,int)>(), &not_stdcall_func);

可能无法使用,因为两个回调都需要userData

第二个选项:

template<typename T> struct CallbackWrapper;
template<typename R, typename... Params> struct CallbackWrapper<R(Params...)> {
    using stdcall_callback_t = R(__stdcall*)(Params..., void*);
    using cdecl_callback_t = R(__cdecl*)(Params..., void*);
    using MyType = CallbackWrapper<R(Params...)>;
    CallbackWrapper(cdecl_callback_t target, void* target_userdata) : _target(target), _target_userdata(target_userdata) {}
    stdcall_callback_t callback() const { return &MyType::callback_function; }
private:
    static R __stdcall callback_function(Params... ps, void* userData) {
        auto This = reinterpret_cast<MyType*>(userData);
        return This->_target(std::forward<Params>(ps)..., This->_target_userdata);
    }
    cdecl_callback_t _target;
    void* _target_userdata;
};

...
extern void external_api(void(__stdcall*)(int,int,void*), void* userdata);
extern void __cdecl not_stdcall_func(int,int, void*);

void * userdata_for_not_stdcall_func = nullptr;
CallbackWrapper<void(int, int)> wrapper(&not_stdcall_func, userdata_for_not_stdcall_func);
external_api(wrapper.callback(), &wrapper);
// make sure wrapper is alive for as long as external_api is using the callback!

答案 3 :(得分:3)

回答自己,希望有人能提供更简单的解决方案 方法与解释here相同。

我们将使用以下内容:

  1. 使用任何所需的调用约定,无捕捉lambda可以自动强制转换为函数指针。
  2. C-API函数提供void* user_data方式将数据传递给回叫。
  3. 我们将通过C-API两个labmdas:

    1. 一个是无法进行正确的召唤约定;
    2. 另一个捕获回调fn-ptr并作为user_data传递给无捕获的lambda来调用。它捕获两个原始回调原始user_data以供内部使用。
    3. 以下是代码:

      // This is a lambda that calls the (cdecl) callback via capture list
      // However, you can't convert a non-captureless lambda to a function pointer
      auto callback_trampoline = [&callback, &user_data](const void *aBuf, CTMuint aCount) -> CTMuint
      {
          return callback(aBuf, aCount, user_data);
      };
      
      using trampoline_type = decltype(callback_trampoline);
      
      // so we create a capture-less wrapper which will get the lambda as the user data!
      // this CAN be cast to a function pointer!
      auto callback_wrapper_dispatcher = [](const void *aBuf, CTMuint aCount, void *aUserData) -> CTMuint
      {
          auto& lambda = *reinterpret_cast<trampoline_type*>(aUserData);
          return lambda(aBuf, aCount);
      };
      
      ctmSaveCustom(context_, callback_wrapper_dispatcher, &callback_trampoline, nullptr);
      

      这是类型安全的并且按预期工作。

      将它变成一个类似于@krzaq答案中建议的通用工具会很酷。

      <强>更新
      这是一个简单的公式,只有一个无捕捉的lambda,但是相同的概念:

      auto payload = std::tie(callback, user_data);
      using payload_type = decltype(payload);
      auto dispatcher = [](const void *aBuf, CTMuint aCount, void *aUserData)->CTMuint
      {
          // payload_type is visible to the captureless lamda
          auto& payload = *reinterpret_cast<payload_type*>(aUserData);
          return std::get<0>(payload)(aBuf, aCount, std::get<1>(payload));
      };
      ctmSaveCustom(context_, dispatcher, &payload, nullptr);