17
votes

I am calling SetTimer in a function of a Class.

SetTimer(NULL, 0, 10000, (TIMERPROC) TimerCallBack);  

Where TimerCallBack is:

static VOID CALLBACK TimerCallBack(HWND, UINT, UINT, DWORD)

Now my need is to call one of the method of class which initiated timer, since TimerCallBack is static it has no access to the class object anymore.

I cant find any way to pass object pointer along with the SetTimer so that I can receive it back on Callback function.

Is there any other way to achieve this, if its not supported using SetTimer then which other way I can implement this.

4

4 Answers

28
votes

You don't need a map. Use the idEvent parameter. Microsoft gave it the UINT_PTR type so it fits a pointer, even in 64 bits:

SetTimer(hwnd, (UINT_PTR)bar, 1000, MyTimerProc);

void CALLBACK MyTimerProc(HWND, UINT, UINT_PTR idEvent, DWORD)
{
    Bar* bar = (Bar*)idEvent;
}

Note that you need to use an actual HWND. If the HWND is null, Windows will generate the idEvent internally.

9
votes

Obviously, if you were directing timer messages at a window, you could just store the user data with the window.

The only way to do this with a TimerProc is to make a class that manages a statically scoped map of timer-id's to user data objects.

Something like this (As this is a c++ question, Im just implementing a quick and dirty functor type thing so that the TimerMgr can arrange callbacks directly to members of classes, which is usually why you are trying to store user data:

// Timer.h
#include <map>
class CTimer {
public:
  class Callback {
  public:
    virtual void operator()(DWORD dwTime)=0;
  };
  template<class T>
  class ClassCallback : public Callback {
    T* _classPtr;
    typedef void(T::*fnTimer)(DWORD dwTime);
    fnTimer _timerProc;
  public:
    ClassCallback(T* classPtr,fnTimer timerProc):_classPtr(classPtr),_timerProc(timerProc){}
    virtual void operator()(DWORD dwTime){
      (_classPtr->*_timerProc)(dwTime);
    }
  };

  static void AddTimer(Callback* timerObj, DWORD interval){
    UINT_PTR id = SetTimer(NULL,0,interval,TimerProc);
    // add the timer to the map using the id as the key
    _timers[id] = timerObj;
  }
  static void CALLBACK TimerProc(HWND hwnd,UINT msg,UINT_PTR timerId,DWORD dwTime){
    _timers[timerId]->operator()(dwTime);
  }
private:
  static std::map<UINT_PTR, Callback*> _timers;
};

// In Timer.cpp
#include <windows.h>
#include <Timer.h>
std::map<UINT_PTR,CTimer::Callback*> CTimer::_timers;

// In SomeOtherClass.cpp
class CSomeClass {
  void OnTimer1(DWORD dwTime){
  }
public:
  void DoTimerStuff(){
    CTimer::AddTimer( new CTimer::ClassCallback<CSomeClass>(this,&CSomeClass::OnTimer1), 100);
  }
};

Removing timers from the map is left as an exercise for the reader :)


  • ClassCallback needed to inherit Callback.
  • static variables need to be defined
  • Code now builds and runs correctly on MSVC 9.0
4
votes

There exists one more solution, but in order to use that one - HWND parameter in SetTimer should be non-NULL.

It's possible to store extra data into window itself, using API function SetWindowLongPtr with parameter GWLP_USERDATA.

So you could add into your class following function:

void SetLocalTimer(UINT_PTR nIDEvent, UINT nElapse)
{
    SetWindowLongPtr(m_hWnd, GWLP_USERDATA, (LONG_PTR)this);
    SetTimer(nIDEvent, nElapse, _TimerRouter);
}

and timer router function, which determines what to do on given timer.

static void CALLBACK _TimerRouter(HWND hwnd, UINT, UINT_PTR nEventID, DWORD)
{
    YourClassName* inst = (YourClassName*)GetWindowLong(hwnd, GWLP_USERDATA);
    if( !inst )
        return;

    switch (nEventID)
    {
        case 0:
            inst->DoThis();
            break;
        case 1:
            inst->DoThat();
            break;
    }
}

This allows also to use nEventID as function identifier to be called.

There still exists a risk that user data will be used from more than one place, but I think it's good practice to keep UI window matching it's class model this pointer.

3
votes

Given that you don't appear to be using MFC (CWnd::OnTimer means you'd have access to the class), and you don't have a HWND (you could conceivably set a property on the HWND on timer-creation and get it back in your proc), there is another option.

SetTimer returns a UINT_PTR which is the timer ID. This is what you'll get in your TimerProc and will also pass to KillTimer when you're done with it. Using this, we can create a map that maps a timer ID to some user-defined object of your creation:

class MyAppData
{
}; // eo class MyAppData

typedef std::map<UINT_PTR, MyAppData*> MyDataMap;
MyDataMap dataMap;

Then we create your timer:

MyAppData* _data = new MyAppData();  // application-specific data
dataMap[SetTimer(NULL, 0, 10000, (TIMERPROC)TimerCallBack)] = _data;

And in your procedure:

static void CALLBACK TimerCallBack(HWND _hWnd, UINT _msg. UINT_PTR _idTimer, DWORD _dwTime)
{
    MyAppData* data = dataMap[_idTimer];
} // eo TimerCallBack