class MyTimer {
...
void MyTimerProc();
...
}
int nTimerID = SetTimer( NULL, 1234, 1000, MyTimerProc );
...you will get a compiler error like:
{{{{ Example 1 -- using a global (non-member) function }}}}
#include <windows.h>
#include <stdio.h>
#include <Mmsystem.h>
#pragma comment(lib, "Winmm.lib" )
void CALLBACK MyTimerProc( UINT, UINT, DWORD, DWORD, DWORD ) {
printf( "---------- timer tick ----------- \r\n");
}
class CBaseTimer {
public:
CBaseTimer(int nIntervalMs) { m_nIntervalMs=nIntervalMs;}; // ctor
~CBaseTimer() { Kill(); }; // dtor
int m_nID;
long m_nIntervalMs;
void Start() { // TBD: Check if 0 (failed)
m_nID= timeSetEvent( m_nIntervalMs, 10,
MyTimerProc, 0, TIME_PERIODIC );
}
void Kill() {
::timeKillEvent( m_nID );
}
};
//------------------------------------- main() -- example usage of the object
int main(int argc, char* argv[])
{
CBaseTimer cTimer( 1000 );
cTimer.Start();
for (int j=0; j< 40; j++) {
Sleep(100);
printf( "Hi there\r\n" );
}
return 0;
}
But if you want the callback to be a member function:
class CBaseTimer {
...
void CALLBACK MyTimerProc( UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2 );
...
You will get the dreaded compiler error. The common/documented way to avoid this is to make the callback into a static member function.
class CBaseTimer {
...
static void CALLBACK MyTimerProc( UINT, UINT, DWORD, DWORD, DWORD ); //<<--- note: "static"
...
The "static" specifier indicates that it is common code used by all instances of the object
and that the code cannot directly access non-static members. It is basically a global function, just like in Example 1; the calling convention is the same (no
this pointer on the stack).
{{{{ Example 2: Failing attempt to use a member function }}}}
class CMsgTickTimer: public CBaseTimer {
public:
CMsgTickTimer( int nIntervalMs, LPCSTR szMsg ) {
m_nIntervalMs= nIntervalMs;
strncpy( m_szMsg, szMsg, sizeof(m_szMsg) );
}
char m_szMsg[100];
static void CALLBACK MyTimerProc( UINT, UINT, DWORD, DWORD, DWORD ) {
printf( "------ %s ------- \r\n", m_szMsg ); // <<-- ERROR
}
};
The MyTimerProc function is attempting to access m_szMsg, a per-instance (non-static) value, but it is trying to do that in a static member function. And that's not allowed. Thus, the compiler error:
{{{{ Example 3: Final version, allows access to derived object members }}}}
#include <windows.h>
#include <stdio.h>
#include <Mmsystem.h>
#pragma comment(lib, "Winmm.lib" )
class CBaseTimer {
public:
CBaseTimer(int nIntervalMs=1000) { m_nIntervalMs=nIntervalMs; }; // ctor
~CBaseTimer() { Kill(); }; // dtor
int m_nID;
long m_nIntervalMs;
static void CALLBACK MyTimerProc( UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2 ) {
CBaseTimer* pThis= (CBaseTimer*)dwUser;
pThis->OnTimer();
}
void Start() { // TBD: Check if 0 (failed)
m_nID= timeSetEvent( m_nIntervalMs, 10, MyTimerProc, (DWORD)this, TIME_PERIODIC ); //<<--- Note: this
}
void Kill() {
::timeKillEvent( m_nID );
}
virtual void OnTimer()=0; // must supply a fn in derived objects
};
//------------------------------------------ derived from the base class
class CMsgTickTimer: public CBaseTimer {
public:
CMsgTickTimer( int nIntervalMs, LPCSTR szMsg ) {
m_nIntervalMs= nIntervalMs;
strncpy( m_szMsg, szMsg, sizeof(m_szMsg) );
}
char m_szMsg[100];
void OnTimer() {
printf( "---------- %s ----------- \r\n", m_szMsg );
}
};
//------------------------------------------------ example of usage
int main(int argc, char* argv[])
{
CMsgTickTimer cTimer( 500, "1/2 second is up" );
cTimer.Start();
for (int j=0; j< 40; j++) {
Sleep(100);
printf( "Hi there\r\n" );
}
return 0;
}
The output shows that the OnTimer() fn of the derived object is called periodically.
MyThreadProc( LPVOID p )
{
CSomeObject* pThis= (CSomeObject*)p;
printf("Member string variable is %s", pThis->m_sSomeMember );
}
But the technique used in Example 3 is much more useful. It uses pThis to call into a function of the object. That way, execution "gets into" the object and from that point on, your code can access members normally.
UINT MyObjectThreadProc( LPVOID pParam ) {
MyObject* pThis= (MyObject*)pParam;
UINT nRet= pThis->Run(); // The thread does all the work in here
return( nRet );
}
=-=-=-=-=-=-=-=-=-=-=-=-=-
Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.
Comments (2)
Commented:
A lot of GUI libraries (including MFC) use the way you explained. This approach allows to use virtual functions and make class hierachies for the GUI controls.
If it's allowed to me, I'd like to say "thanks, good job and nice article."
Commented: