void foo(int(__stdcall *callback)());
C函数指针回调的两个主要缺陷是:
>无法存储绑定表达式
>无法存储捕获羔羊
我想知道包装这些功能的最佳方法.第一个对成员函数回调特别有用,而第二个对于使用周围变量的内联定义,但这些定义不是唯一的用途.
这些特定函数指针的另一个属性是它们需要使用__stdcall调用约定.据我所知,这完全消除了兰布斯的选择,另外还有一点麻烦.我至少要允许__cdecl.
这是最好的,我能够想出没有事情开始弯腰依赖于功能指针没有的支持.它通常在标题中.以下是Coliru的以下示例.
#include <functional> //C function in another header I have no control over extern "C" void foo(int(__stdcall *callback)()) { callback(); } namespace detail { std::function<int()> callback; //pretend extern and defined in cpp //compatible with the API,but passes work to above variable extern "C" int __stdcall proxyCallback() { //pretend defined in cpp //possible additional processing return callback(); } } template<typename F> //takes anything void wrappedFoo(F f) { detail::callback = f; foo(detail::proxyCallback); //call C function with proxy } int main() { wrappedFoo([&]() -> int { return 5; }); }
然而,这是一个很大的缺陷.这不是入门的.如果变量在使用之前被重新分配,则旧函数将永远不会被调用(不考虑多线程问题).
我曾尝试过的一件事是将std ::函数存储为数据成员并使用对象,因此每个操作都将在不同的变量上运行,但是没有办法将该对象传递给代理.将对象作为参数将导致签名不匹配,并且绑定不会将结果存储为函数指针.
我有一个想法,但没有玩过,是一个std ::函数的向量.但是,我认为从这里清除的唯一真正的安全时间将是在没有使用它时清除它.但是,每个条目首先在wrappedFoo中添加,然后在proxyCallback中使用.我想知道如果一个计数器在前者中增加并在后者中递减,然后在清除向量之前检查零值将起作用,但它似乎是一个比所需要的更复杂的解决方案.
>允许任何函数对象
>不仅仅是C回调的调用约定(如果它是一样的关键,用户可以通过正确的调用约定传入一些东西)
是线程安全/重入
注意:作为Mikael PeRSSon的答案的一部分,明显的解决方案是使用应该存在的void *参数.然而,这绝对不是一个全能的,最终的选择,主要是由于无能.那些没有这个选项的功能的可能性在哪里可以得到有趣的,而且是一个非常有用的答案的主要途径.
解决方法
有一些方法可以在运行时生成代码,例如您可以在LLVM trampoline intrinsics读取生成转发函数,存储额外的状态,非常类似于lambdas而是运行时间定义.
不幸的是,这些都不是标准的,所以你是滞留的.
通过状态的最简单的解决方案是…实际通过状态.啊!
明确定义的C回调将需要两个参数:
>指向回调函数本身的指针
> A void *
后者由代码本身未使用,并且在调用时简单地传递给回调.根据接口,回调负责销毁它,或者供应商,甚至第三个“销毁”功能可以通过.
有了这样一个接口,你可以有效地传递线程安全的状态在C级重新进入时尚,因此自然地将其包装在C中,具有相同的属性.
template <typename Result,typename... Args) Result wrapper(void* state,Args... args) { using FuncWrapper = std::function<Result(Args...)>; FuncWrapper& w = *reinterpret_cast<FuncWrapper*>(state); return w(args...); } template <typename Result,typename... Args) auto make_wrapper(std::function<Result(Args...)>& func) -> std::pair<Result (*)(Args...),void*> { void* state = reinterpret_cast<void*>(&func); return std::make_pair(&wrapper<Result,Args...>,state); }
如果C接口不提供这样的设施,你可以啃一点,但最终你是非常有限的.如上所述,一个可能的解决方案是将国家外部持有,使用全局变量,并尽力避免争议.
这里有一个粗略的草图:
// The FreeList,Store and Release functions are up to you,// you can use locks,atomics,whatever... template <size_t N,typename Result,typename... Args> class Callbacks { public: using FunctionType = Result (*)(Args...); using FuncWrapper = std::function<Result(Args...)>; static std::pair<FunctionType,size_t> Generate(FuncWrapper&& func) { // 1. Using the free-list,find the index in which to store "func" size_t const index = Store(std::move(state)); // 2. Select the appropriate "Call" function and return it assert(index < N); return std::make_pair(Select<0,N-1>(index),index); } // Generate static void Release(size_t); private: static size_t FreeList[N]; static FuncWrapper State[N]; static size_t Store(FuncWrapper&& func); template <size_t I,typename = typename std::enable_if<(I < N)>::type> static Result Call(Args...&& args) { return State[I](std::forward<Args>(args)...); } // Call template <size_t L,size_t H> static FunctionType Select(size_t const index) { static size_t const Middle = (L+H)/2; if (L == H) { return Call<L>; } return index <= Middle ? Select<L,Middle>(index) : Select<Middle + 1,H>(index); } }; // class Callbacks // Static initialization template <size_t N,typename... Args> static size_t Callbacks<N,Result,Args...>::FreeList[N] = {}; template <size_t N,typename... Args> static Callbacks<N,Args...>::FuncWrapper Callbacks<N,Args...>::State[N] = {};