将 Timer 对象化
Timer这玩意儿很常用,却又很烦人。烦人之处有四:
1. 如果将其设到HWND上,则
a) 必须手工维护Timer的ID,小心翼翼地保证这些ID不重复,可能有人(比如我)就不怎么喜欢手工维护硬编码的ID。
b) 必须跟一个HWND关联,在没有HWND的时候,或者HWND不方便用的时候,就麻烦了。比如前公司有个GUI系统,是个类似HWND控件和DirectUI控件混合支持的系统(看上去很强大是不?),在HWND控件上使用SetTimer很方便,直接用原生的就行了,但如果在DirectUI控件上想要搞个Timer,就傻了。
2. 如果不将其设到HWND上,则
a) ID倒是可以让它生成,虽然我很喜欢,但不一定所有人喜欢,这与1.a两者必居其一,无法两全。
b) 回调函数又涉及成员化的问题,不然在一个对象化的系统里就很难写。
就我个人而言,我喜欢2.a特性,因此着眼于解决2.b问题。
回调函数成员化,看着好像很眼熟。不错,我们曾经在《学习下WTL的thunk》里面干过这事情。因此几个月前我就觉得对于Timer也是可以做到的,但由于各种原因没时间去弄,同时也很遗憾前公司“架构师”没有采用这种方案。
今天刚刚打眼到公司有人也做了这件事(1.a + 2.b 模式),趁目前还没去很仔细地去研究,赶紧自己先写一个差异化版本,以避免不必要的版权纠纷^_^
Thunk需要占用一个正常参数。我们观察一下Timer的回调函数格式:
VOID CALLBACK TimerProc( _In_ HWND hWnd, _In_ UINT uMsg, _In_ UINT_PTR idEvent, _In_ DWORD dwTime ); |
很不错,前面三个参数几乎都是没用的(至少第一个是没用的,这就够了)。
先把原先为了WNDPROC的Thunk改得通用些,WNDPROC改成LPVOID或者模版化,所有出现“Wnd”的地方都去掉“Wnd”字样,改完后变成:
http://xllib.codeplex.com/SourceControl/latest#SourceCode/xl/Win32/GUI/xlThunk.h
然后写Timer的实现。代码比较短,我先全贴了:
typedef Function<void (DWORD dwTime)> TimerCallback; class Timer { public: Timer() : m_uTimerId(0) { } ~Timer() { Kill(); } public: bool Set(UINT uElapse, TimerCallback fnCallback) { if (m_uTimerId != 0) { return false; } m_fnCallback = fnCallback; m_thunk.SetObject(this); m_thunk.SetRealProc(StaticTimerProc); m_uTimerId = SetTimer(nullptr, 0, uElapse, m_thunk.GetThunkProc()); if (m_uTimerId == 0) { return false; } return true; } void Kill() { if (m_uTimerId != 0) { KillTimer(nullptr, m_uTimerId); m_uTimerId = 0; } } protected: static VOID CALLBACK StaticTimerProc(HWND hWnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) { return ((Timer *)hWnd)->m_fnCallback(dwTime); } protected: UINT_PTR m_uTimerId; Thunk<TIMERPROC> m_thunk; TimerCallback m_fnCallback; }; |
注意Timer::Set里面,设好Thunk的数据以后,直接把Timer创建在Thunk上就可以了,比起窗口那个处理干净利落多了。咦?窗口里为什么要搞个StartProc,然后再在StartProc里把回调函数设到Thunk上呢?
是这样的,注册窗口类的时候就需要一个回调函数,此时窗口未创建。CreateWindow的过程中,会调用到回调函数(WM_CREATE),如果没有特殊处理,需要调用回DefWindowProc,其第一个参数是HWND,而我们此时如果使用Thunk的话,就会篡改掉系统调用回调函数时给出的HWND,从而没法正确调用DefWindowProc。也就是说,如果第一次被调用需要使用第一个参数的,就需要像窗口的处理一样,搞个StartProc第一次用。
这里我们使用SetTimer(NULL, ...),这第一个参数任何时候都不需要使用,所以可直接将Timer创建在Thunk上。
用例:
int main() { xl::Timer t; t.Set(1000, [](DWORD dwTime) { printf("%u\n", dwTime); }); MSG msg = {}; while (GetMessage(&msg, nullptr, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return 0; } |
运行结果:
源代码见:
http://xllib.codeplex.com/SourceControl/latest#SourceCode/xl/Win32/Timer/xlTimer.h
流浪了近一个月,我又开始上班啦!
最新文章
- 零基础如何系统学习Java Web
- jq表头固定
- .NET Core、DNX、DNU、DNVM、MVC6学习资料
- 获取body标签元素方法
- nRF51822之WDT浅析
- perl 学习杂项笔记
- 关于bootstrap列偏移的两种方式
- JavaScript 父子页面相互调用总结
- 201521123022 《Java程序设计》 第十四周学习总结
- Quartz源码——QuartzSchedulerThread.run() 源码分析(三)
- springboot-mybatis 批量insert
- JS高级用法
- [Matlab+C/C++] 读写二进制文件
- 机器学习算法与Python实践之(五)k均值聚类(k-means)
- JavaWeb 乱码问题终极解决方案!
- gradle编译命令 &; 自动打包等
- 002. Ansible部署及配置介绍
- oracle 之 插入超长字段并包含&;字符的处理方法
- OpenCV——输入输出XML和YAML文件
- PyCharm 4.0.4 开启代码自动补全