前言:
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类与父类的关系图:
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方法查询控件的接口。然后我们就可以调用控件提供的方法。