用 C++11 接口包装 C 回调的最佳方法是什么?
假设这是一个要包装的 C 函数:
Let's say this is a C function to be wrapped:
void foo(int(__stdcall *callback)());
C 函数指针回调的两个主要陷阱是:
The two main pitfalls with C function pointer callbacks are:
- 无法存储绑定表达式
- 无法存储捕获的 lambdas
我想知道包装此类函数的最佳方法.第一个对于成员函数回调特别有用,第二个对于使用周围变量的内联定义特别有用,但这不是唯一的用途.
I would like to know the best way to wrap functions like these to do so. The first is particularly useful for a member function callback, and the second for an inline definition that uses surrounding variables, but those are not the only uses.
这些特定函数指针的另一个属性是它们需要使用 __stdcall
调用约定.据我所知,这完全消除了 lambdas 作为一个选项,否则有点麻烦.我也希望至少允许 __cdecl
.
The other property of these particular function pointers is that they need to use the __stdcall
calling convention. This, to my knowledge, eliminates lambdas as an option completely, and is a bit of a nuisance otherwise. I'd like to allow at least __cdecl
as well.
这是我能想到的最好的方法,而不会让事情开始转向依赖函数指针所没有的支持.它通常位于标题中.以下是关于 Coliru 的示例.
This is the best I am able to come up with without things starting to bend back to relying on support that function pointers don't have. It would typically be in a header. Here is the following example on 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;
});
}
然而,有一个重大缺陷.这不是可重入的.如果变量在使用前被重新赋值,旧函数将永远不会被调用(不考虑多线程问题).
There is, however, a major flaw. This is not re-entrant. If the variable is reassigned to before it's used, the old function will never be called (not taking into account multithreading issues).
我尝试过的一件事是将 std::function
存储为数据成员并使用对象,因此每个对象都会对不同的变量进行操作,但没有将对象传递给代理的方法.将对象作为参数会导致签名不匹配,绑定它不会让结果存储为函数指针.
One thing I have tried that ended up doubling back on itself was storing the std::function
as a data member and using objects, so each would operate on a different variable, but there was no way to pass the object to the proxy. Taking the object as a parameter would cause the signature to mismatch and binding it would not let the result be stored as a function pointer.
我有一个想法,但还没有尝试过,它是 std::function
的向量.但是,我认为唯一真正安全的擦除时间是在没有使用它时清除它.但是,每个条目首先添加到 wrappedFoo
中,然后在 proxyCallback
中使用.我想知道是否有一个计数器在前者中递增并在后者中递减,然后在清除向量之前检查零是否可行,但这听起来像是一个比必要的更复杂的解决方案.
One idea I have, but have not played around with is a vector of std::function
. However, I think the only real safe time to erase from it would be to clear it when nothing is using it. However, each entry is first added in wrappedFoo
, then used in proxyCallback
. I'm wondering if a counter that is incremented in the former and decremented in the latter, then checked for zero before clearing the vector would work, but it sounds like a more convoluted solution than necessary anyway.
有没有办法用函数指针回调来包装C函数,使得C++包装的版本:
Is there any way to wrap a C function with a function pointer callback such that the C++ wrapped version:
- 允许任何函数对象
- 允许的不仅仅是 C 回调的调用约定(如果它是相同的很重要,用户可以传入具有正确调用约定的内容)
- 线程安全/可重入
注意:作为 Mikael Persson 答案的一部分所述,显而易见的解决方案是使用应该存在的 void *
参数.然而,遗憾的是,这不是一个最终的选择,主要是由于无能.对于那些没有这个选项的函数来说,可能会变得有趣,并且是获得非常有用答案的主要途径.
Note: The obvious solution, stated as part of Mikael Persson's answer, is to make use of the void *
parameter that should exist. However, this is sadly not a be-all, end-all option, mostly due to incompetence. What possibilities exist for those functions that do not have this option is where this can get interesting, and is the primary route to a very useful answer.
推荐答案
不幸的是,你走运了.
有一些在运行时生成代码的方法,例如你可以阅读LLVM trampoline internals 在这里你生成一个存储额外状态的转发函数,非常类似于 lambdas 但运行时定义.
There are ways to generate code at runtime, for example you can read on LLVM trampoline intrinsics where you generate a forwarding function that stores additional state, very akin to lambdas but runtime defined.
不幸的是,这些都不是标准的,因此您陷入困境.
Unfortunately none of those are standard, and thus you are stranded.
传递状态最简单的解决方案是……实际传递状态.啊!
The simplest solution to pass state is... to actually pass state. Ah!
定义良好的 C 回调将采用两个参数:
Well defined C callbacks will take two parameters:
- 指向回调函数本身的指针
- A
void*
后者不被代码本身使用,在调用时简单地传递给回调.根据接口,回调负责销毁它,或者供应商,甚至可以传递第三个销毁"函数.
The latter is unused by the code itself, and simply passed to the callback when it is called. Depending on the interface either the callback is responsible to destroy it, or the supplier, or even a 3rd "destroy" function could be passed.
有了这样的接口,你就可以在线程安全的& 中有效地传递状态.C 级别的可重入方式,因此自然而然地将其封装在具有相同属性的 C++ 中.
With such an interface, you can effectively pass state in a thread-safe & re-entrant fashion at the C level, and thus naturally wrap this up in C++ with the same properties.
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 接口没有提供这样的功能,你可以稍微修改一下,但最终你是非常有限的.如前所述,一个可能的解决方案是在外部保持状态,使用全局变量,并尽最大努力避免争用.
If the C interface does not provide such facilities, you can hack around a bit, but ultimately you are very limited. As was said, a possible solution is to hold the state externally, using globals, and do your best to avoid contention.
这里有一个粗略的草图:
A rough sketch is here:
// 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 Result, typename... Args>
static size_t Callbacks<N, Result, Args...>::FreeList[N] = {};
template <size_t N, typename Result, typename... Args>
static Callbacks<N, Result, Args...>::FuncWrapper Callbacks<N, Result, Args...>::State[N] = {};
相关文章