• ATL和MFC的C++类和HWND的映射机制


    最近看深入解析ATL这本书的时候看到ATL中的窗口类实现的时候,很是惊异于ATLThunk的运用技术,ATL运用Thunk技术把C++的成员函数置换成Windows窗口的消息处理函数。那么更古老的MFC框架又是怎么实现CWnd类到HWND窗口类之间的映射的呢? 下面的文章将ATLMFC的窗口封装机制做个对比.

     

    如果让我完成C++类到HWND窗口的映射,我会写如下的代码

    BOOL CMyWnd::Create(...)

    {

          ...

          HWND hWnd = ::CreateWindowEx(...);

          this->m_hWnd = hWnd;

          //置换hWnd窗口过程

          ...

    }

    我相信很多程序员都会像这样封装的,但是这样有一个很大的问题,API函数CreateWindowEx在创建返回之前,Windows已经发送了一些消息给创建好的窗口,其中包括很重要的WM_CREATE消息,而将C++的成员函数置换hWnd的窗口例程是在CreateWindowEx返回之后,因此C++的成员函数就没办法处理WM_CREATE消息,这可就不行了。看看MFCATL是怎么完成的。

     

    1. MFC的实现过程:

          MFCCWnd封装了Windows窗口,其创建函数为CreateEx成员函数,CreateEx函数在调用CreateWindowEx方法之前首先通过AfxHookWindowCreate函数建立一个CBT钩子,该钩子的作用在于钩取创建窗口的事件.赶在创建窗口完成之前建立HWNDCWnd的全局映射,之后置换窗口处理函数为AfxWndProc标准函数,这样做的目的在于CreateEx函数内部调用CreateWindowEx API创建HWND, CreateWindowEx函数返回之后调用AfxUnhookWindowCreate卸载CBT钩子。

    AfxWndProc函数过程:

          从全局映射中取出与句柄HWND相对应的CWnd对象,找到后调用CWnd:: WindowProc的成员函数,剩下的调用就在于CWnd中消息和消息处理函数的映射表了.

     

    2.ATL的实现过程:

          ATL中的宏DECLARE_WND_CLASSCWindowImpl类提供Windows窗口的注册信息,其定义如下:

    #define DECLARE_WND_CLASS(WndClassName) "

    static ATL::CWndClassInfo& GetWndClassInfo() "

    { "

          static ATL::CWndClassInfo wc = "

          { "

               { sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, StartWindowProc, "

                0, 0, NULL, NULL, NULL, (HBRUSH)(COLOR_WINDOW + 1), NULL, WndClassName, NULL }, "

               NULL, NULL, IDC_ARROW, TRUE, 0, _T("") "

          }; "

          return wc; "

    }

    通过宏代码可以看出,其提供给注册的窗口例程为StartWindowProc函数, StartWindowProc函数代码如下:

    static LRESULT CALLBACK StartWindowProc(HWND hWnd, UINT uMsg,
            WPARAM wParam, LPARAM lParam)
        {
            CContainedWindowT
    < TBase >* pThis = (CContainedWindowT< TBase >*)_AtlWinModule.ExtractCreateWndData();
            ATLASSERT(pThis 
    != NULL);
            
    if(!pThis)
            {
                
    return 0;
            }
            pThis
    ->m_hWnd = hWnd;

            
    // Initialize the thunk.  This was allocated in CContainedWindowT::Create,
            
    // so failure is unexpected here.

            pThis
    ->m_thunk.Init(WindowProc, pThis);
            WNDPROC pProc 
    = pThis->m_thunk.GetWNDPROC();
            WNDPROC pOldProc 
    = (WNDPROC)::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)pProc);
    #ifdef _DEBUG
            
    // check if somebody has subclassed us already since we discard it
            if(pOldProc != StartWindowProc)
                ATLTRACE(atlTraceWindowing, 
    0, _T("Subclassing through a hook discarded.\n"));
    #else
            pOldProc;    
    // avoid unused warning
    #endif
            
    return pProc(hWnd, uMsg, wParam, lParam);
        }

    Windows发送第一个消息给hWnd窗口时,ATL完成CWindowImplhWnd的映射。据《深入解析ATL》介绍:第一个窗口消息发生在Windows 第一次把新的HWND传递给应用程序时。它发生在CreateWindow(Ex)返回HWND之前。完成窗口类映射之后,接着应用Thunk技术把pThis的成员函数替换成hWnd的窗口例程,

    最后调用pProc处理这次接收到的消息。真是严谨啊,一条消息都不会漏掉.

  • 相关阅读:
    mysql的binlog日志格式
    Git的基本使用
    Tomcat安装部署
    [ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock' (111)]
    云盘的创建及使用
    ntp服务器配置
    如何更改mysql的密码策略?
    Centos6 升级glibc-2.17,解决Requires: libc.so.6(GLIBC_2.14)(64bit)错误解决方法
    Git的基本使用
    UES
  • 原文地址:https://www.cnblogs.com/fangkm/p/1426523.html
Copyright © 2020-2023  润新知