views:

294

answers:

4

I need to bind a method into a function-callback, except this snippet is not legal as discussed in demote-boostfunction-to-a-plain-function-pointer.

What's the simplest way to get this behavior?

struct C {
void m(int x) {
    (void) x;
    _asm int 3;
}};

typedef void (*cb_t)(int);
int main() {
    C c;
    boost::function<void (int x)> cb = boost::bind(&C::m, &c, _1);
    cb_t raw_cb = *cb.target<cb_t>(); //null dereference
    raw_cb(1);
    return 0;
}
+1  A: 

Either you can shove that bound parameter into a global variable and create a static function that can pick up the value and call the function on it, or you're going to have to generate per-instance functions on the fly - this will involve some kind of on the fly code-gen to generate a stub function on the heap that has a static local variable set to the value you want, and then calls the function on it.

The first way is simple and easy to understand, but not at all thread-safe or reentrant. The second version is messy and difficult, but thread-safe and reentrant if done right.

Edit: I just found out that ATL uses the code generation technique to do exactly this - they generate thunks on the fly that set up the this pointer and other data and then jump to the call back function. Here's a CodeProject article that explains how that works and might give you an idea of how to do it yourself. Particularly look at the last sample (Program 77).

Note that since the article was written DEP has come into existance and you'll need to use VirtualAlloc with PAGE_EXECUTE_READWRITE to get a chunk of memory where you can allocate your thunks and execute them.

Eclipse
on-the-fly code gen is definitely way too complex. looking for as simple as possible.
Dustin Getz
the first way is kinda what I did, see my reply..is that what you're talking about?
Dustin Getz
I figured it would be, but thought I'd throw it out there anyway.
Eclipse
A: 

i have it working right now by turning C into a singleton, factoring C::m into C::m_Impl, and declaring static C::m(int) which forwards to the singleton instance. talk about a hack.

Dustin Getz
If you're only ever going to use one call-back at a time, then this will work just fine.
Eclipse
+1  A: 

You can make your own class to do the same thing as the boost bind function. All the class has to do is accept the function type and a pointer to the object that contains the function. For example, this is a void return and void param delegate:

template<typename owner>
class VoidDelegate : public IDelegate
{
public:
   VoidDelegate(void (owner::*aFunc)(void), owner* aOwner)
   {
      mFunction = aFunc;
      mOwner = aOwner;
   }
   ~VoidDelegate(void)
   {}
   void Invoke(void)
   {
      if(mFunction != 0)
      {
         (mOwner->*mFunction)();
      }
   }

private:
   void (owner::*mFunction)(void);
   owner* mOwner;
};

Usage:

class C
{
   void CallMe(void)
   {
      std::cout << "called";
   }
};
int main(int aArgc, char** aArgv)
{
   C c;
   VoidDelegate<C> delegate(&C::CallMe, &c);
   delegate.Invoke();
}

Now, since VoidDelegate<C> is a type, having a collection of these might not be practical, because what if the list was to contain functions of class B too? It couldn't.

This is where polymorphism comes into play. You can create an interface IDelegate, which has a function Invoke:

class IDelegate
{
   virtual ~IDelegate(void) { }
   virtual void Invoke(void) = 0;
}

If VoidDelegate<T> implements IDelegate you could have a collection of IDelegates and therefore have callbacks to methods in different class types.

Chap
looks promising..can you post usage?
Dustin Getz
Unfortunately, you're just replacing one member function call with another.
Eclipse
Correct. This is called external polymorphism. You basically can pass off a member function to an object, pass this object around and call it later on the class that it was setup for. GREAT for events! This is like delegates in C#.
Chap
Yes, but it doesn't solve the OPs problem of trying to get a raw pointer suitable for call-backs.
Eclipse
A: 
#include <iostream>
typedef void(*callback_t)(int);

template< typename Class, void (Class::*Method_Pointer)(void) >
void wrapper( int class_pointer )
{
   Class * const self = (Class*)(void*)class_pointer;
   (self->*Method_Pointer)();
}

class A
{
public:
   int m_i;
   void callback( )
   { std::cout << "callback: " << m_i << std::endl; }
};

int main()
{
   A a = { 10 };
   callback_t cb = &wrapper<A,&A::callback>;
   cb( (int)(void*)&a);
}
caspin