本文说明两个问题:1.windows的消息处理机制;2.怎么往SetTimer的回调函数传递参数。首先看第一个问题,我们都知道windows是消 息驱动的,windows呈现给用户的任何可以看到听到的东西几乎都是消息驱动的,在底层windows为每个线程准备了一个消息队列,如果用户线程注册 了某个消息,那么在适当的时候windows就会将消息投递到该线程的消息队列中,然后由该线程取出队列中的消息,然后处理之,这个过程有两个参与者,一 个是windows系统,它主要负责投递消息,收不收是用户线程的事,另一个就是用户线程,它主要负责取出消息并处理消息,即使用户线程因为睡眠或者根本就没有设定消息循环,系统还是会投递的,系统和用户线程的消息接口就是消息队列,这就在用户和系统之间关于消息解除了耦合,在用户线程处理消息的时候,其 实还有一个消息队列,因为一个线程不一定只接收一种消息而且不一定马上就能处理完并返回,这个消息队列我们把它叫做消息分发队列或者简称分发队列用来与系统的消息队列区分,注意分发队列里面的消息都是已经格式化后的消息,分发给谁呢?当然是分发给消息的回调函数了,对于有窗口的就是先分发给窗口过程,然后 由窗口过程分发给具体的处理函数。
下面我们来通过一个例子说明一下,用vs2005或VC建立一个Win32工程,然后看自动生成的代码:
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
...
// 主消息循环:
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);//msg中按照消息号识别
}
}
return (int) msg.wParam;
}
以上就是消息循环,该线程循环接收消息,然后DispatchMessage消息,Dispatch到窗口过程:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
switch (message)//message就是消息号
{
case WM_COMMAND:
...
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
以上实际上就是windows消息机制的全景,对于windows的timer当然也要套用上面的模式了,在SetTimer调用后,实际上就注册了WM_TIMER消息,以下是函数定义:
UINT_PTR SetTimer(
HWND hWnd,
UINT_PTR nIDEvent,
UINT uElapse,
TIMERPROC lpTimerFunc
);
lpTimerFunc就是回调函数,其形式为:
VOID CALLBACK TimerProc(
HWND hwnd,
UINT uMsg,
UINT_PTR idEvent,
DWORD dwTime
);
SetTimer的参数uElapse就是时间间隔,比如设置为1000即1秒,现在有了一个问题,请看下列代码:
VOID CALLBACK TimerFunc(HWND hwnd,UINT uMsg,UINT_PTR idEvent,DWORD dwTime)
{
Sleep(5000);
}
DWORD CALLBACK AutoBakup( PVOID lpParam )
{
MSG msg;
UINT id=SetTimer(NULL,1,1000,TimerFunc);
BOOL bRet;
while( ((bRet = GetMessage(&msg,NULL,0,0))!= 0) )
{
if(bRet==-1)
{
break;
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
KillTimer(NULL,id);
return 0;
}
竟 然在timer的处理函数中睡眠了,那么SetTimer时的1000毫秒触发一次timer回调还会进行吗?其实不会进行了,本质上那个1000毫秒就 不是说触发回调函数的间隔,而是产生WM_TIMER消息的间隔,因为回调函数中睡眠了,所以也就阻塞了本线程,这个线程就不再往前走了,但是底层的 WM_TIMER消息也会因此而不再投递吗?不会,消息的投递其实不是本线程进行的,而是系统进行的,本线程已经睡眠了,但是系统却没有睡眠,它会继续往该线程的消息队列投递WM_TIMER消息,只不过这些消息不再由该线程的GetMessage取出了,因为它睡眠了(如果对回调函数是否和 GetMessage是否为统一线程有疑义,那么用GetCurrentThreadId()检测一下就好),消息全部堆积在队列里面,然后等待睡眠结束后再一个一个处理。好吧,这个问题解决了,下一个问题又来了,试着将回调函数改为下面的:
VOID CALLBACK TimerFunc(HWND hwnd,UINT uMsg,UINT_PTR idEvent,DWORD dwTime)
{
MessageBoxA(NULL,"zhaoya","msg",MB_OK);
Sleep(5000);
}
发 现完全按照SetTimer时设置的那样,1秒弹出一个消息框,一个一个出来,Sleep好像根本就没有执行,线程根本没有睡眠阻塞,我把 MessageBoxA(NULL,"","",MB_OK)换成printf就不行了,线程立即执行Sleep,这是为何?关键就在 MessageBoxA上,它实际上是一个模态对话框,既然是对话框它就有消息循环,它并没有开独立的线程,因此可以肯定还是原来的线程,原来 MessageBoxA的内部实现了GetMessage-->TranslateMessage-->DispatchMessage的循环,由于还是原来的线程,所以它GetMessage将还包括WM_TIMER,另外还有消息框自己的一些关于界面的消息,比如WM_PAINT,消息框 的出现只是在系统中又注册了一些需要的消息,就是消息框的关于界面事件的消息。WM_TIMER回调函数在那,于是调用之,于是又是一个消息框出来了,但 是一旦你点击最上面的OK键,那么程序立即向下进行,进入Sleep,开始睡眠,所有的消息框好像死掉一般,因为就一个线程,它睡眠了,消息循环不再进行,当然所有消息框的消息循环也不再进行,什么WM_PANIT之类的消息都将阻塞,于是消息框们都和死了一样。
通过以上论述,我想关于windows消息大致已经说清了,下面解决第二个问题,如何向SetTimer的回调函数传递自定义参数。再看TimerProc:
VOID CALLBACK TimerPro(HWND hwnd,UINT uMsg,UINT_PTR idEvent,DWORD dwTime);
看 看MSDN关于第三个参数idEvent的解释,就是Timer的id,我们可以用SetTimer的返回值来作为自定义参数的指针,然后在 TimerProc回调函数中通过idEvent取出,强制转换为自己定义的类型,可是SetTimer的返回值并不是我们所能左右的啊,那么还是要从 MSG结构下手了:
typedef struct tagMSG { // msg
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG;
看 看wParam和lParam,MSDN上说是额外参数,我们只需要重新设置值就可以了,于是我们要取wParam和lParam这两个参数与 TimerProc形参的交集,这个交集就是我们可以自定义的参数,经过测试,发现wParam其实就是SetTimer返回的id,也就是 TimerProc的形参idEvent,于是例子如下:
VOID CALLBACK TimerFunc(HWND hwnd,UINT uMsg,UINT_PTR idEvent,DWORD dwTime)
{
char * buf = (char*)idEvent;
printf( "%s/n", buf );//这里打印的就是"abcde"
}
DWORD CALLBACK AutoBakup( PVOID lpParam )
{
char * buf = "abcde";
MSG msg;
UINT id=SetTimer(NULL,1,1000,TimerFunc);
BOOL bRet;
while( ((bRet = GetMessage(&msg,NULL,0,0))!= 0) )
{
if(bRet==-1)
{
break;
}
else
{
TranslateMessage(&msg);
msg.wParam = (WPARAM)buf;//这里设置参数
DispatchMessage(&msg);
}
}
KillTimer(NULL,id);
return 0;
}
如 果你说,这么把idEvent占了的话,真正的timer的id不就得不到了吗?唉,晕!如果能传递一个指针那么就没有设呢没不能传递了,X位机器上的X 位指针是可以指遍整个虚拟内存的,你可以把自定义参数包装成一个结构,该结构的一个字段指向真正的timer的id,一切...
分享到:
相关推荐
SetTimer函数的用法,很实用看看就知道了。
怎么往SetTimer的回调函数传递参数
线程内使用SetTimer实现定时器 代码中MyThreadTimer时线程定时器的实现
VC++定时器,比SetTimer更精确
vc++2005定时器settimer()的使用 查了一些资料,写了个简单的程序
那么,如何利用这些形式参数,传递程序逻辑处理所需要的参数呢?本文给出一个解决方案,非常好。该解决方案解决了我在实际工程中的切实问题;无需再将需传递的参数声明为丑丑的、破坏封装性的公有变量了。 希望该文...
下面的代码实现每1/10秒在屏幕上输出字符'a',在vc6与vs2008下调试通过。在vc6下直接可以运行,05或08下按照注释的地方改下就可以了
SetTimer(hwnd, 1001, nTime, TimerProc); Key = UP; } break; case VK_DOWN: { if (STAY == Key) { Key = DOWN; } else if (UP == Key || DOWN == Key) { break; } SetTimer(hwnd, 1001, nTime, ...
C# SetTimer() KillTimer() 演示 调用系统user32进行计时并输出,时长为1秒。10秒钟后自动停止。 PS:网上没一个能用的源码,结果研究了半天才搞明白。 看到好多人说不能用,应该不会吧,我亲自写出来试过的。我的...
C# SetTimer() KillTimer() 演示 调用系统user32进行计时并输出,时长为1秒。10秒钟后自动停止。
这一份代码是实现MFC的窗口创建完成需要立刻对某些控件进行响应,以及如何使用SetTimer和KillTimer的例子。 因为在MFC的初始化函数中,窗口创建还没有完成不能对窗口上的控件进行初始化操作,这是需要调用OnCreate...
我们知道,在Windows中,可以通过调用SetTimer函数为应用程序分配一个计时器。当指定了一个时间间隔以后,Windows系统将每隔指定的时间向应用发送一条WM_TIMER消息,从而使应用程序能够实现许多与时间相关的动作。...
用MFC中的SetTimer、OnTimer和KillTimer实现的计时器与倒计时的简单Demo。开发工具为VS2010。 MFC中的OnTimer()函数用于实现定时控制功能,定时控制功能主要由下面三个函数共同实现: SetTimer, KillTimer()和...
使用Visual Studio2008编写,基于MFC 的C++程序SetTimer的源代码,ontimer,killtimer等定时器的使用方法,可直接运行,可自行编写和修改源代码。
windows下的settimer是一个毫秒级的定时器,但是有时候我们需要更加精确的定时器功能,这个例子可以帮我们了解windows下的微妙级的定时器的用法
这项计划要求( WM_TIMER ) ,是专为时间维持。 在这个项目中我用三( 3 )功能( SetTimer , OnTimer , KillTimer ) ,这些功能都支持该计划的选择
windows控制台使用定时器。使用系统的API函数settimer开启定时器。
关于C# Timer类 在C#里关于定时器类就有3个 ...应用于WinForm中的,它是通过Windows消息机制实现的,类似于VB或Delphi中的Timer控件,内部使用API SetTimer实现的。它的主要缺点是计时不精确,而
本人从零开始学C++/MFC的时候,积累和整理的学习笔记,其中有大部分常见问题和处理办法,看过很过帖子,也查过很多资料,最终把他们记录下来。 Demo内容: ★ ::OnTimer OnTimer()函数用于实现定时控制功能,定时...