3

I have some embedded OS functions that I need to simulate on a linux machine. The approach I've been instructed to take is to overload the embedded OS functions and wrap them around POSIX threads so the linux machine can handle the embedded OS functions during unit tests and whatnot.

The embedded OS function to create a new thread is: OSCreateTask(OStypeTFP functionPointer, OSTypeTcbP taskId, OStypePrio priority)

I need to convert that OStypeTFP type into the void function pointer that pthread_create expects: (void * (*)(void *) is what the compiler tells me it's expecting)

I was hoping to create a typedef that I could use it like:

typedef void (*OStypeTFP)(void);

// Function to run task/thread in
void taskFunction(void) { while(1); }

// Overloaded Embedded OS function
void OSCreateTask(OStypeTFP tFP, OStypeTcbP tcbP, OStypePrio prio)
{
  pthread_attr_t threadAttrs;
  pthread_t thread;

  pthread_attr_init(&threadAttributes);
  pthread_create(&thread, &threadAttributes, &tFP, NULL);
}

// Creates a task that runs in taskFunction
OSCreateTask (taskFunction, id, prio);

but the compiler complains that functionPointer is of type void (**)(void) when pthread_create expects void * (*)(void *)

Do I need to change my typedef somehow, or do I need to do some typecasting? Both?

cjameston
  • 199
  • 3
  • 11
  • The function has to take a `void*` parameter and return `void*` – imreal Oct 27 '16 at 15:21
  • You need to read more about [`pthread_create`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_create.html) and possibly about threads in general. As well as pointers to functions and functions decays to pointers and what the address-of operator does with a pointer variable. – Some programmer dude Oct 27 '16 at 15:22
  • To "overload" a function (name) is to provide different functions with the same name but different argument types. C does not support that. – John Bollinger Oct 27 '16 at 15:22
  • @JohnBollinger I apologize, "overload" is the wrong word to use. I need to wrap a function that LOOKS like the embedded OS function around pthread_create. – cjameston Oct 27 '16 at 15:25
  • @imreal Will that work since taskFunction "takes" and "returns" void, not a void pointer? – cjameston Oct 27 '16 at 15:28
  • @cjameston no, you have to change its signature, or if you cant write a wrapper that does and ignores the parameter – imreal Oct 27 '16 at 15:30

2 Answers2

5

You need an adapter function:

typedef void (*OStypeTFP)(void);

// Function to run task/thread in
void taskFunction(void) { while(1); }

void *trampoline(void *arg) 
{
    OStypeTFP task = (OStypeTFP)arg;
    task();
    return NULL;
}    

// Overloaded Embedded OS function
void OSCreateTask(OStypeTFP tFP, OStypeTcbP tcbP, OStypePrio prio)
{
  pthread_attr_t threadAttrs;
  pthread_t thread;

  pthread_attr_init(&threadAttrs);
  pthread_create(&thread, &threadAttrs, trampoline, tFP);
}

// Creates a task that runs in taskFunction
OSCreateTask (taskFunction, id, prio);

Of course it is safe only if you system allows casts from void * to function pointer. But since we are at POSIX environment - it should be OK.

Sergio
  • 8,099
  • 2
  • 26
  • 52
  • @BjornA. My bad. Thanks! – Sergio Oct 27 '16 at 15:32
  • Ah ha! I had tried something like this before, but I tried just calling arg() instead of making a new task variable and then calling task(). Thanks! – cjameston Oct 27 '16 at 15:44
  • @2501 It's true that C does not _require_ conversion between function and object pointers to work, but it does not _forbid_ that either. And POSIX, which specifies the `pthread_*` API, *does* require conversion between function pointers and `void *` (specifically) to work. Therefore, this answer is perfectly fine as long as there isn't any additional data that needs to be passed to the shim. Since OP is being cagey about which embedded OS they are emulating, we have no way of knowing whether they will need additional data. – zwol Oct 27 '16 at 17:32
  • @zwol Actually when I said, doesn't permit, I meant the behavior isn't defined. C does permit this as an extension, which is what posix is doing. My original comment ignores this context. – 2501 Oct 27 '16 at 17:33
  • @Sergio Yes C allows conversion between void* and function pointers as an extension. J.5.7 – 2501 Oct 27 '16 at 17:38
  • If you were worried about the function pointer / void pointer conversion, you could pass a pointer to the function pointer instead (with a pthreads barrier to stop `OSCreateTask` returning before `trampoline` has read the function pointer value from the passed pointer). – caf Oct 27 '16 at 23:13
3

If I understand correctly, the signature of a thread procedure on the embedded OS is void thread_proc(void). For POSIX threads, on the other hand, it is void *thread_proc(void *).

You can't paper over this difference with casts and typedefs: you need to arrange for an appropriate return value. You need a shim function:

typedef void (*OStypeTFP)(void);
struct emu_OSCreateTask_thread_start_data
{
    OStypeTFP real_thread_proc;
    // possibly other stuff
};

void *emu_OSCreateTask_shim_thread_proc (void *xctx)
{
    struct emu_OSCreateTask_thread_start_data *ctx = xctx;

    ctx->real_thread_proc();
    return 0;
}

void OSCreateTask(OStypeTFP tFP, OStypeTcbP tcbP, OStypePrio prio)
{
    pthread_attr_t threadAttrs;
    pthread_t thread;
    struct emu_OSCreateTask_thread_start_data *ctx =
        malloc(sizeof(struct emu_OSCreateTask_thread_start_data));

    ctx->real_thread_proc = tFP;

    pthread_attr_init(&threadAttributes);
    pthread_create(&thread, &threadAttributes,
                   emu_OSCreateTask_shim_thread_proc, ctx);
}

Note: ctx is allocated on the heap, and leaked, because it needs to live until after emu_OSCreateTask_shim_thread_proc returns, which may be indefinitely later than when OSCreateTask returns. Without knowing more about the API you're trying to emulate, I can't tell you where you ought to be stashing it so that it can get freed when appropriate, but there's probably somewhere. Maybe in tcbP?

Note 2: I use a context object instead of just stuffing the "real_thread_proc" in pthread_create's context pointer (as in Sergio's answer) because I suspect you will wind up needing to do more stuff in the shim, and needing more data from the outer context to do it. (You're on a POSIX system, so it is safe to stuff function pointers into a void *.)

zwol
  • 135,547
  • 38
  • 252
  • 361
  • I guess we should`free()` `ctx` before return from `emu_OSCreateTask_shim_thread_proc()` – Sergio Oct 27 '16 at 15:36
  • @Sergio Not necessarily; it might wind up needing to survive until the equivalent of `pthread_join`. – zwol Oct 27 '16 at 15:38
  • @zwol This is the correct answer as it wraps the pointer in the struct. I see no reason to not free after the function call is done as pthread_join known nothing about the passed parameter. It is perfectly correct that the created thread frees the memory. – 2501 Oct 27 '16 at 17:25
  • @2501 We don't know all the wrinkles of the complete API the OP is trying to emulate; we cannot say whether that is true. – zwol Oct 27 '16 at 17:29