2

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.

vixu
  • 119
  • 1
  • 6
  • 2
    *variable number of arguments of variable types?* Hard to do. Probably not worth doing. When you have a function with completely arbitrary inputs, you probably have a design error. – user4581301 Aug 20 '20 at 23:30
  • 1
    You could always make your wrapper hide the `void*` stuff by aggregating `Params` into another struct that includes a function pointer for dispatch. Then, you can define all your "task" functions as functions receiving a `Params&` type or whatever. A hidden dispatcher will digest the aggregated struct (params + function pointer) and call your C++ function correctly. – paddy Aug 20 '20 at 23:35
  • 2
    Rethinking my previous statement. Are the things you want to pass in known at compile time? If so, then yes tuple and templates to the rescue. Looks like you're recreating `std::thread`. – user4581301 Aug 20 '20 at 23:37
  • 1
    If the API you use doesn't support it, then it's not really possible. The only work-around is to use objects (like your structure object `params`) and pass pointers to them. – Some programmer dude Aug 20 '20 at 23:45
  • 1
    Can you just pass an `std::function` in? – Eric Aug 20 '20 at 23:51
  • 1
    @Eric you can't really pass std::function as function pointer – vtronko Aug 20 '20 at 23:55
  • 1
    The problem is once you have passed in a bunch of data and passing it around as a `void*` has erased the type, you need to know what the type was to get the data back out again. If you have C++11 (or better) support, Eric's pitch of `std::function` is where to start. It does exactly what you're trying to do. – user4581301 Aug 20 '20 at 23:57
  • 2
    @vtronko , no, but you can pass it as the user argument and then cast and call it in the function you do pass as a function pointer. . – user4581301 Aug 20 '20 at 23:58
  • The @Eric 's answer seems interesting, since I am on C++17 I will try it out but I wonder if it is really worth it to go with this approach in a quite big project. Not sure if it would make the code slower or heavier on memory. – vixu Aug 21 '20 at 00:05
  • 1
    If you have C++17 and its Standard Library is fully implemented for freeRTOS, you should have access to goodies and can make a lot of the target-specific calls you have to use just go away. I haven't found any documentation for `createTask`, but if it is spinning up threads or something like them, then `std::thread` probably took care of this problem for you. I'd kill for like `std::thread` in the embedded systems work I do. – user4581301 Aug 21 '20 at 00:14
  • 1
    Anyway, start with the simple, easy way to do it. If it is fast enough, who cares how slow it is? If it's too slow, you'll find out pretty fast, and try something more complicated and hopefully faster. And if it's slow, but only happens once at start-up, usually it falls into the "Who cares? bucket. Start with simple and works and only go for hard when you have to. – user4581301 Aug 21 '20 at 00:17
  • @user4581301 I don't know if I could use threads from other library but I will keep that in mind, thanks. And thanks to everyone for their suggestions – vixu Aug 21 '20 at 00:22
  • 1
    Looks like [How can I pass a C++ lambda to a C-callback that expects a function pointer and a context?](https://stackoverflow.com/questions/20525977/how-can-i-pass-a-c-lambda-to-a-c-callback-that-expects-a-function-pointer-and) is the same question, minus the FreeRTOS connection. – Eric Aug 21 '20 at 15:29

1 Answers1

2

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.

Eric
  • 95,302
  • 53
  • 242
  • 374
  • Thanks for the solution, I have implemented this in my code and it works. However, I cannot stop thinking about the performance. I will ask maybe a little bit inappropriate question. Since I have an embedded system project with very limited power supply, I believe that the performance is important for me, because the more is done in the powered state the better. But my team and I are inexperienced so the safety is also important. I need some more design guidance, should I just go with it and eventually refactor it if something goes wrong or just scrap it to maximize the performance? – vixu Aug 21 '20 at 15:05
  • 1
    What makes you believe this is unperformant? Chances are the cost of creating a task is higher than the cost of the `std::function`. – Eric Aug 21 '20 at 15:14
  • Well, I have read that ```std::function``` is slower compared to standard function pointers and it can allocate memory dynamically and I think that it is considered bad for embedded systems (but I am not sure why). I am just a little bit confused but I want to make an educated decision somehow. – vixu Aug 21 '20 at 15:19
  • 1
    I've updated with a version that performs no dynamic memory allocation. However, bear in mind that `CreateTask` likely _does_ allocate memory anyway. – Eric Aug 21 '20 at 15:21