zoukankan      html  css  js  c++  java
  • Windows子类化和与c++类挂钩

    介绍 我第一次看到CWnd类文档我觉得很棒。一个很好的包装W32 API为c++上下文。但随着时间的推移,某些选择MFC开发人员过去就没有更适合语言的增强。特别是,没有可能性子类Windows“窗口”不止一次CWnd对象。问题已经解决了以各种方式(我记得保罗迪Lascia,从微软)。我提出这个解决方案,是基于STL集合和一个类用来捕获窗口程序,做自己的调度。 你所要做的是来自CWndSubclasser类并覆盖SubWndProc函数。你可以实例化在堆上尽可能多的实例,每个实例关联到一个窗口。不同的子类可以联系到一个相同的窗口。窗户是由HWND,因此,他们不需要CWnd窗口。 此外,用同样的方法用于窗口子类化我也创建类来捕获例程(他们可能有用如果你有独立获取特定的消息的窗口指导)和WH_FOREGROUNDIDLE(有用的管理空闲时间处理例如在图书馆,你不一定有访问CWinApp对象)挂钩。 这些类是独立的。你不需要钩钩子类或子类。 派生类和子类化 这两个概念不能混淆。 类派生发生在OOP语言和-本质上在于定义类的其他基类的基础上,在某些功能(可能是虚拟的)所取代。这发生在一个类的定义。 窗口子类化发生在一个窗口过程取代了另一种可能,或有时电话原来的一个。这种情况以外,独立的“类”的定义(或…定义了原始窗口过程)。在这个意义上,派生类是“静态”(由编译器),而窗口子类化是“动态”(在运行时完成)。 在MFC所有窗户都基于相同的窗口过程(AfxWndProc),一旦检测到窗口(HWND)消息称,分派消息到一个虚拟函数CWnd对象(OnWndMsg)解析相关的消息映射和调用所需的处理程序(如果有的话)或调用默认情况下,轮流——通过原始消息DefWndProc或一个原始窗口过程最终现有如果不是由MFC的窗口。 当这样做,MFC本身就是做一个“子类”,但它存储原来的程序在一个成员变量。因此只有一个(或没有,如果你得到了CWnd皱眉现有non-MFC窗口)子类化是可能的。 “子类”的概念本质上是相同的,Win32:你用另一个代替一个窗口过程,而在新程序处理消息——决定何时以及如何调用前一个(默认行为)。 子类化的需要,当你必须做一个特定的任务在一个特定的消息,各种不同的窗口。 每个不同的窗口可能-本身CWnd派生的对象,但是如果你有陷阱一些信息(例如定制菜单行为或出现在同样的方式为所有你的windows)你必须重新实现相同的处理程序对所有CWnd类。这就是子类化可能有用:你创建另一个对象,拦截窗口程序,并将为每个窗口的一个实例。 该对象定义如何处理消息并调用原来的窗口过程。 在这个实现中,但是,我不想为每个子类使用一个窗口过程,但依赖于一个特定对象的虚函数。所以我提供了一个全球内部窗口过程取代了旧的(如果是AfxWndProc,这意味着我们是子类化一个MFC窗口…没有MFC知道)和分派消息迭代和递归(稍后我将更清晰)通过一个HWND相关“子类”的列表。 事实上,“子类”存储在相反的顺序在一个std:: lst和列表存储在std:: HWND映射关联。第一次CWndSubclasser对象关联到一个窗口,窗口子类化(W32意义上)。所有后续CWndSubclassers最终相关窗口,不要再子类,而只是链到相关列表窗口。 当消息的窗口过程,它标识列表并调用一个虚函数(WndProcSub)中的第一个对象列表(HWND最后相关)。 它取决于你叫——处理——默认()成员函数,递归地调用虚函数的下一个对象(或前面的窗口过程,如果列表结束)。因此,无论你把默认的()调用(一开始或和你的覆盖)你——实际上——影响了处理的顺序。就像在Win32中调用DefWndProc。 连接 更简单地说,为了“hook”,我只提供一个静态函数来分派到对象的内部列表。这些对象根据需要从CHookIdle或CWndProcHook派生。 历史和依赖 在部署子类的过程中,我发现子类销毁时出现了一个问题。 特别地,DefWndProc(主要被每个Windows窗口使用)经常处理生成其他消息的消息。这将导致对窗口过程(不管它是什么)进行递归调用。这意味着我们不能销毁一个子类——例如——在WM_NCDESTROY消息上,因为它的虚函数可能仍然被以前的WM_SYSCOMMAND(但还没有返回)调用(在标题栏上点击“关闭”按钮)。 因此,必须采用以下规则: 总是在堆上构造子类对象,不要将它们的删除与CWnd销毁关联起来。 只有在确认不再进行递归后才删除对象。 一个非常简单的方法就是使用智能指针(啊哦…这里包含的标题比那篇文章中发布的更近……实际上,在提供的窗口过程中(在wndsubclassert .cpp中),每当需要调用一个子类时,就会在堆栈上定义一个智能指针,并将其初始化为子类实例。 如果你在堆上创建了实例之后,用一个GE_::Safe::PtrStrong引用它,你可以确保子类永远不会被销毁,直到“某些东西”(你或窗口过程)仍然需要它。 当您不再需要您的子类时,只需将您的引用智能指针设置为NULL(或销毁智能指针)。只有在所有递归都返回之后,对象才会被删除(因为每个递归都会在返回时破坏它自己创建的智能指针)。 现在,由于这些对象被设计成由智能指针处理,我用最安全的方式来做,通过从Safe::ObjStrong派生子类基。所提供的示例完全执行所描述的操作。隐藏,收缩,复制Code

    // ../utility/WndSubclasser.h"
        ...            
        class CWndSubclasser;
        typedef Safe::PtrStrong<CWndSubclasser> PWndSubclasser;
        ...
                
    // Appwnd.h
    ...
    class CAppWnd :
        public CFrameWnd
    {
    protected:
        PHookIdle1 _pHookIdle1;
        GE_::Utils::PWndSubclasser _pS1, _pS2;
    public:
        CAppWnd(void);
        virtual ~CAppWnd(void);
        DECLARE_MESSAGE_MAP()
        afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
        afx_msg void OnPaint();
    };
    ...
    
    
    //AppWnd.cpp
    ...
    int CAppWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)
    {
        if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
            return -1;
        
        CApp::OutConsole("Creating CApWnd: not yet subclassed
    ");
    
        _pS1 = new CSub1;
        _pS1->Subclass(*this);
        _pS2 = new CSub2;
        _pS2->Subclass(*this);
        _pHookIdle1.New();
    
        return 0;
    }
    ...

    使用的代码 我没有专门针对这些对象的智能指针,因此,它们默认假设“dynamic_cast”是可能的。因此,必须在项目上启用RTTI。 如果你不想(或者你不能),定义智能指针到你的派生类,指定第二个模板参数为GE_::Safe::FStaticCast (defaut是FDynamicCast),并删除所有对调试类GE_::Mfc::STrace的引用。(只是为了调试)。但是,我建议启用RTTI。 示例代码 所提供的示例展示了如何使用从CWndSubclasser派生的两个对象对CFrameWnd(它只绘制静态文本片段)进行两次子类化。同时,一个空闲处理器和一个钩子被实例化。您可以轻松地多次重复做相同的事情。在子类中,被捕获的消息在相关的控制台中显示文本行。 此外,“About”对话框与用于子类化大型机的同一对象的另一个实例被子类化。 请注意它的着色方式和光标的行为,并注意这样做的代码如何只在一个特定对象上编写一次。(我没有重新给静态文本控件上色,也没有在按钮上处理光标形状,只是为了让差异更明显) 描述 要理解的关键是CWndSubclasser的子类函数和Destroy函数。隐藏,收缩,复制Code

    bool CWndSubclasser::Subclass(HWND hWnd)
    {
        if(!hWnd || _hWnd) return false;
        _hWnd = hWnd;
        _bCleared = false;
        TLstpWndSubClasser& lst = g_map[hWnd];
        lst.push_front(this);
    
        WNDPROC& oldPrc = g_prcmap[hWnd];
        if(!oldPrc) //not yet subclassed
        {
            oldPrc = (WNDPROC) GetWindowLongPtr(hWnd, GWL_WNDPROC);
            SetWindowLongPtr(hWnd, GWL_WNDPROC, (LONG_PTR)SubWindowProc);
        }
        
        return true;
    }
    
    bool CWndSubclasser::Clear()
    {
        if(!_hWnd) return false;
        _bCleared = true;
    
        TLstpWndSubClasser& lst = g_map[_hWnd];
        lst.remove(this);
        
        if(lst.size() == 0)
        {
            g_map.erase(_hWnd);
            SetWindowLongPtr(_hWnd, GWL_WNDPROC, (LONG_PTR)g_prcmap[_hWnd]);
            g_prcmap.erase(_hWnd);
        }
    
        return true;
    }

    在析构函数中也调用了Clear函数(但是您可以无限次地调用它),并且子类函数必须由您在一个存在的窗口中调用。 g_xxx变量是在未命名名称空间中定义为全局的std::映射。 在这个实现中,子类意味着将子类对象推入与HWND相关的专用列表中,并将HWND窗口过程设置为wndsubclassert .cpp中提供的窗口过程。 窗口过程的定义如下:收缩,复制Code

    struct
    SWndSubclasserParams {
        TLstpWndSubClasser::iterator
        i; TLstpWndSubClasser::iterator
        end; HWND
        hWnd; UINT uMsg; WPARAM wParam; LPARAM lParam; LPVOID
        prevtoken; };
    LRESULT
    
    CALLBACK SubWindowProc( HWND
    hwnd, //      handle to window UINT
    uMsg, //      message identifier WPARAM
    wParam, //  first message parameter LPARAM
    lParam //   second message parameter )
    {
        TMapHwndLstSubClasser::iterator
        mapI = g_map.find(hwnd); ASSERT(mapI
        != g_map.end());
        static CCriticalSection ccs;
        CSingleLock    lock(&ccs, true);
        SWndSubclasserParams prms;
        TLstpWndSubClasser& lst = mapI->second;
        prms.i = lst.begin();
        prms.end = lst.end();
        prms.hWnd = hwnd; prms.uMsg = uMsg;
        prms.wParam = wParam; prms.lParam = lParam;
        prms.prevtoken = NULL;
    
        return CWndSubclasser::Call(&prms);
    }
    

    注意:所有这些代码都在一个未命名的名称空间中。你永远不会直接使用它。 基本上,SWndSubclasserParams内部结构是在堆栈上分配并填充的。 然后,它作为地址在静态调用函数的令牌参数中传递,该函数执行实际的工作。隐藏,收缩,复制Code

    LRESULT CWndSubclasser::Call(LPVOID token)
    {
        SWndSubclasserParams* pPrms = (SWndSubclasserParams*)token;
        
        if(pPrms->i == pPrms->end) 
            //finished the list: just call the original window procedure. 
            //This will go into AfxWndProc and hence in 
            //CWnd::OnWndMsg and into the message map.
            return CallWindowProc(
               g_prcmap[pPrms->hWnd], 
               pPrms->hWnd, 
               pPrms->uMsg, 
               pPrms->wParam, 
               pPrms->lParam
            );
        
        CWndSubclasser* pWS = *pPrms->i;
        PWndSubclasser pKeep(pWS);
        pPrms->i++; // the nextime Call is called, 
                       // will process the next in list
        pPrms->prevtoken = pWS->_token; //save for later
        pWS->_token = token; //remember for Defauklt calling
        LRESULT r=0;
        if(pPrms->uMsg == WM_NCDESTROY)
            pWS->_bCleared = true; //will not destroy yet
        
        if(pWS->_bCleared) r = pWS->Default(); //skip a declared 
                                                     // as "destroyed"
        else r = pWS->WndProcSub(
            pPrms->uMsg, 
            pPrms->wParam, 
            pPrms->lParam); //just call the subproc
        
        pWS->_token = pPrms->prevtoken; //restore previous token 
                                     //(now can return also from recursion)
        return r;
    }

    CWndSubclasser中的迭代器旨在指向“下一个进程”的子类。 如果我们在列表的末尾,我们只需调用ex窗口过程。 否则 我们取回有指向的物体 我们增加引用计数(PWndSubclasser pKeep(pWs)将一直存在直到返回) 我们增加迭代器(用于将来的递归) 在保存旧值之后,我们将“token”放入我们刚刚调用的对象中。 我们调用(除某些特殊情况外)WndProcSub虚函数。 这是前任根据您的需要,WndProcSub调用默认值,然后再次调用Call(但迭代器已被递增,因此将引用下一个子类)。 这个技巧允许你定义你想要的许多子类:所有你需要做的是覆盖WndProcSub和,根据消息: 调用Default(),然后执行一些处理(子类充当上一个子类和默认窗口过程所采取的“默认”操作的附加组件) 执行您的进程,不调用默认值(您可以用自己的功能替换该功能) 在进程之后调用Default()。 其他对象 使用相同的技术,我还定义了CHookIdle和CWndProcHook。 不同的是,它们是一般的窗口挂钩,不关联到特定的窗口。 CHookIdle链中的一个列表和一个安装在WH_FOREGROUNDIDLE上的静态钩子子程,调用OnIdle虚函数。(在您的覆盖中调用Default()由您决定。注意:基础,只调用默认值) CWndProcHook链在另一个列表中,一个静态钩子子程安装在WH_CALLWNDPROC上调用OnHook。 CWndSubclasser和CWndProcHook 这两个物体的功能有一定的重叠,但它们不是相同的。 我认为注意它们之间的区别是很重要的: CWndSubclasser在实例化时不做任何事情,直到它被关联到一个特定的HWND。从那时起,它只响应与它相关的HWND。CWndProcHook在Windows将要调用一个窗口过程时响应。它不涉及特定的窗口,但可以“监视”一切。 CWndSubclasser依赖于一个“替代”窗口过程。原始的调用是Default()。由你来决定“是否”、“何时”(或“何地”)调用它。CWndProcHook依赖于一个Windows钩子。它可以采取行动,但它不是一个真正的窗口过程覆盖。窗口过程总是被调用。Default()仅用于定义“如果”和“何时”来处理可能已经创建的CWndProcHook的不同实例,并因此进行链接。 其他东西 我在包中还包含了smartptc .h(它们在类中使用)和另一个非常简单的类:GE_::Mfc::STrace。它可以用于支持RTTI的项目,以对函数进行跟踪,并附加递归级别。 它的用法很简单:在堆栈上声明一个变量,然后调用Trace函数。典型案例为:GE_::Mfc::STrace trc;trc.Trace(类型id(*),“& lt; & lt; yourtext>祝辞”);在调试器的输出窗口中,你会看到: 正在进行的跟踪递归的数量 对象的运行时类型名称 对象的地址 你提供的文本 它用于我在本文中描述的对象的调试版本中。 本文转载于:http://www.diyabc.com/frontweb/news7200.html

  • 相关阅读:
    编程实现SQL Server备份和还原
    [推荐]asp.net string与color互换
    ASP.NET执行SQL超时的解决方案
    Starling 环形进度条实现
    [转]列举好游戏应具备的10个要素
    基于Starling的mask实现
    一直以来最头痛的问题:怎么样才能行之有效的解决游戏中后期枯燥的问题呢?求解
    [转]战棋系统的分析
    [转] java开源游戏
    设计模式(基础篇)软件设计七大设计原则(续一)
  • 原文地址:https://www.cnblogs.com/Dincat/p/13467477.html
Copyright © 2011-2022 走看看