C++中用于C回调函数的"动态"函数指针

5
我有一个用于管理相机配置的API。需要管理344个选项。当某个值更改时,API会调用回调函数来通知程序。注册函数采用一个函数指针作为回调函数。由于无法确定回调的来源,因此不能使用单个函数进行回调
一种解决方法是创建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编译器不在列表中。编辑2:正如其他人指出的那样,这应该也适用于VS。

你如何将上下文传递给回调函数?你能操纵传递给void*参数的内容吗?Option是否有一些字段可用于标识回调选项?它会传递到回调函数中吗?在那个void*参数中传递了什么?static void(*)(void*) callbacks[]是无效的语法,应该是static void(*callbacks[])(void*) - KamilCuk
如果您没有办法将任何上下文传递给回调函数,那么我只能看到为每种情况创建回调函数的方法。答案中的libffi只是动态地为每种情况创建函数,所以这是正确的方法。 - KamilCuk
如果它与VS一起使用,那是我的错,但这并不改变它严重依赖编译器的事实。 - user1396055
相较于344个单独的定义,这些宏更容易维护。 - Peter - Reinstate Monica
Option * 参数不是用来区分哪个选项被更新或其他吗?这可以是一个带有虚函数的 C++ 对象;单个回调函数可以进行分派。 - Kaz
显示剩余2条评论
2个回答

2
您可以使用 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

这篇文章从两个方面回答了我的问题。它既可能,也不可能。首先,它是可能的,但需要在二进制级别上操作代码;其次,它是不可能的,因为你必须做一些特定于操作系统和编译器的事情。例如,在VS中,这个解决方案是行不通的。谢谢。 - user1396055
为什么这在VS上不能工作?至少有一个libffi的conan包。 - Botje

2
您没有提供有关相机API或调用语义的详细信息;如果它是公开可用的软件,则提供实际文档的链接会很有帮助。
无论如何,我发现这个问题很有趣,并将在下面展示一种解决方案,该解决方案利用了我们可以使用模板生成不同函数的事实,而不是手动编码它们。
下面的代码假定void RegisterCallback(Option *ptr, void (fn*)(void*))中的Option *ptr参数是您从相机获取的指针;它作为指示器向相机API表示您要为哪个选项注册此回调。可以想象,相机API具有包含344个成员的枚举,用于替换下面的模板参数和映射键类型。
简单的递归模板函数被称为以注册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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接