65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.64/5 (17投票s)

2003 年 3 月 22 日

5分钟阅读

viewsIcon

121599

downloadIcon

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 映射到其字符串等效项的痛苦,并提取 WPARAMLPARAM,将它们代表的任何内容转换为有意义的文本。

使用代码

  • 将以下源文件添加到您的项目中

    注意:在我的演示项目中,这些文件位于一个单独的“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 个文件。
© . All rights reserved.