Search code examples
c++pointersargumentsword-wrapvoid

Wrapping around a C function that takes its arguments with void* in C++


I am wrapping around a C function from freeRTOS that creates a task and takes its arguments with void pointer in C++. The function looks a little bit like this:

void createTask(TaskFunction_t taskCode, void * args);

So to my understanding to pass 2 arguments to the task I would need to create a struct, cast its address to void*, pass it and then cast it back to the original state like so:

struct Params
{
    const int a;
    const double b;
};

static void task(void * args)
{
   auto params = *static_cast<Params*>(args);
   // do something with params.a and params.b
}

int main()
{
   Params params{1, 2.2};
   createTask(task, static_cast<void*>(&params));
}

What would be preferred way of wrapping this function so that I could pass a variable number of arguments of variable types? Should I just leave void * args as an argument or is there something that could be done with templates or maybe tuples to simplify this process a little bit.


Solution

  • In C++11 onwards, you can use something like

    static void call_task(void *args) {
        auto& f = *static_cast<std::function<void()>*>(args);
        f();
    }
    
    // note: need this to stay alive!
    std::function<void()> f = [&](){
        // Any arguments you like here
        do_whatever(1, 2, 3)
    };
    CreateTask(call_task, static_cast<void*>(&f));
    

    You need to ensure the lifetime of f is longer than that of the task (just as you would for your Params object).


    You can actually avoid std::function altogether, as:

    template<typename Func>
    void call_func(void *args) {
        auto& f = *static_cast<Func*>(args);
        f();
    }
    
    template<typename Func>
    void wrapped_create_task(Func& func) {
        CreateTask(call_func<Func>, static_cast<void*>(&func));
    }
    
    // you can still use `std::function` here, but you don't have to.
    auto f = [&](){
        // Any arguments you like here
        do_whatever(1, 2, 3)
    };
    
    // this works on any object that implements `operator ()`
    wrapped_create_task(f)
    

    Again, it's really important that f remains alive for the duration of its execution. You can't put it on a stack that dies before the task does.