0

What I am trying to accomplish is registering a callback in an embedded environment. This callback will be in one of two forms:

void (*cb) (void *ctxt); or
void ClassA::VirtualFn (void);

This code will only run on an ARM platform using GCC. The register callback function MUST be virtual due to some dynamic binding done at runtime. Both the above functions are equivalent at the assembly level (they both take a single pointer). Furthermore the callback mechanism is in assembly for performance purposes because it occurs in ISR context so I don't have to worry about that. All I really need is a function that takes either of the above and stores the passed function pointer and context pointer. ie:

void isr_cb (void *ctxt) {}
gpio->RegisterIsr (isr_cb, cptr);
gpio->RegisterIsr (&ClassA::IsrHandler, this);

I've tested this by casting the virtual member function to (void (*) (void *)) and indeed everything works as expected (apart from the compiler warnings).

an0n
  • 61
  • 1
  • 7
  • Is it really a `virtual` member-function? Must it be called virtual, or is simple dispatch enough? – Deduplicator Aug 15 '14 at 16:43
  • Yes, unfortunately it must be virtual. I do runtime loading of elf objects so all member functions are virtual in both classes. I believe this precludes me from using any sort of templating mechanism right? – an0n Aug 15 '14 at 16:48
  • Would `std::function` (c++11) or `boost::function` combined with `bind` not work? – Mark B Aug 15 '14 at 16:49
  • The functions being in elf-SOs does not in any way imply `virtual`. Also, I'm not asking whether the function is `virtual`, but whether it is `virtual` and you have to honor that. @MarkB: I read it that an0n needs high performance here, so should avoid any overhead. – Deduplicator Aug 15 '14 at 16:50
  • This is an embedded system running a custom kernel using newlib-nano for libc. The elf's are not shared objects. They are statically linked elf's with get loaded using an elf loader I wrote. They must be virtual to allow user-space applications to access member functions at runtime using the vtable. The callback mechanism is an ISR redirection implemented in assembly. Basically 2 loads and a branch so once I have pointers and the ISR is installed (pointer and context) I don't need any sort of delegate method to call the callback. – an0n Aug 15 '14 at 17:20
  • Sorry there are a lot of peculiar details to the system. I can give more info if necessary. – an0n Aug 15 '14 at 17:21

3 Answers3

1

Casting a virtual member function to void (*)(void *) sounds like a harsh approach.

You said:

The register callback function MUST be virtual due to some dynamic binding done at runtime.

I take it to mean that you have an object of type ClassA. If that is correct, then you should:

  1. Register a static member of ClassA to the callback mechanism whose signature is:

    void (*)(ClassA& ref);
    
  2. Add virtual member function that does the real work.

    void doStuff();
    
  3. Let's say the registered callback function is:

    static void foo(Class& ref);
    

    In the implementation of foo, call doStuff on ref.

    static void foo(Class& ref)
    {
       ref.doStuff();
    }
    
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • Actually if you changed the parameter to a pointer it would effectively have the same signature as the free function callback. – Mark B Aug 15 '14 at 17:16
  • I agree, casting to void (*)(void *) was just to prove the rest of the system is working. As this is a user facing API I want to avoid the compiler warnings. However if there was a trick to silence the compiler (without pragmas, etc) then I'd be happy with a non-portable solution. The virtual member of ClassA could be any class. In the end I need to ignore the type information and just take the pointer to the function. – an0n Aug 15 '14 at 17:27
0

You will have a much easier time if you can make the utilize either std::bind, in c++11 or boost::bind. Both will let you mix member function pointers and free functions with each other if the interface (i.e. actual output type and actual input parameter type are the same) e.g.

int f(double val)
{

}

class A {

   int memberf(double val);

}

A instanceOfA;
auto f1 = std::bind(f, std::placeholders::_1);
auto f2 = std::bind(&A::memberf, &instanceOfA, std::placeholders::_1);

f1 and f2 are now both functions that take a double and return an int, independent of the fact that one is a member function and the other is a free function, there is no cast needed. I have to admit I can't write the actual type from memory.

Harald Scheirich
  • 9,676
  • 29
  • 53
  • It seems this is the most common, portable approach. However it may be heavy handed for this deeply embedded system. I understand there is a performance penalty when invoking the boost::bind callback. Currently the redirection adds only 4 instructions which I believe is 6 clock cycles with the pipeline flush after the branch. Minimizing the added latency on interrupt is a high priority. In addition I would need to compile/add the boost library and dependencies. My current total ROM space is 256kB. – an0n Aug 15 '14 at 17:39
  • @an0n How many instructions was the code using `bind` and `function`? Keep in mind that `bind` and friends are header-only components. – Mark B Aug 15 '14 at 19:11
  • @anon, that kind of level of optimization and limit I usually don't have to deal with. Solutions always depend on requirements, if your code works for you but you don't 'like' maybe you might just have to live with them. Otherwise you could pay the performance and code size cost and write your own functor, that correctly wraps your the free function pointer + context and the member function in its subclasses. I am not sure you can have it both ways – Harald Scheirich Aug 15 '14 at 21:35
0

First things first, there is no such thing as a generic function-pointer in C nor C++.

Still, a fully-conformant and high-performant solution is possible.

As the non-member-function is already of the proper type, it is trivial.

  1. If you know the full set of non-virtual member-functions and virtual-function slots you might want to pass, define a simple forwarder for each, along the lines of:

    void forward_to_IsrHandler(void* p) {
        ((ClassA*)p)->IsrHandler();
    }
    gpio->RegisterIsr (forward_to_IsrHandler, (void*)this);
    
  2. If you don't know, or really want to sacrifice standard-conformity at the altar of performance, you can just convert your member-function-pointer to a normal function-pointer (gcc extension, other compilers have their own):

    void (*tmp)(ClassA*) = (void(*)(ClassA*))&ClassA::IsrHandler;
    

    Use -Wno-pmf-conversions to remove the warning on use of the extension.
    Because a call to a function void(*)(void*) and void(*)(ClassA*) look exactly the same on ARM, you can then use it thus, even though it is strictly speaking UB:

    gpio->RegisterIsr ((void(*)(void*))tmp, (void*)this);
    

Selectively disabling GCC warnings for only a small code-segment:

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wno-pmf-conversions"
    // Do the foul deed here
#pragma GCC diagnostic pop
Community
  • 1
  • 1
Deduplicator
  • 44,692
  • 7
  • 66
  • 118
  • 1. The set of virtual callback functions is not known. 2. Since the calling code will be user compiled code (not linked directly to kernel) I'd rather avoid disabling conversion warnings all together but it's looking like I may have to go that direction... – an0n Aug 15 '14 at 17:46
  • @an0n: That only disables that one warning. Still, you might be better off disabling it only for that one line, added example. – Deduplicator Aug 15 '14 at 17:52