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(¬_stdcall_func); // error
external_api(make_callback<void(int,int), ¬_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 = ¬_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
void *
)? Otherwise a wrapper function is going to be a bit of a hassle regarding thread safety / multiple callbacks. – Arissa__stdcall
function, there is no way around it -- it is what it is. – Mukund