前言:
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方法查詢控件的接口。然后我們就可以調用控件提供的方法。