总结了一些关于多媒体定时器的使用和处理跨线程更新窗口的原理和方法
微软在32位版本的系统里提供了一组所谓的"多媒体定时器"API,多媒体定时器可以使应用程序最大限度的获得硬件平台支持的定时精度。可以实现高精度的定时,例如可以应用于 MIDI序列发生器,MIDI时间产生的精度在一毫秒之内。
一、多媒体定时器的使用方法
设置多媒体定时器timeSetEvent()函数,定时精度为ms级。利用该函数可以实现周期性的函数调用。
1、函数的原型如下:
MMRESULT timeSetEvent( UINT uDelay,
UINT uResolution,
LPTIMECALLBACK lpTimeProc,
WORD dwUser,
UINT fuEvent )
该函数设置一个定时回调事件,此事件可以是一个一次性事件或周期性事件。事件一旦被激活,便调用指定的回调函数, 成功后返回事件的标识符代码,否则返回NULL。
函数的参数说明如下:
uDelay:以毫秒指定事件的周期。意味着理论上可以达到1毫秒的精度.
Uresolution:以毫秒指定延时的精度,数值越小定时器事件分辨率越高。缺省值为1ms。
LpTimeProc:指向一个回调函数。
DwUser:存放用户提供的回调数据。
FuEvent:指定定时器事件类型:
TIME_ONESHOT:uDelay毫秒后只产生一次事件
TIME_PERIODIC :每隔uDelay毫秒周期性地产生事件。
具体应用时,可以通过调用timeSetEvent()函数,将需要周期性执行的任务定义在LpTimeProc回调函数 中(如:定时采样、控制等),从而完成所需处理的事件。需要注意的是,任务处理的时间不能大于周期间隔时间。另外,在定时器使用完毕后, 应及时调用timeKillEvent()将之释放。
2、回调函数:
void CALLBACK TimeProc(UINT uID,UINT uMsg,DWORD dwUser,DWORD dw1,DWORD dw2);
参数uID是该多媒体定时器的标识,dwUser与timeSetEvent中的DwUser一致,传递回调函数中需要使用的参数。
3、需要注意的问题:
(1)、timeSetEvent在控制台程序和窗口程序中都可以运行,timeSetEvent执行后(若成功)会启动额外的线程,猜测这是timeSetEvent可以同时运行在控制台和窗口程序中的原因.。
(2)、由于多媒体定时器是另启动线程处理定时操作,所以在.回调函数中只能访问本线程的MFC对象、不能调用任何系统函数,除了PostMessage, timeGetSystemTime, timeGetTime, timeSetEvent, timeKillEvent, midiOutShortMsg, midiOutLongMsg, OutputDebugString等。
(3)、采用多媒体定时器时,1s测试的误差较大,原因是多媒体定时器需要启动额外的线程,导致一定的时间开销。
二、句柄映射和跨线程访问
句柄映射:为了防止多个线程并发地访问同一个MFC对象,MFC对象和Windows对象之间有一个一一对应的关系,这种关系以映射的形式保存在创建线程的当前模块的模块-线程状态信息中。当一个线程使用某个MFC对象指针P时,ASSERT_VALID(P)将验证当前线程的当前模块是否有Windows句柄和P对应,即是否创建了P所指的Windows对象,验证失败导致ASSERT断言中断程序的执行。如果一个线程要使用其他线程的Windows对象,则必须传递Windows对象句柄,不能传递MFC对象指针。
但是通常我们需要用定时器实现一些定时更新窗口的命令,更改一些窗口的参数或者调用窗口的函数,准确地说这些都不是对窗口的操作,是对于窗口对应并绑定的MFC界面包装对象的操作。但是由于句柄映射的机制,跨线程传递MFC界面包装对象的指针并在自己的线程中使用是不正确的,通过实验发现,如果更改该对象的参数和自定义函数结果是不确定的,很可能产生正确的结果,但是调用该MFC类继承的函数就会出现异常。那么如何达到更新窗口的效果呢,资料显示有两种办法:
1、 通过发消息的方法转到UI线程去处理,用sendMessage给窗口发送自定义消息并设置自己的消息处理函数来实现这些功能,窗口收到消息之后调用与之绑定的MFC界面包装对象的消息处理函数进行处理。这种办法是符合windows机制并且是线程安全的,但是由于要多发送至少一条消息,所以牺牲了效率。
2、 传递窗口句柄给自定义的线程,并在线程中通过FromHandle()函数声称一个临时界面包装对象与窗口句柄绑定,这样也可以操作该窗口,但是它的派生类功能就消失了,也就是说通过FromHandle()生成的窗口只能是CWnd的实例,不具有自己定义的那些属性和操作。而Updatedata()函数由于是MFC自己提供的一个对话框数据交换机制(DDX)的操作,不是通过向窗口句柄发消息来实现的而是通过虚函数机制。因此调用的将是CWnd::DoDataExchange不是自己派生类DoDataExchange,所以窗口不会进行正常更新。