一个方便实现Windows钩子的模板单例类






4.64/5 (17投票s)
2003 年 3 月 22 日
5分钟阅读

121599

1653
通过使用模板管理器类简化Windows钩子的实现。
引言
我最近提交了一篇 菜单皮肤文章,该文章使用了全局 Windows 钩子来子类化应用程序中的所有菜单,以便应用程序可以覆盖标准的 Windows 菜单渲染。
实现 Windows 钩子的代码是相当标准的样板代码,我之前也在其他地方使用过,并将其复制粘贴到了菜单项目中。
但是,我一直未能找到一种解决方案,让我不再需要每次想要 Windows 钩子时都将相同的代码复制粘贴到新项目中,这让我感到很不爽。
几天前,我读了 Shog9 关于气球帮助的文章,这是一篇关于替换 MessageBox()
的精彩文章。
最让我眼前一亮的是,它使用了 thunking 技术,将类成员函数作为回调传递给通常只接受全局回调函数的 Windows 函数。(如果您想了解更多关于 thunking 的信息,我建议您阅读该文章。)
注意:这之所以如此有趣,是因为许多 Windows 全局回调函数很麻烦,因为它们通常没有提供传递用户定义数据(如类对象指针)的机制,从而无法在不同上下文中被不同类使用。
所以,这是一种解决全局回调问题的方法,尽管(在兴奋过去之后)这是一种相当棘手的方法。
你看,在这种情况下,thunking 实际上是一段自修改代码,对于我这个一直认为自修改代码是大忌的人来说,我觉得最好还是避免使用它,因为如果出了问题,我不知道该如何健壮地修复。
但是,我必须承认,它为我指明了解决全局回调问题的方向:模板。
我必须承认,我在掌握模板方面一直很迟钝。主要问题是,由于对模板的实际经验不足,我倾向于无法发现何时可以将它们用作特定设计问题的解决方案,并且由于没有实现许多模板解决方案的经验,我无法获得更多关于何时最佳使用它们的见解,以此类推……
但是一旦这个想法萌生,一切似乎都变得如此明显。
通过模板化(这是个词吗?)一个基类,任何派生自该基类的类都可以拥有自己的一组静态回调函数,这些函数将与其他派生类的函数完全不同。
诚然,同一类的不同实例仍然需要共享一个函数“实例”,但就我而言,我编写的 Windows 钩子类通常都是单例类,用于提供应用程序范围内的钩子。
注意:如果基类没有被模板化(又出现了那个词),那么任何静态类函数都将相同,无论你在类层次结构的哪个位置,都不会给你带来任何额外的好处。
实现
设计思路确定后,这项任务就变得相当简单了。
基本上,我实现了一个相当标准的单例类,然后向其中添加了模板。(注意:据我所知,如果单例的 GetInstance()
方法不是 public 的,就不需要提供默认的拷贝构造函数,但我欢迎纠正。)
我还完成了为大多数 Windows 钩子添加静态函数的工作,以及供派生类覆盖所需钩子的虚函数。
注意:如果您请求了特定类型的钩子,但没有为它提供处理程序,基类将会断言。这与 MFC 中 ownerdraw 的实现类似,如果您忘记覆盖 CWnd::OnMeasureItem()
和 CWnd::OnDrawItem()
,它们也会断言。
CHookMgr
还利用了一个部分实现的的消息跟踪系统,我打算将其完成并提交给 CodeProject。它能做的是简单地消除将 Windows 消息 ID 映射到其字符串等效项的痛苦,并提取 WPARAM
和 LPARAM
,将它们代表的任何内容转换为有意义的文本。
使用代码
- 将以下源文件添加到您的项目中
注意:在我的演示项目中,这些文件位于一个单独的“skinwindows”文件夹中,因为它们是一个更大皮肤系统的一部分,但您没有必要这样做。
CHookMgr
(hookmgr.h/.cpp) - 模板化的基管理器CWinClasses
(winclasses.h/.cpp) - 用于检索和测试窗口类的辅助类ISMsgManager/ISMsgHandler
(ismsgmanager.h/.cpp, ismsghandlers.h) - 用于将消息转换为有用的字符串输出的辅助类。注意:这些文件可以被放入任何需要消息跟踪输出的项目中。- wclassdefines.h - 所有窗口类(及其他一些)的方便的
#defines
- 派生您自己的钩子管理器类,并根据需要覆盖钩子函数,如下所示
class CMyHookMgr : protected CHookMgr<CMyHookMgr> { // so that the template class can see the protected c'tor friend class CHookMgr<CMyHookMgr>; public: virtual ~CMyHookMgr(); static Initialize(UINT uMyFlags, ....) { // call base class Initialize to set up hooks required GetInstance().InitHooks(HM_CALLWNDPROC | HM_CBT | ...); ... // any other initialization } protected: CMyHookMgr(); protected: // virtual overrides virtual void OnCallWndProc(const MSG& msg); { ... } virtual BOOL OnCbt(int nCode, WPARAM wParam, LPARAM lParam) { ... } };
- 最后,在应用程序启动代码的适当位置调用
CMyHookMgr::Initialize(...);
- 就是这样。
Copyright
代码在此提供给您无限制地使用和滥用,但您不能修改它并声称是自己的。
历史
- 1.0 - 初始发布
注释
- 示例应用程序代码是我 菜单皮肤文章 中的代码,已更新为使用
CHookMgr
。 - 源代码仅包含上面列出的 7 个文件。