假设这是一个要包装的 C 函数:
void foo(int(__stdcall *callback)());
C 函数指针回调的两个主要缺陷是:
- 无法存储绑定表达式
- 无法存储捕获的 lambda
我想知道包装这些函数的最佳方法。第一个对于成员函数回调特别有用,第二个对于使用周围变量的内联定义特别有用,但这些并不是唯一的用途。
这些特定函数指针的另一个属性是它们需要使用__stdcall
调用约定。据我所知,这完全消除了 lambdas 作为一个选项,否则有点麻烦。我想至少__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::function
为数据成员并使用对象,因此每个对象都会对不同的变量进行操作,但是无法将对象传递给代理。将对象作为参数会导致签名不匹配并且绑定它不会让结果存储为函数指针。
我有一个想法,但还没有玩过,是std::function
. 但是,我认为唯一真正安全的擦除它的时间是在没有任何东西使用它时清除它。但是,每个条目首先添加在 中wrappedFoo
,然后在 中使用proxyCallback
。我想知道在前者中递增并在后者中递减,然后在清除向量之前检查为零的计数器是否会起作用,但这听起来像是一个比必要的更复杂的解决方案。
有没有办法用函数指针回调包装 C 函数,使得 C++ 包装版本:
- 允许任何函数对象
- 允许的不仅仅是 C 回调的调用约定(如果它是相同的至关重要,用户可以通过正确的调用约定传入一些东西)
- 是线程安全的/可重入的
注意:作为 Mikael Persson 回答的一部分,显而易见的解决方案是利用void *
应该存在的参数。然而,遗憾的是,这不是一个万能的、最终的选择,主要是由于无能。对于那些没有此选项的功能,存在哪些可能性是有趣的地方,并且是获得非常有用的答案的主要途径。