更改调用约定
Changing Calling Convention
我有一个 第三方 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);
// ^^^^^^^
有没有一种方法可以安全地将 and/or 用一种调用约定包装的函数转换为另一种调用约定?
我确实找到了一种方法,通过传递调用 second 捕获 lambda 的强制转换的无捕获 lambda。第一个作为回调传递,第二个通过 void* user_data
。这有效并且是类型安全的。但是看似简单的东西却很绕。
您可以为不同调用约定之间的转换制作包装器:
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(¬_stdcall_func); // error
external_api(make_callback<void(int,int), ¬_stdcall_func>());
如果要在运行时确定指针,则可以将回调保留在用户数据中。您必须正确管理它的生命周期,但很可能您已经需要这样做了。再次尝试通用解决方案。进行回调并告诉它哪个参数是用户数据指针:
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 = ¬_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);
根据 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...>{});
}
对于 visual c++(从 VC11 开始),state-less 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;
}
自己回答,希望有人有更简单的解决方案。
该方法与解释的相同 here.
我们将使用以下内容:
- 无捕获 lambda 可以自动 转换为具有任何 所需调用约定的函数指针。
- C-API 函数提供了一种
void* user_data
将数据传递给回调的方法。
我们将通过 C-API 两个 labmda:
- 一个是无捕获转换为正确的调用约定;
- 另一个捕获回调 fn-ptr 并作为
user_data
传递给无捕获 lambda 以进行调用。它捕获 原始回调 和 原始 user_data
供内部使用。
代码如下:
// 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);
如果在编译时回调未知,您有以下选择:
- 使用单个包装函数并在
user_data
中传递目标回调。 Pro - 相当容易使用; con - 需要 user_data
供自己使用;需要非常相似的函数签名
- 使用包装器 class,分配 class 的实例并在
user_data
中传递 this
。 Pro - 更通用,因为它可以在每个实例中捕获一些数据(例如,它可以存储 user_data
用于目标回调或将附加数据传递给目标回调);缺点 - 需要管理包装器实例生命周期
- 为每个不同的目标回调构建单独的 thunk。 Pro - 不需要使用
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)>(), ¬_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(¬_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!
我有一个 第三方 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);
// ^^^^^^^
有没有一种方法可以安全地将 and/or 用一种调用约定包装的函数转换为另一种调用约定?
我确实找到了一种方法,通过传递调用 second 捕获 lambda 的强制转换的无捕获 lambda。第一个作为回调传递,第二个通过 void* user_data
。这有效并且是类型安全的。但是看似简单的东西却很绕。
您可以为不同调用约定之间的转换制作包装器:
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(¬_stdcall_func); // error
external_api(make_callback<void(int,int), ¬_stdcall_func>());
如果要在运行时确定指针,则可以将回调保留在用户数据中。您必须正确管理它的生命周期,但很可能您已经需要这样做了。再次尝试通用解决方案。进行回调并告诉它哪个参数是用户数据指针:
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 = ¬_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);
根据 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...>{});
}
对于 visual c++(从 VC11 开始),state-less 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;
}
自己回答,希望有人有更简单的解决方案。
该方法与解释的相同 here.
我们将使用以下内容:
- 无捕获 lambda 可以自动 转换为具有任何 所需调用约定的函数指针。
- C-API 函数提供了一种
void* user_data
将数据传递给回调的方法。
我们将通过 C-API 两个 labmda:
- 一个是无捕获转换为正确的调用约定;
- 另一个捕获回调 fn-ptr 并作为
user_data
传递给无捕获 lambda 以进行调用。它捕获 原始回调 和 原始user_data
供内部使用。
代码如下:
// 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);
如果在编译时回调未知,您有以下选择:
- 使用单个包装函数并在
user_data
中传递目标回调。 Pro - 相当容易使用; con - 需要user_data
供自己使用;需要非常相似的函数签名 - 使用包装器 class,分配 class 的实例并在
user_data
中传递this
。 Pro - 更通用,因为它可以在每个实例中捕获一些数据(例如,它可以存储user_data
用于目标回调或将附加数据传递给目标回调);缺点 - 需要管理包装器实例生命周期 - 为每个不同的目标回调构建单独的 thunk。 Pro - 不需要使用
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)>(), ¬_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(¬_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!