c++ - 更改调用约定

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

我有一个 第 3 方 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 的强制转换的无捕获 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)...);
    }
};

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

//  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

关于c++ - 更改调用约定,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40326733/

相关文章:

PHP和C++汉字倒序UTF-8编码单元

c++ - char8_t *的printf()格式字符是什么?

c++ - 在没有模板的情况下评估另一个类的动态函数指针

c - gdb单步执行动态函数

python - Cython 函数指针和异常

c中的调用约定

c++ - 构造函数初始化列表中的ADL

c++ - 单击 EDIT 控件时 WINAPI 保存对话框打开两次

c++ - ESP 调用约定问题 C++

c - 为什么 gcc 忽略 __attribute__((stdcall))?