Link to home
Start Free TrialLog in
Avatar of cfm
cfm

asked on

Callback functions

I'm trying to set up a callback function in a class, but I'm getting errors at compiling time.

The following gets accepted:
class CEngine
{
public:
  void SetCallBack(void (*aFunction)(someclass))
  {
    aCallBackPointer = aFunction;
  }
  void CallFunction()
  {
    aCallBackPointer(some_correct_parameter);
  }
private:
  void (*aCallBackPointer)(someclass);
};

The following cause an error:
class CView
{
public:
  void Update(someclass aParameter);
  void SetCallBack()
  {
    CEngine eng;
    eng.SetCallBack(Update);
  }
};

The error gets generated for the "eng.SetCallBack(Update);" line.  The error description is: Can not convert parameter 1 from 'void (someclass)' to 'void (__cdecl *)(someclass)'.

Help !!
Avatar of jkr
jkr
Flag of Germany image

A class' member function can only be a callback when it's declared 'static' (as the compiler tries to pass 'this' as the 1st parameter otherwise), e.g.

class CMyClass: public CWhatever
{
//...

static MyClassesTimerCallBack ( HWND, UINT, UINT, DWORD); // parameter signature for a timer callback

}

To use it (in a method of CMyClass):

// set timer callback procedure....
TIMERPROC lpTimerProc = ( TIMERPROC) MyClassesTimerCallBack´;


// create timer
if ( ! ( m_uTimerID = SetTimer ( NULL,
( UINT) this,
TIMER_DELAY,
lpTimerProc
)

To use it outside of CMyClass, you'll have to specify 'CMyClass::MyTimerCallBack' instead.

Let me know if you have further questions.
BTW: The example is  a Windows timer callback, but it also suits your problem...

Feel free to ask if you need more information...
Avatar of cfm
cfm

ASKER

Static functions don't have access to the class members and I need that.

I also don't want to pass the class pointer to the engine, since the engine must work with any calling class.

Any other suggestions of doing this?

> A class' member function can only be a callback when it's
> declared 'static' (as the compiler tries to pass 'this'
> as the 1st parameter otherwise), e.g.

this is only true when you do not have control over how the callback is actually called, in this case where you are control both ends, everything is fine.

The program below shows the sort of thing you can do (I have not added a parameter to the callback, but this is very easy to do, if you want it).

class AbstractCallback {
public:
    virtual void operator()() = 0;
};

template <class T>
class SimpleCallback : public AbstractCallback  
{
public:
    typedef void (T::*CB)();
      
    SimpleCallback(T* instance, CB callback)
        : mInstance(instance), mCallback(callback)
      {}

    virtual ~SimpleCallback() {}

    virtual void operator()() {
        (mInstance->*mCallback)();
    }
protected:
    T* mInstance;
    CB mCallback;
};

class CEngine
{
public:
    void SetCallBack(AbstractCallback* cb) {
        mCb = cb;
    }
    void CallFunction() {
        (*mCb)();
    }
private:
    AbstractCallback* mCb;
};

class CView
{
public:
    void Update() {
        cout << "Hello" << endl; }
    void SetCallBack() {
        SimpleCallback<CView>* cb = new SimpleCallback<CView>(this, Update);
        CEngine eng;
        eng.SetCallBack(cb);
        eng.CallFunction();  // Force the callback
    }
};

void main()
{
    CView cv;
    cv.SetCallBack();
}


also, the program does not delete the callback pointer, either the caller or the client can do this depending on how you set things up, but the destructor of CEngine or CView should do this.

>>Static functions don't have access to the class members
>>and I need that.

That's (partially) correct. However, there are several methods to get around this - could you explain the environment in which you're using the callback (e.g. the declaration of 'SetCallBack()', which is causing the compiler error)?

Avatar of cfm

ASKER

I'm writing an engine that controls communication and runs asynchronously.  The engine must somehow inform other functions if something was received (e.g. inform the view to display the info).

Creating a new message type and posting that to the message queue is not possible.  Asking the other classes to poll the engine is possible, but I don't like it.  A callback function seemed the way to go.

Some more info.  This all happens in a dll, so the declaration of static global variables is out.  The use of a static class variable storing the this pointer is possible if you are sure only one instance of the class will be created.

Any other solutions?
The solution I have posted should work for you (with no statics involved at all).  

In essence the callback function is wrapped in a class, and the use of an abstract base class and a templated derived class ensures that the solution is generic.
Avatar of cfm

ASKER

Thanks jasonclark.  Post something as an answer and I'll accept it.
ASKER CERTIFIED SOLUTION
Avatar of jasonclarke
jasonclarke

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial