• ATL7窗口类详细剖析


    前言:

     ATL是微软继MFC之后提供的一套C++模板类库,小巧、精妙、效率极高。它的主要作用是为我们编写COM/DOM/COM+程序提供了丰富的支持。但是ATL只能写COM么?我以前只是MFC程序员的时候,一直有此误解。但其实ATL提供了很多类用来帮助编写WIN32窗口程序,可能没有MFC使用的广泛和方便(当然啦,因为ATL本来难度就较一般的C++类库大)。用ATL编写WIN32窗口程序有什么好处?小巧、效率这些好处之外,还有一个我认为非常大的好处,写一个EXE形式的COM服务程序,该程序拥有自己的窗口可以和用户交互。你想象一下,一个友好的窗口程序,同时暴露了一些COM接口使得可以和其他程序跨进程通信,是不是非常的便利呢?

    使用ATL编写WIN32窗口应用程序你具备以下基础知识,包括WIN32SDK编程能力、C++模板技术、COM编程的能力。要求很高啊,正因为这样,才萌发了写这篇文章的念头。

     HWND和CWindow类

    HWND是WINDOWS窗口的灵魂,每个窗口都对应一个HWND变量,称为窗口句柄。

    我们可以通过HWND向窗口发送消息,让窗口做一些我们想要的动作或者获取窗口的某些信息(比如设置/窗口标题)。

    CWindow类保存了窗口句柄,并且包装了一些常用的基于窗口句柄的对窗口的操作。CWindow类定义在atlwin.h文件中。CWindow类提供了很多成员变量和函数,有几个比较重要的:

    1
    2
    3
    HWND m_hWnd;//保存了窗口句柄
    static RECT rcDefault;//静态变量,保存了默认的窗口的初始位置和大小
    _declspec(selectany) RECT CWindow::rcDefault = { CW_USEDEFAULT, CW_USEDEFAULT, 0, 0 };

    Create成员函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
           HWND Create(LPCTSTR lpstrWndClass, HWND hWndParent, _U_RECT rect = NULL, LPCTSTR szWindowName = NULL,DWORD dwStyle = 0, DWORD dwExStyle = 0,
    _U_MENUorID MenuOrID = 0U, LPVOID lpCreateParam = NULL) throw()
        {
            ATLASSERT(m_hWnd == NULL);
            if(rect.m_lpRect == NULL)
                rect.m_lpRect = &rcDefault;
            m_hWnd = ::CreateWindowEx(dwExStyle, lpstrWndClass, szWindowName,
                dwStyle, rect.m_lpRect->left, rect.m_lpRect->top, rect.m_lpRect->right - rect.m_lpRect->left,
                rect.m_lpRect->bottom - rect.m_lpRect->top, hWndParent, MenuOrID.m_hMenu,
                _AtlBaseModule.GetModuleInstance(), lpCreateParam);
            return m_hWnd;
        }

    Creat函数第一步,检测窗口是否已经拥有句柄,然后判断;第二步,检测rect参数的变量m_plpRect是否为NULL,rect类型为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class _U_RECT
    {
    public:
         _U_RECT(LPRECT lpRect) : m_lpRect(lpRect)
         { }
         _U_RECT(RECT& rc) : m_lpRect(&rc)
         { }
         LPRECT m_lpRect;
    };

    我们可以直接传递一个RECT变量的指针,RECT变量的指针会被用作构造函数的参数创建一个临时的_U_RECT变量,作为参数传递给Create函数。

    第三步调用CreateWindowEx函数。这是一个WIN32函数。可以指定扩展窗口风格、已注册窗口类名称、窗口标题、窗口风格、窗口位置矩形、父窗口句柄、菜单资源ID、进程实例和创建窗口时可以指定的创建参数。

     注意,这里的进程实例句柄来自于_AtlBaseModule.GetModuleInstance(),_AtlBaseModule变量声明于atlcore.h文件中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    extern CAtlBaseModule _AtlBaseModule;
    CAtlBaseModule的声明也在atlcore.h文件中
    class CAtlBaseModule : public _ATL_BASE_MODULE
    {
    public :
         static bool m_bInitFailed;
         CAtlBaseModule() throw();
         ~CAtlBaseModule() throw ();
      
         HINSTANCE GetModuleInstance() throw()
         {
             return m_hInst;
         }
         HINSTANCE GetResourceInstance() throw()
         {
             return m_hInstResource;
         }
         HINSTANCE SetResourceInstance(HINSTANCE hInst) throw()
         {
             return static_cast< HINSTANCE >(InterlockedExchangePointer((void**)&m_hInstResource, hInst));
         }
      
         bool AddResourceInstance(HINSTANCE hInst) throw();
         bool RemoveResourceInstance(HINSTANCE hInst) throw();
         HINSTANCE GetHInstanceAt(int i) throw();
    };
      
    __declspec(selectany) bool CAtlBaseModule::m_bInitFailed = false;
    extern CAtlBaseModule _AtlBaseModule;

    CAtlBaseModule类用来取代旧版的ATL中的CComModule类。主要作用是保存进程实例句柄和资源句柄,并且是线程安全的。

    使用CWindow类

     说了这么多,我们先来写一个例子程序。

    创建Win32项目CWindow。在stdafx.h中加入代码:#include <atlbase.h>。这样,ATL会在程序一启动就自动实例化_AtlBaseModule对象。也就是说我们不需要自己创建CAtlBaseModule对象。

    创建窗口程序首先要注册窗口类,创建窗口,建立消息泵,在窗口过程函数中对消息进行处理。CWindow类可以帮助我们创建窗口。所以Win32代码作如下修改:

    在stdafx.h中加入代码:#include <atlwin.h>

    _WinMain(...)中调用InitInstance函数的地方改为:

    1
    2
    3
    4
    5
    6
    7
    8
    //创建窗口
    CWindow wnd;
    wnd.Create(szWindowClass,0,CWindow::rcDefault,L"Window Application",WS_OVERLAPPEDWINDOW,WS_EX_CLIENTEDGE);
    if(!wnd)
       return -1;
    wnd.CenterWindow();
    wnd.ShowWindow(nCmdShow);
    wnd.UpdateWindow();

    好了,现在全局变量HINSTANCE hInst变量可以删除掉,所有需要使用hInst的地方都可以用_AtlBaseModule.GetModuleInstance()替换。

    一切大功告成!

    CWindowImpl类

    在第一章中,讨论了CWindow类的使用,但是注册窗口类,窗口过程函数仍然是使用的Win32 SDK方式。我们可以通过编写自己的派生自CWindowImpl类的子类达到简化这些工作的目的。

    ProcessWindowMessage与消息映射宏

    CWindowImpl类是一个最终派生自CWindow类的模板类。它可以在第一次调用Create函数时自动注册窗口类,并且通过thunk机制将窗口类中的窗口过程函数映射到自己派生类的成员函数,同时提供了很多宏用于建立消息映射语句。

    CWindowImple类与父类的关系图:

    CWindowImple类与父类的关系图

    CWindowImplRoot类默认的模板参数TBase为CWindow,所以绝大多数情况下,CWindowImpl类派生自CWindow类。而另一个父类CMessageMap类非常简单:

    1
    2
    3
    4
    5
    6
    class ATL_NO_VTABLE CMessageMap
    {
    public:
         virtual BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
             LRESULT& lResult, DWORD dwMsgMapID) = 0;
    };

    只是声明了一个纯虚函数,我们的派生类必须实现ProcessWindowMessage函数,否则我们的派生类将不能实例化。我们要实现的ProcessWindowMessage函数是一个非常类似于WindowProc函数的成员函数,里面有大量的switch/case语句,可以根据不同的消息调用其他成员函数进行处理,为了简化这些工作,ATL提供了BEGIN_MSG_MAP/END_MSG_MAP以及MESSAGE_HANDLER宏帮助我们实现这个函数。如下:

    1
    2
    3
    BEGIN_MSG_MAP(CMainWindow)
    COMMAND_ID_HANDLER(IDM_EXIT, OnFileExit)
    END_MSG_MAP()

    MESSAGE_HANDLER(msg,func)宏将消息交给指定的函数处理。

    MESSAGE_RANGE_HANDLER(msgFirst,msgLast,func)宏处理一定范围内的窗口消息。

    这里的func函数具有下面的形式:

    1
    LRESULT MessageHandler(UINT nMsg,WPARAM wparam,LPARAM lparam,BOOL& bHandled)

    如果消息没有被func函数处理,则会交给缺省窗口过程处理,如果被func处理,同时又想让消息继续流动下去而不是截断,则可以将bHandled设为FALSE。

    为了方便处理,ATL对WM_COMMAND和WM_NOTIFY消息提供了更方便的宏,WM_COMMAND用于菜单被按下、加速键被按下或者WIN32控件发送通知给父窗口;WM_NOTIFY用于WIN32控件通知父窗口。WM_NOTIFY是用于后来增加的新控件的,因为那时WM_COMMAND消息的WPARAM和LPARAM的所有位都已经用完了。

    1
    2
    COMMAND_HANDLER(id,code,func)
    NOTIFY_HANDLER(id,code,func)

    处理函数原型:

    1
    2
    LRESULT CommandHandler(WORD wNotifyCode,WORD wID,HWND hWndCtl,BOOL& bHandled)
    LRESULT NotifyHandler(int idCtrl,LPNMHDR pnmh,BOOL& bHandled)

    有时候消息处理函数不关心code参数,下面的宏更加方便,比如用于菜单:

    1
    2
    COMMAND_ID_HANDLER(id,func)
    NOTIFY_ID_HANDLER(id,func)

    还有其他一些宏:

    1
    2
    3
    4
    5
    COMMAND_RANGE_HANDLER(idFirst,idLast,func)
    NOTIFY_RANGE_HANDLER(idFirst,idLast,func)
      
    COMMAND_CODE_HANDLER(code,func)
    NOTIFY_CODE_HANDLER(code,func)

     窗口创建与消息路由

    CWindowImpl类的Create函数内部注册窗口类,然后创建窗口:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    HWND Create(HWND hWndParent, _U_RECT rect = NULL, LPCTSTR szWindowName = NULL,
                  DWORD dwStyle = 0, DWORD dwExStyle = 0,
                  _U_MENUorID MenuOrID = 0U, LPVOID lpCreateParam = NULL)
         {
             if (T::GetWndClassInfo().m_lpszOrigName == NULL)
                  T::GetWndClassInfo().m_lpszOrigName = GetWndClassName();
             ATOM atom = T::GetWndClassInfo().Register(&m_pfnSuperWindowProc);
      
             dwStyle = T::GetWndStyle(dwStyle);
             dwExStyle = T::GetWndExStyle(dwExStyle);
             // set caption
             if (szWindowName == NULL)
                  szWindowName = T::GetWndCaption();
             return CWindowImplBaseT< TBase, TWinTraits >::Create(hWndParent, rect, szWindowName,
                  dwStyle, dwExStyle, MenuOrID, atom, lpCreateParam);
         }

    T::GetWndClassInfo()函数将返回CWndClassInfo类型,CWndClassInfo定义如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #define CWndClassInfo CWndClassInfoW
    typedef _ATL_WNDCLASSINFOW CWndClassInfoW;
    struct _ATL_WNDCLASSINFOW
    {
         WNDCLASSEXW m_wc;
         LPCWSTR m_lpszOrigName;
         WNDPROC pWndProc;
         LPCWSTR m_lpszCursorID;
         BOOL m_bSystemCursor;
         ATOM m_atom;
         WCHAR m_szAutoName[5+sizeof(void*)*CHAR_BIT];
         ATOM Register(WNDPROC* p)
         {
             return AtlWinModuleRegisterWndClassInfoW(&_AtlWinModule, &_AtlBaseModule, this, p);
         }
    };

    静态成员函数GetWndClassInfo()是通过宏DECLARE_WND_CLASS定义的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #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; /
    }

    GetWndClassInfo()完成了CWndClassInfo静态变量的初始化工作。非常重要的一点是,将StartWindowProc函数作为窗口过程保存到m_wc. lpfnWndProc中。

    我们可以看到StartWindowProc函数是CWindowImplBaseT类的静态成员函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    template <class TBase, class TWinTraits>
    LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::StartWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
         CWindowImplBaseT< TBase, TWinTraits >* pThis = (CWindowImplBaseT< TBase, TWinTraits >*)_AtlWinModule.ExtractCreateWndData();
         ATLASSERT(pThis != NULL);
         pThis->m_hWnd = hWnd;
         pThis->m_thunk.Init(pThis->GetWindowProc(), 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);
    }

    StartWindowProc函数完成了几个重要的工作:

    1)获取我们的派生类对象的指针,该指针在第一次窗口过程被调用时将保存到ATL的列表_AtlCreateWndData*中。

    2)保存窗口句柄到m_hWnd中。

    3)初始化m_thunk变量,m_thunk是一个类型,内部保存了一个结构变量:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    struct _stdcallthunk
    {
         DWORD   m_mov;          // mov dword ptr [esp+0x4], pThis (esp+0x4 is hWnd)
         DWORD   m_this;         //
         BYTE    m_jmp;          // jmp WndProc
         DWORD   m_relproc;      // relative jmp
         void Init(DWORD_PTR proc, void* pThis)
         {
             m_mov = 0x042444C7; //C7 44 24 0C
             m_this = PtrToUlong(pThis);
             m_jmp = 0xe9;
             m_relproc = DWORD((INT_PTR)proc - ((INT_PTR)this+sizeof(_stdcallthunk)));
             // write block from data cache and
             // flush from instruction cache
             FlushInstructionCache(GetCurrentProcess(), this, sizeof(_stdcallthunk));
         }
         //some thunks will dynamically allocate the memory for the code
         void* GetCodeAddress()
         {
             return this;
         }
    };

    该结构包含了两个汇编指令:move和jmp。有了它的帮助,StartWindowProc函数内部在执行return pProc(hWnd, uMsg, wParam, lParam);语句之前,就能够不知不觉的在调用栈里将窗口句柄偷换成我们的派生类的指针。
           同时,由于SetWindowLongPtr将当前窗口过程修改为静态成员函数WindowProc。所以WindowProc函数将被调用,WindowProc函数内部将调用我们的目的地函数ProcessWindowMessage,消息路由完成。

    我们总结一下消息路有的经过:

    窗口创建时,将StartWindowProc注册为窗口过程;

    窗口的第一个消息到来时,ATL将窗口指针保存到全局列表中;

    第二个窗口消息到来时,StartWindowProc将句柄保存,从全局列表中获得窗口类的指针,同时将WindowProc成员函数指定为窗口过程,并且用thunk技术将调用栈里面的句柄替换成this指针,然后调用WindowProc;

    WindowProc内部调用ProcessWindowMessage函数,该函数是通过消息映射宏帮助建立的,该函数内部根据不同的消息调用对应的消息映射函数。

    后续的消息到来时,ATL将直接调用WindowProc函数。

     最后一个问题是,窗口类注册时需要指定一个名字以便日后引用,CWindowImpl的Create函数第一次被调用时将检测是否创建,如果没有则创建注册窗口类,同时也指定窗口类的名称。注册窗口类的函数是_ATL_WNDCLASSINFOW结构的Register成员函数。Register内部调用了类AtlModuleRegisterWndClassInfoParamW的成员函数:

    1
    2
    3
    4
    5
    6
    7
    8
    static void FormatWindowClassName(PXSTR szBuffer, void* unique)
        {
    #if defined(_WIN64) // || or Windows 2000
            ::wsprintfW(szBuffer, L"ATL:%p", unique);
    #else
            ::wsprintfW(szBuffer, L"ATL:%8.8X", reinterpret_cast<DWORD_PTR>(unique));
    #endif
        }

    获得了窗口类名称,其实就是把WINCLASSEX变量的内存地址转换成字符串作为窗口类的名字。最后注册窗口类还是依赖于API RegisterClassExW。下面也是AtlModuleRegisterWndClassInfoParamW类的成员函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    ATLINLINE ATLAPI_(ATOM) AtlWinModuleRegisterClassExW(_ATL_WIN_MODULE* pWinModule, const WNDCLASSEXW *lpwc)
    {
         if (pWinModule == NULL || lpwc == NULL)
             return 0;
         ATOM atom = ::RegisterClassExW(lpwc);
         BOOL bRet = pWinModule->m_rgWindowClassAtoms.Add(atom);
         ATLASSERT(bRet);
         (bRet);
         return atom;
    }

    窗口风格

    窗口类风格的设定,归根到底就是CreateWindowEx函数接收的两个参数dwExStyle和dwStyle的设定。在CWindowImpl类的Create函数内部,有这么几行代码:

    1
    2
    3
    4
    5
    6
    7
    dwStyle = T::GetWndStyle(dwStyle);
    dwExStyle = T::GetWndExStyle(dwExStyle);
    // set caption
    if (szWindowName == NULL)
         szWindowName = T::GetWndCaption();
    return CWindowImplBaseT< TBase, TWinTraits >::Create(hWndParent, rect, szWindowName,
           dwStyle, dwExStyle, MenuOrID, atom, lpCreateParam);

    类型T是通过模板参数传递进来的,是我们的派生类,我们的派生类从CWindowImplBaseT继承了GetWndStyle和GetWndExStyle函数。CWindowImplBaseT类是通过调用模板参数类的静态成员函数来实现这两个函数的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    template <class TBase = CWindow, class TWinTraits = CControlWinTraits>
    class ATL_NO_VTABLE CWindowImplBaseT : public CWindowImplRoot< TBase >
    {
    public:
         WNDPROC m_pfnSuperWindowProc;
         CWindowImplBaseT() : m_pfnSuperWindowProc(::DefWindowProc)
         {}
         static DWORD GetWndStyle(DWORD dwStyle)
         {
             return TWinTraits::GetWndStyle(dwStyle);
         }
         static DWORD GetWndExStyle(DWORD dwExStyle)
         {
             return TWinTraits::GetWndExStyle(dwExStyle);
         }
         virtual WNDPROC GetWindowProc()
         {
              return WindowProc;
    }
    ......

    TWinTraits模板参数通常是一个CWinTraits类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    template <DWORD t_dwStyle = 0, DWORD t_dwExStyle = 0>
    class CWinTraits
    {
    public:
         static DWORD GetWndStyle(DWORD dwStyle)
         {
             return dwStyle == 0 ? t_dwStyle : dwStyle;
         }
         static DWORD GetWndExStyle(DWORD dwExStyle)
         {
             return dwExStyle == 0 ? t_dwExStyle : dwExStyle;
         }
    };

    我们只需要将窗口风格和扩展风格作为模板参数传递进去,然后将整个类作为模板参数传递给我们的派生类,就可以创建我们需要的风格的窗口。也可以使用ATL预定义模板类。如下:

    1
    2
    3
    4
    5
    6
    typedef CWinTraits<WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 0>                      CControlWinTraits;
    typedef CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, WS_EX_APPWINDOW | WS_EX_WINDOWEDGE>      CFrameWinTraits;
      
    typedef CWinTraits<WS_OVERLAPPEDWINDOW | WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, WS_EX_MDICHILD>     CMDIChildWinTraits;
      
    typedef CWinTraits<0, 0> CNullTraits;

    EXE组件的窗口表现

    参见工程GPSRecv,同时注意,如果没有实现一个接口的话,进程会自动结束,所以必须至少实现一个接口。

    修改WNDCLASSEX

    第一次调用Create成员函数的时候,CWindowImpl类将替我们完成窗口类的注册和窗口的创建工作。GetWndClassInfo成员函数可以让我们获取到CWindClassInfo结构。CWindClassInfo.m_atom成员标志窗口类是否已被注册,CWindClassInfo.m_wc就是WNDCLASSEX结构。我们可以很方便的获得它并在注册前修改m_wc的成员。如下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    CMainWindow::CMainWindow(void)
    {
         CWndClassInfo& wci=GetWndClassInfo();
         if(!wci.m_atom)
         {
             wci.m_wc.hIcon=LoadIcon(hInst,(LPCTSTR)IDI_ATLWINDOW2);
    wci.m_wc.hIconSm=(HICON)LoadImage(hInst,MAKEINTRESOURCE(IDI_SMALL),IMAGE_ICON,16,16,LR_DEFAULTCOLOR);
             wci.m_wc.hbrBackground=CreateHatchBrush(HS_DIAGCROSS,RGB(0,0,255));
         }
    }

    超类化

    类似于C++的继承。目的:扩展基类窗口的一些功能。子窗口复制基类窗口的窗口过程,然后替换掉名字和窗口过程,如果消息自己处理完后仍然想交给基类窗口处理,那么可以路由到基类窗口过程。

    宏DECLARE_WND_SUPERCLASS(子类窗口名称,基类窗口名称)帮助我们实现这一步骤,下面我们要做的就是编写消息映射宏进行消息处理。

    子类化

    用SetWindowsLong函数将基类窗口的窗口过程替换成子类的窗口过程。

    消息链

    如果我们的窗口类的在处理某一个消息的时候发现其实已经有一个另一个类的成员函数能够处理,我们如何办呢。让我们的窗口类派生自这个类,并在消息映射宏中使用该成员函数。这是一个手工造的方法,还有一种相对自动化的方法,就是使用宏CHAIN_MSG_MAP宏,该宏会调用另一个类的ProcessWindowMessage函数。举个例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    template <typename Deriving>
    class CFileHandler {
    public:
    // Message map in base class
    BEGIN_MSG_MAP(CMainWindow)
     COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew)
     COMMAND_ID_HANDLER(ID_FILE_OPEN, OnFileOpen)
     COMMAND_ID_HANDLER(ID_FILE_SAVE, OnFileSave)
     COMMAND_ID_HANDLER(ID_FILE_SAVE_AS, OnFileSaveAs)
     COMMAND_ID_HANDLER(ID_FILE_EXIT, OnFileExit)
    END_MSG_MAP()
      
     LRESULT OnFileNew(WORD, WORD, HWND, BOOL&);
     LRESULT OnFileOpen(WORD, WORD, HWND, BOOL&);
     LRESULT OnFileSave(WORD, WORD, HWND, BOOL&);
     LRESULT OnFileSaveAs(WORD, WORD, HWND, BOOL&);
     LRESULT OnFileExit(WORD, WORD, HWND, BOOL&);
    };
      
    class CMainWindow :
        public CWindowImpl<CMainWindow, CWindow, CMainWinTraits>,
        public CFileHandler<CMainWindow>
    {
    public:
    BEGIN_MSG_MAP(CMainWindow)
     MESSAGE_HANDLER(WM_PAINT, OnPaint)
     COMMAND_ID_HANDLER(ID_HELP_ABOUT, OnHelpAbout)
     // Chain to a base class
     CHAIN_MSG_MAP(CFileHandler<CMainWindow>)
    END_MSG_MAP()
    ...
    };

    如果刚好我们的窗口类拥有一个成员变量,也许也是一个窗口对象,它能够帮助我们处理一些消息,这时候我们应该用另一个宏CHAIN_MSG_MAP_MEMBER。这两个宏的唯一区别就是一个使用::调用ProcessWindowMessage函数,另一个使用.符号调用。

    关于更加细节的变化,请参考<<ATL Internals>> (2nd Edition)。

    消息转发

    ATL提供了宏FORWARD_NOTIFICATIONS来实现这个功能。实际上该宏调用了下面的函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    static LRESULT Atl3ForwardNotifications(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
         LRESULT lResult = 0;
         switch(uMsg)
         {
         case WM_COMMAND:
         case WM_NOTIFY:
    #ifndef _WIN32_WCE
         case WM_PARENTNOTIFY:
    #endif // !_WIN32_WCE
         case WM_DRAWITEM:
         case WM_MEASUREITEM:
         case WM_COMPAREITEM:
         case WM_DELETEITEM:
         case WM_VKEYTOITEM:
         case WM_CHARTOITEM:
         case WM_HSCROLL:
         case WM_VSCROLL:
         case WM_CTLCOLORBTN:
         case WM_CTLCOLORDLG:
         case WM_CTLCOLOREDIT:
         case WM_CTLCOLORLISTBOX:
         case WM_CTLCOLORMSGBOX:
         case WM_CTLCOLORSCROLLBAR:
         case WM_CTLCOLORSTATIC:
             lResult = ::SendMessage(::GetParent(hWnd), uMsg, wParam, lParam);
             break;
         default:
             bHandled = FALSE;
             break;
         }
         return lResult;
    }

    因此,这实际上硬编码,只有这些消息才会被反射给父窗口。

    CAxHostWindow类

    CAxHostWindow类帮助我们实现了ActiveX控件包容器所需要支持的各种接口。

    CAxWindowT类

    该类简化了CAxHostWindow的使用。创建Grid控件的代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    LRESULT CMainWindow::OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
    {
        RECT rect;
        GetClientRect(&rect);
        LPCTSTR pszName=__T("SimpleGrid.Grid");
        HWND hwndContainer=m_ax.Create(m_hWnd,rect,pszName,WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN, WS_EX_CLIENTEDGE);
        if(!hwndContainer)
            return -1;
        return 0;
    }
    CMainWindow:public CWindowImpl<...>
    {
    private:
    CAxWindow m_ax;
    }

    或者可以分两部创建:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    LRESULT CMainWindow::OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
    {
        RECT rect;
        GetClientRect(&rect);
        //创建控件容器
        HWND hwndContainer=m_ax.Create(m_hWnd,rect,0,WS_CHILD|WS_VISIBLE);
        if(!hwndContainer)
            return -1;
        //创建控件
        CComBSTR pszName("SimpleGrid.Grid");
        HRESULT hr=m_ax.CreateControl(pszName);
        if(hr!=S_OK)
            return -1;
        return 0;
    }

    CAxHostWindow类提供了MoveWindow方法移动窗口的位置和大小。

    CAxHostWindow类提供了QueryControl方法查询控件的接口。然后我们就可以调用控件提供的方法。

  • 相关阅读:
    Java 基础:继承中的执行顺序
    Java 基础:抽象类与接口
    ElasticSearch : 基础简介
    Java GUI : 实现排序算法的动态演示
    Java GUI:将JPanel添加进JScrollPane
    菜鸟的算法入门:java的链表操作
    Docker与Tomcat:去掉项目名称进行访问
    vue输入框的搜索返回信息高亮显示
    position: static 和 position: unset
    vue css /deep/ 用法
  • 原文地址:https://www.cnblogs.com/liangxiaofeng/p/5678867.html
Copyright © 2020-2023  润新知