C++ "Dynamic" C 回调函数的函数指针

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}};

对于此解决方案,我需要使用单独的 tool/script 生成 header。

另一种解决方案是使用一些预处理器魔法 (BoostPP),例如

BOOST_PP_FOR((0,1024), PRED, OP, MYSTERIOUS_CALLBACK_MACRO);

根据我的经验,这些宏对于开发人员来说是不可读的,并且难以维护。

理想情况下我可以使用类似 RegisterCallback(popt, [n](void*){/*n-th callback*/});

但是 lambda 函数是仿函数而不是函数指针。

我的问题是:我可以动态创建这些函数吗?或者对于这个问题有没有比上面两个更好的解决方案?

谢谢。

编辑

我已经从 Botje 那里得到了答复。将 object 传递给函数回调需要您访问二进制级别的代码(超出 C/C++ 的级别)。如果你可以允许,那么 libffi 可以是一个解决方案,因为它生成一个指向你函数的每个实例的特定指针。它得到广泛支持,但是,例如 Visual Studio 编译器不在列表中。

编辑 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 或调用的语义;如果它是公开可用的软件,link 到实际文档会很有帮助。

无论如何,我发现这个问题很有趣,并将在下面展示一个解决方案,该解决方案利用了这样一个事实,即我们可以使用模板生成不同的函数,而不是手动编写代码。

下面的代码假定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