C++ “Dynamic” function pointers for C callback functions

六眼飞鱼酱① 提交于 2020-12-26 06:56:43

问题


I have an API for managing a camera configuration. There are 344 individual options to manage. When a certain value changes the API calls a callback function to notify the program. The register function takes a

void RegisterCallback(Option * ptr, void (fn*)(void*))

function pointer as a callback function. I cannot use a single function for callback, because I do now where is the callback coming from.

One solution is to create 344 individual callback functions:

void callback0(void*);
void callback1(void*);
...
void callback{n-1}(void*);

static void(*)(void*) callbacks[] = {callback0, callback1, ..., callback{n-1}};

For this solution I would need to generate the header with a separate tool/script.

Another solution would be to use some preprocessor magic (BoostPP), like

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

In my experience these macros are unreadable for the developers, and are difficult to maintain.

Ideally I could use something like RegisterCallback(popt, [n](void*){/*n-th callback*/});

But the lambda function is a functor and not a function pointer.

My question is this: Can I create these functions dynamically? Or is there a better solution for this problem than the two above?

Thank You.

EDIT

I have gotten an answer from Botje. Passing an object to a function callback requires You to access the code on the binary level (beyond that of C/C++). If You can permit that, then libffi can be a solution, as it generates a specific pointer to each instance of Your function. It is widely supported, but, for example Visual Studio Compiler is not on the list.

EDIT 2 As others pointed it out it should work with VS too.


回答1:


You can generate closures dynamically using libffi. These are regular function pointers that can retain one pointer to user data, in this case the callback number.

#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 produces the following output:

This is callback #13 with argument 0xabcdef
This is callback #57 with argument 0x1234



回答2:


You do not provide many details about the camera API or the semantics of the call; if it is publicly available software a link to the actual documentation would be helpful.

In any case I found the question interesting and will show a solution below which uses the fact that we can generate different functions with templates, instead of coding them by hand.

The code below assumes that the Option *ptr parameter in void RegisterCallback(Option *ptr, void (fn*)(void*)) is a pointer which you obtained from the camera; it serves as an indicator to the camera API for which option you want to register this callback. Conceivably the camera API has an enumeration with 344 members or such for this, which would replace the size_t template parameters and map key type below.

A simple recursive template function is called to register a callback for each of the 344 options.

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

Sample session:

$ 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


来源:https://stackoverflow.com/questions/60719322/c-dynamic-function-pointers-for-c-callback-functions

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!