[英]C++ "Dynamic" function pointers for C callback functions
我有一个用于管理相机配置的 API。 有 344 个单独的选项需要管理。 当某个值发生变化时,API 会调用回调函数来通知程序。 注册函数需要一个
void RegisterCallback(Option * ptr, void (fn*)(void*))
函数指针作为回调函数。 我不能使用单个函数进行回调,因为我现在知道回调来自.
一种解决方案是创建 344 个单独的回调函数:
void callback0(void*);
void callback1(void*);
...
void callback{n-1}(void*);
static void(*)(void*) callbacks[] = {callback0, callback1, ..., callback{n-1}};
对于此解决方案,我需要使用单独的工具/脚本生成标头。
另一种解决方案是使用一些预处理器魔法(BoostPP),比如
BOOST_PP_FOR((0,1024), PRED, OP, MYSTERIOUS_CALLBACK_MACRO);
根据我的经验,这些宏对于开发人员来说是不可读的,并且难以维护。
理想情况下,我可以使用类似RegisterCallback(popt, [n](void*){/*n-th callback*/});
但是 lambda 函数是一个函子而不是函数指针。
我的问题是:我可以动态创建这些函数吗? 或者有没有比上面两个更好的解决方案?
谢谢你。
编辑
我从 Botje 那里得到了答案。 将对象传递给函数回调需要您访问二进制级别的代码(超出 C/C++ 级别)。 如果您可以允许,那么 libffi 可以是一个解决方案,因为它会生成一个指向您的函数的每个实例的特定指针。 它得到了广泛的支持,但是,例如 Visual Studio Compiler 不在列表中。
编辑 2正如其他人指出的那样,它也应该与 VS 一起使用。
您可以使用libffi动态生成闭包。 这些是常规函数指针,可以保留一个指向用户数据的指针,在这种情况下是回调编号。
#include <iostream>
#include <vector>
#include "ffi.h"
using namespace std;
void the_callback(size_t num, void* arg) {
cout << "This is callback #" << num << " with argument " << hex << arg << endl;
}
void generic_cb(ffi_cif *cif, void *ret, void **args, void *user_data) {
the_callback((size_t) user_data, *(void **)args[0]);
}
typedef void (*Callback)(void*);
ffi_cif callback_cif;
int main() {
ffi_type *argtypes[] = { &ffi_type_pointer };
ffi_status st = ffi_prep_cif(&callback_cif, FFI_DEFAULT_ABI, 1, &ffi_type_void, argtypes);
std::vector<Callback> callbacks;
for (size_t i = 0; i < 100; i++) {
void *cb;
ffi_closure *writable = (ffi_closure*)ffi_closure_alloc(sizeof(ffi_closure), &cb);
st = ffi_prep_closure_loc(writable, &callback_cif, generic_cb, (void*)i, cb);
callbacks.push_back((Callback)cb);
}
callbacks[13]((void*)0xabcdef);
callbacks[87]((void*)0x1234);
}
这会产生以下输出:
This is callback #13 with argument 0xabcdef
This is callback #57 with argument 0x1234
你没有提供很多关于相机 API 或调用语义的细节; 如果它是公开可用的软件,则指向实际文档的链接会有所帮助。
无论如何,我发现这个问题很有趣,并将在下面展示一个解决方案,该解决方案利用我们可以使用模板生成不同函数的事实,而不是手动编码它们。
下面的代码假设void RegisterCallback(Option *ptr, void (fn*)(void*))
中的Option *ptr
参数是您从相机获取的指针; 它用作相机 API 的指示器,您要为哪个选项注册此回调。 可以想象,相机 API 有一个包含 344 个成员等的枚举,这将替换下面的size_t
模板参数和映射键类型。
调用一个简单的递归模板函数来为 344 个选项中的每一个注册一个回调。
#include <cstdio>
#include <map>
#include <cassert>
using namespace std;
typedef void (*voidFnPtrT)(void *);
////////////////////////////////////////////////////////////
// This part simulates the camera API.
class Camera
{ public:
static constexpr size_t NUM_OPTS = 344;
static constexpr size_t OPTSIZE = 100;
struct Option
{
unsigned char data[OPTSIZE];
};
Option &GetOption(size_t index) { return options[index]; }
void RegisterCallback(Option *opt, voidFnPtrT callback)
{
callbacks[opt] = callback;
}
/// Set a new option value and call the callback,
/// if set for that option.
void SetOptionVal(size_t index, Option *newVal)
{
assert(index < NUM_OPTS);
options[index] = *newVal;
OnOptionChange(index);
}
private:
Option options[NUM_OPTS];
map<Option *, voidFnPtrT> callbacks;
/// If there is a callback registered
/// with the option at that index, call it.
void OnOptionChange(size_t index)
{
auto iter = callbacks.find(options + index);
if(iter != callbacks.end())
{
(*iter->second)(iter->first);
}
}
};
//////////// End API simulation ///////////////////////////////
/// A single user function which can serve as a
/// callback for any property change. The property is
/// identified through the index (could be an enum
/// or any other compile time constant).
void singlebigcallback(size_t index, void *data)
{
// This is still awkward, but in some location we must code
// what happens for each option. In terms of lines of code
// it is not much advantageous to simply putting each switch
// case contents directly into a specialization of callbackTemplFn.
// In terms of maintainability that may actually be even better.
// Anyway, you wanted a single function, here it is.
switch(index)
{
// obviously this demo code does not need a switch but can
// be handled by assembling a string, but imagine some distinct
// activity in each case.
case 0: printf("reacting to change in property 0\n"); break;
case 1: printf("reacting to change in property 1\n"); break;
case 2: printf("reacting to change in property 2\n"); break;
default: printf("property callback for %zu not yet implemented\n", index); break;
}
}
/// A template with a number template parameter.
/// The signature of each instantiation is
/// void ()(void *) and hence can be used
/// as a callback function for the camera.
template<size_t N> void callbackTemplFn(void *data)
{
singlebigcallback(N, data);
}
/// This function registers the proper callbackTemplFn
/// for the given N and then recursively calls itself
/// with N-1.
template<size_t N> void RegisterAllCallbacks(Camera &cam)
{
cam.RegisterCallback(&cam.GetOption(N), &callbackTemplFn<N>);
RegisterAllCallbacks<N-1>(cam);
}
/// The recursion end: There is no index smaller than 0,
/// so we register the proper function and return.
template<> void RegisterAllCallbacks<0>(Camera &cam)
{
cam.RegisterCallback(&cam.GetOption(0), &callbackTemplFn<0>);
// No further recursion.
}
int main()
{
Camera camera;
Camera::Option opt; // Normally one would put some data in there.
RegisterAllCallbacks<Camera::NUM_OPTS-1>(camera);
camera.SetOptionVal(0, &opt);
camera.SetOptionVal(2, &opt);
camera.SetOptionVal(Camera::NUM_OPTS-1, &opt);
return 0;
}
示例会话:
$ g++ -Wall -o callbacks callbacks.cpp && ./callbacks
reacting to change in property 0
reacting to change in property 2
property callback for 343 not yet implemented
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.