分类: C/C++
2008-08-04 09:34:31
下载本文配套源代码
介绍
很多人认为ATL只是用来编写COM组件的,其实你也可以使用ATL中的窗口类来创建基于窗口的应用程序。虽然你可以将基于MFC的程序转换为ATL,但是ATL中对于UI(译注:用户界面)组件的支持太少了。所以,这就要求你需要自己编写很多代码。例如,在ATL中没有文档/视图,所以在你想使用它的时候就需要自己实现了。在本篇中,我们将要探究一些关于窗口类的秘密,以及ATL技术实现的秘密。WTL(Window Template Library,窗口模板库),虽然到现在(译注:本文于2002年10月27日发表在CodeProject)还不为Microsoft所支持,但是它在制作图形应用程序方面跨出了一大步。WTL就是基于ATL的窗口类的。
在开始讨论基于ATL的程序之前,让我们从一个经典的Hello world程序开始吧。这个程序完全用SDK编写,并且我们中几乎所有人都已经熟悉它了。
程序66.
#include这个程序没有什么新鲜的东西,它就是显示了一个窗口,并在窗口中央显示Hello world。LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { char szAppName[] = "Hello world"; HWND hWnd; MSG msg; WNDCLASS wnd; wnd.cbClsExtra = NULL; wnd.cbWndExtra = NULL; wnd.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wnd.hCursor = LoadCursor(NULL, IDC_ARROW); wnd.hIcon = LoadIcon(NULL, IDI_APPLICATION); wnd.hInstance = hInstance; wnd.lpfnWndProc = WndProc; wnd.lpszClassName = szAppName; wnd.lpszMenuName = NULL; wnd.style = CS_HREDRAW | CS_VREDRAW; if (!RegisterClass(&wnd)) { MessageBox(NULL, "Can not register window class", "Error", MB_OK | MB_ICONINFORMATION); return -1; } hWnd = CreateWindow(szAppName, "Hello world", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); while (GetMessage(&msg, NULL, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; } LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { HDC hDC; PAINTSTRUCT ps; RECT rect; switch (uMsg) { case WM_PAINT: hDC = BeginPaint(hWnd, &ps); GetClientRect(hWnd, &rect); DrawText(hDC, "Hello world", -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); EndPaint(hWnd, &ps); break; case WM_DESTROY: PostQuitMessage(0); break; } return DefWindowProc(hWnd, uMsg, wParam, lParam); }
class ZWindow { public: HWND m_hWnd; ZWindow(HWND hWnd = 0) : m_hWnd(hWnd) { } inline void Attach(HWND hWnd) { m_hWnd = hWnd; } inline BOOL ShowWindow(int nCmdShow) { return ::ShowWindow(m_hWnd, nCmdShow); } inline BOOL UpdateWindow() { return ::UpdateWindow(m_hWnd); } };在这里,我只封装了目前需要的API。你可以向这个类中添加全部的API。对于这个类来说的唯一优点,就是你不用像API那样传递HWND参数了,这个类本身会传递这个参数。
class ZWindow { public: HWND m_hWnd; ZWindow(HWND hWnd = 0) : m_hWnd(hWnd) { } inline void Attach(HWND hWnd) { m_hWnd = hWnd; } inline BOOL ShowWindow(int nCmdShow) { return ::ShowWindow(m_hWnd, nCmdShow); } inline BOOL UpdateWindow() { return ::UpdateWindow(m_hWnd); } LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_DESTROY: PostQuitMessage(0); break; } return ::DefWindowProc(hWnd, uMsg, wParam, lParam); } };你需要为WNDCLASS或WNDCLASSEX的一个域提供这个回调函数的地址。并且,你需要在创建ZWindow类对象之后像这样赋值:
ZWindow zwnd; WNDCLASS wnd; wnd.lpfnWndProc = wnd.WndProc;但是当你编译程序的时候,编译器会给出类似这样的错误:
cannot convert from ''long (__stdcall ZWindow::*)(struct HWND__ *, unsigned int,unsigned int,long)'' to ''long (__stdcall *)(struct HWND__ *, unsigned int, unsigned int,long)原因是你不能将成员函数作为回调函数来传递。为什么呢?因为在成员函数的情况下,编译器会自动传给成员函数一个参数,这个参数是指向这个类的指针,或者换句话说是this指针。所以这就意味着当你在成员函数中传递了n个参数的话,那么编译器会传递n 1个参数,并且那个附加的参数就是this指针。这条错误消息就表明编译器不能将成员函数转换为全局函数。
#include这个程序只是简单示范了一下ZWindow的用法,说实话,这个类就不会做什么特别的了。它只是对Windows API的一层包装,唯一的优点就是你不需要传递HWND参数了,但是你必须得在调用成员函数的时候输入对象的名称。class ZWindow { public: HWND m_hWnd; ZWindow(HWND hWnd = 0) : m_hWnd(hWnd) { } inline void Attach(HWND hWnd) { m_hWnd = hWnd; } inline BOOL ShowWindow(int nCmdShow) { return ::ShowWindow(m_hWnd, nCmdShow); } inline BOOL UpdateWindow() { return ::UpdateWindow(m_hWnd); } LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_DESTROY: PostQuitMessage(0); break; } return ::DefWindowProc(hWnd, uMsg, wParam, lParam); } }; int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { char szAppName[] = "Hello world"; HWND hWnd; MSG msg; WNDCLASS wnd; ZWindow zwnd; wnd.cbClsExtra = NULL; wnd.cbWndExtra = NULL; wnd.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wnd.hCursor = LoadCursor(NULL, IDC_ARROW); wnd.hIcon = LoadIcon(NULL, IDI_APPLICATION); wnd.hInstance = hInstance; wnd.lpfnWndProc = ZWindow::WndProc; wnd.lpszClassName = szAppName; wnd.lpszMenuName = NULL; wnd.style = CS_HREDRAW | CS_VREDRAW; if (!RegisterClass(&wnd)) { MessageBox(NULL, "Can not register window class", "Error", MB_OK | MB_ICONINFORMATION); return -1; } hWnd = CreateWindow(szAppName, "Hello world", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); zwnd.Attach(hWnd); zwnd.ShowWindow(nCmdShow); zwnd.UpdateWindow(); while (GetMessage(&msg, NULL, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; }
ShowWindow(hWnd, nCmdShow);现在,你可以这么做:
zwnd.ShowWindow(nCmdShow);到现在为止,这并不是一个明显的优点。
switch (uMsg) { case WM_PAINT: hDC = ::BeginPaint(hWnd, &ps); ::GetClientRect(hWnd, &rect); ::DrawText(hDC, "Hello world", -1, &rect, DT_CENTER | DT_VCENTER DT_SINGLELINE); ::EndPaint(hWnd, &ps); break; case WM_DESTROY: ::PostQuitMessage(0); break; }这个代码很正确,它会在窗口的正中显示Hello world。但是,为什么要用BeginPaint、GetClientRect和EndPaint这些API呢?根据我们的标准,这些API都应该作为ZWindow的成员函数来使用的——它们的第一个参数都是HWND。
#include程序的输出为:using namespace std; class C { public: void NonStaticFunc() { cout << "NonStaticFun" << endl; } static void StaticFun(C* pC) { cout << "StaticFun" << endl; pC->NonStaticFunc(); } }; int main() { C objC; C::StaticFun(&objC); return 0; }
StaticFun NonStaticFun所以,我们就可以使用和这里相同的技术,也就是将ZWindow对象的地址存入一个全局变量,然后利用这个指针调用非static成员函数。下面是前一个程序的更新版本,在其中我们没有直接调用窗口的API。
#include那么,我们终于有了这个可以工作的程序。现在,让我们来利用面向对象程序设计。如果我们对于每个消息都调用函数,并且使这些函数都成为虚函数的话,那么我们就可以在继承ZWindow类之后调用这些函数了。所以,我们可以自定义ZWindow的默认行为。现在,WndProc是类似这个样子:class ZWindow; ZWindow* g_pWnd = NULL; class ZWindow { public: HWND m_hWnd; ZWindow(HWND hWnd = 0) : m_hWnd(hWnd) { } inline void Attach(HWND hWnd) { m_hWnd = hWnd; } inline BOOL ShowWindow(int nCmdShow) { return ::ShowWindow(m_hWnd, nCmdShow); } inline BOOL UpdateWindow() { return ::UpdateWindow(m_hWnd); } inline HDC BeginPaint(LPPAINTSTRUCT ps) { return ::BeginPaint(m_hWnd, ps); } inline BOOL EndPaint(LPPAINTSTRUCT ps) { return ::EndPaint(m_hWnd, ps); } inline BOOL GetClientRect(LPRECT rect) { return ::GetClientRect(m_hWnd, rect); } BOOL Create(LPCTSTR szClassName, LPCTSTR szTitle, HINSTANCE hInstance, HWND hWndParent = 0, DWORD dwStyle = WS_OVERLAPPEDWINDOW, DWORD dwExStyle = 0, HMENU hMenu = 0) { m_hWnd = ::CreateWindowEx(dwExStyle, szClassName, szTitle, dwStyle, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hWndParent, hMenu, hInstance, NULL); return m_hWnd != NULL; } static LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { ZWindow* pThis = g_pWnd; HDC hDC; PAINTSTRUCT ps; RECT rect; switch (uMsg) { case WM_PAINT: hDC = pThis->BeginPaint(&ps); pThis->GetClientRect(&rect); ::DrawText(hDC, "Hello world", -1, &rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); pThis->EndPaint(&ps); break; case WM_DESTROY: ::PostQuitMessage(0); break; } return ::DefWindowProc(hWnd, uMsg, wParam, lParam); } }; int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { char szAppName[] = "Hello world"; MSG msg; WNDCLASS wnd; ZWindow zwnd; wnd.cbClsExtra = NULL; wnd.cbWndExtra = NULL; wnd.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wnd.hCursor = LoadCursor(NULL, IDC_ARROW); wnd.hIcon = LoadIcon(NULL, IDI_APPLICATION); wnd.hInstance = hInstance; wnd.lpfnWndProc = zwnd.WndProc; wnd.lpszClassName = szAppName; wnd.lpszMenuName = NULL; wnd.style = CS_HREDRAW | CS_VREDRAW; if (!RegisterClass(&wnd)) { MessageBox(NULL, "Can not register window class", "Error", MB_OK | MB_ICONINFORMATION); return -1; } g_pWnd = &zwnd; zwnd.Create(szAppName, "Hell world", hInstance); zwnd.ShowWindow(nCmdShow); zwnd.UpdateWindow(); while (GetMessage(&msg, NULL, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; }
static LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { ZWindow* pThis = g_pWnd; switch (uMsg) { case WM_CREATE: pThis->OnCreate(wParam, lParam); break; case WM_PAINT: pThis->OnPaint(wParam, lParam); break; case WM_DESTROY: ::PostQuitMessage(0); break; } return ::DefWindowProc(hWnd, uMsg, wParam, lParam); }在这里,OnCreate和OnPaint是虚函数。并且,当我们从ZWindow继承一个类的时候,我们就可以重写所有我们想自定义的这些函数。下面是一个完整的程序,它示范了在派生类中WM_PAINT消息的使用。
#include程序的输出是一个窗口中的一条“Hello world from Drive”消息。在我们使用派生类之前,可以说一切都是顺利的。当我们从ZWindow派生出多于一个类的时候,问题就会发生。这样,所有的消息就都会流向ZWindow最后继承的那个派生类。让我们看看以下的程序。class ZWindow; ZWindow* g_pWnd = NULL; class ZWindow { public: HWND m_hWnd; ZWindow(HWND hWnd = 0) : m_hWnd(hWnd) { } inline void Attach(HWND hWnd) { m_hWnd = hWnd; } inline BOOL ShowWindow(int nCmdShow) { return ::ShowWindow(m_hWnd, nCmdShow); } inline BOOL UpdateWindow() { return ::UpdateWindow(m_hWnd); } inline HDC BeginPaint(LPPAINTSTRUCT ps) { return ::BeginPaint(m_hWnd, ps); } inline BOOL EndPaint(LPPAINTSTRUCT ps) { return ::EndPaint(m_hWnd, ps); } inline BOOL GetClientRect(LPRECT rect) { return ::GetClientRect(m_hWnd, rect); } BOOL Create(LPCTSTR szClassName, LPCTSTR szTitle, HINSTANCE hInstance, HWND hWndParent = 0, DWORD dwStyle = WS_OVERLAPPEDWINDOW, DWORD dwExStyle = 0, HMENU hMenu = 0) { m_hWnd = ::CreateWindowEx(dwExStyle, szClassName, szTitle, dwStyle, CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hWndParent, hMenu, hInstance, NULL); return m_hWnd != NULL; } virtual LRESULT OnPaint(WPARAM wParam, LPARAM lParam) { HDC hDC; PAINTSTRUCT ps; RECT rect; hDC = BeginPaint(&ps); GetClientRect(&rect); ::DrawText(hDC, "Hello world", -1, &rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); EndPaint(&ps); return 0; } virtual LRESULT OnCreate(WPARAM wParam, LPARAM lParam) { return 0; } static LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { ZWindow* pThis = g_pWnd; switch (uMsg) { case WM_CREATE: pThis->OnCreate(wParam, lParam); break; case WM_PAINT: pThis->OnPaint(wParam, lParam); break; case WM_DESTROY: ::PostQuitMessage(0); break; } return ::DefWindowProc(hWnd, uMsg, wParam, lParam); } }; class ZDriveWindow : public ZWindow { public: LRESULT OnPaint(WPARAM wParam, LPARAM lParam) { HDC hDC; PAINTSTRUCT ps; RECT rect; hDC = BeginPaint(&ps); GetClientRect(&rect); SetBkMode(hDC, TRANSPARENT); DrawText(hDC, "Hello world From Drive", -1, &rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); EndPaint(&ps); return 0; } };
#include程序的输出表明,不管你单击了哪个窗口,都会弹出相同的MessageBox。class ZWindow; ZWindow* g_pWnd = NULL; class ZWindow { public: HWND m_hWnd; ZWindow(HWND hWnd = 0) : m_hWnd(hWnd) { } inline void Attach(HWND hWnd) { m_hWnd = hWnd; } inline BOOL ShowWindow(int nCmdShow) { return ::ShowWindow(m_hWnd, nCmdShow); } inline BOOL UpdateWindow() { return ::UpdateWindow(m_hWnd); } inline HDC BeginPaint(LPPAINTSTRUCT ps) { return ::BeginPaint(m_hWnd, ps); } inline BOOL EndPaint(LPPAINTSTRUCT ps) { return ::EndPaint(m_hWnd, ps); } inline BOOL GetClientRect(LPRECT rect) { return ::GetClientRect(m_hWnd, rect); } BOOL Create(LPCTSTR szClassName, LPCTSTR szTitle, HINSTANCE hInstance, HWND hWndParent = 0, DWORD dwStyle = WS_OVERLAPPEDWINDOW, DWORD dwExStyle = 0, HMENU hMenu = 0, int x = CW_USEDEFAULT, int y = CW_USEDEFAULT, int nWidth = CW_USEDEFAULT, int nHeight = CW_USEDEFAULT) { m_hWnd = ::CreateWindowEx(dwExStyle, szClassName, szTitle, dwStyle, x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, NULL); return m_hWnd != NULL; } virtual LRESULT OnPaint(WPARAM wParam, LPARAM lParam) { HDC hDC; PAINTSTRUCT ps; RECT rect; hDC = BeginPaint(&ps); GetClientRect(&rect); ::DrawText(hDC, "Hello world", -1, &rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); EndPaint(&ps); return 0; } virtual LRESULT OnLButtonDown(WPARAM wParam, LPARAM lParam) { return 0; } virtual LRESULT OnCreate(WPARAM wParam, LPARAM lParam) { return 0; } virtual LRESULT OnKeyDown(WPARAM wParam, LPARAM lParam) { return 0; } static LRESULT CALLBACK StartWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { ZWindow* pThis = g_pWnd; if (uMsg == WM_NCDESTROY) ::PostQuitMessage(0); switch (uMsg) { case WM_CREATE: pThis->OnCreate(wParam, lParam); break; case WM_PAINT: pThis->OnPaint(wParam, lParam); break; case WM_LBUTTONDOWN: pThis->OnLButtonDown(wParam, lParam); break; case WM_KEYDOWN: pThis->OnKeyDown(wParam, lParam); break; case WM_DESTROY: ::PostQuitMessage(0); break; } return ::DefWindowProc(hWnd, uMsg, wParam, lParam); } }; class ZDriveWindow1 : public ZWindow { public: LRESULT OnPaint(WPARAM wParam, LPARAM lParam) { HDC hDC; PAINTSTRUCT ps; RECT rect; hDC = BeginPaint(&ps); GetClientRect(&rect); ::SetBkMode(hDC, TRANSPARENT); ::DrawText(hDC, "ZDriveWindow1", -1, &rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); EndPaint(&ps); return 0; } LRESULT OnLButtonDown(WPARAM wParam, LPARAM lParam) { ::MessageBox(NULL, "ZDriveWindow1::OnLButtonDown", "Msg", MB_OK); return 0; } }; class ZDriveWindow2 : public ZWindow { public: LRESULT OnPaint(WPARAM wParam, LPARAM lParam) { HDC hDC; PAINTSTRUCT ps; RECT rect; hDC = BeginPaint(&ps); GetClientRect(&rect); ::SetBkMode(hDC, TRANSPARENT); ::Rectangle(hDC, rect.left, rect.top, rect.right, rect.bottom); ::DrawText(hDC, "ZDriveWindow2", -1, &rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); EndPaint(&ps); return 0; } LRESULT OnLButtonDown(WPARAM wParam, LPARAM lParam) { ::MessageBox(NULL, "ZDriveWindow2::OnLButtonDown", "Msg", MB_OK); return 0; } }; int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { char szAppName[] = "Hello world"; MSG msg; WNDCLASS wnd; ZDriveWindow1 zwnd1; ZDriveWindow2 zwnd2; wnd.cbClsExtra = NULL; wnd.cbWndExtra = NULL; wnd.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH); wnd.hCursor = LoadCursor(NULL, IDC_ARROW); wnd.hIcon = LoadIcon(NULL, IDI_APPLICATION); wnd.hInstance = hInstance; wnd.lpfnWndProc = ZWindow::StartWndProc; wnd.lpszClassName = szAppName; wnd.lpszMenuName = NULL; wnd.style = CS_HREDRAW | CS_VREDRAW; if (!RegisterClass(&wnd)) { MessageBox(NULL, "Can not register window class", "Error", MB_OK | MB_ICONINFORMATION); return -1; } g_pWnd = &zwnd1; zwnd1.Create(szAppName, "Hell world", hInstance); zwnd1.ShowWindow(nCmdShow); zwnd1.UpdateWindow(); g_pWnd = &zwnd2; zwnd2.Create(szAppName, "Hello world", hInstance, zwnd1.m_hWnd, WS_VISIBLE | WS_CHILD | ES_MULTILINE, NULL, NULL, 0, 0, 150, 150); while (GetMessage(&msg, NULL, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; }
#include程序的输出为:using namespace std; struct S { char ch; int i; }; int main() { cout << "Size of character = " << sizeof(char) << endl; cout << "Size of integer = " << sizeof(int) << endl; cout << "Size of structure = " << sizeof(S) << endl; return 0; }
Size of character = 1 Size of integer = 4 Size of structure = 8一个整型和一个字符的尺寸之和应该是5而不是8。那么让我们略微修改一下程序,再添加一个成员变量,看看会发生什么。
#include程序的输出和前一个一样。那么这里发生了什么?再修改一下程序,看看布幔之下发生了什么吧。using namespace std; struct S { char ch1; char ch2; int i; }; int main() { cout << "Size of character = " << sizeof(char) << endl; cout << "Size of integer = " << sizeof(int) << endl; cout << "Size of structure = " << sizeof(S) << endl; return 0; }
#include程序的输出为:using namespace std; struct S { char ch1; char ch2; int i; }s; int main() { cout << "Address of ch1 = " << (int)&s.ch1 << endl; cout << "Address of ch2 = " << (int)&s.ch2 << endl; cout << "Address of int = " << (int)&s.i << endl; return 0; }
Address of ch1 = 4683576 Address of ch2 = 4683577 Address of int = 4683580这是由于结构和联合成员的字对齐的缘故。如果你注意观察的话,你就能推断出来这个结构外的每个变量都存储在能被4整除的地址上,这是为了提高处理器的性能。所以,这里的结构分配了4的整数倍的内存空间,也就是4683576,ch1和它有相同的地址。ch2成员存储在这个位置之后,而int i存储在4683580的位置上。这个位置不是4683578的原因是它不能被4整除。现在的问题是,4683578和4683579的位置上是什么呢?答案是如果变量是本地变量,那么这里是垃圾值;如果是static或全局变量,那么是0。让我们看看下面这个程序来更好地理解这一点。
#include程序的输出为:using namespace std; struct S { char ch1; char ch2; int i; }; int main() { S s = { ''A'', ''B'', 10}; void* pVoid = (void*)&s; char* pChar = (char*)pVoid; cout << (char)*(pChar 0) << endl; cout << (char)*(pChar 1) << endl; cout << (char)*(pChar 2) << endl; cout << (char)*(pChar 3) << endl; cout << (int)*(pChar 4) << endl; return 0; }
A B … … 10程序的输出清楚地表明,那些空间中是垃圾值,就像下表一样。
#include程序的输出为:using namespace std; #pragma pack(push, 1) struct S { char ch; int i; }; #pragma pack(pop) int main() { cout << "Size of structure = " << sizeof(S) << endl; return 0; }
Size of structure = 5
#pragma pack(push,1) // 存储机器代码的结构 struct Thunk { BYTE m_jmp; // jmp指令的操作码 DWORD m_relproc; // 相对jmp }; #pragma pack(pop)这种类型的结构保存了thunk代码,它可以在不工作的时候执行。让我们来看看下面这种简单的情况,我们将要使用thunk来执行我们想要执行的函数。
#include程序的输出为:#include using namespace std; class C; C* g_pC = NULL; typedef void(*pFUN)(); #pragma pack(push,1) // 存储机器代码的结构 struct Thunk { BYTE m_jmp; // jmp指令的操作码 DWORD m_relproc; // 相对jmp }; #pragma pack(pop) class C { public: Thunk m_thunk; void Init(pFUN pFun, void* pThis) { // 跳转指令的操作码 m_thunk.m_jmp = 0xe9; // 相应函数的地址 m_thunk.m_relproc = (int)pFun - ((int)this sizeof(Thunk)); FlushInstructionCache(GetCurrentProcess(), &m_thunk, sizeof(m_thunk)); } // 这是回调函数 static void CallBackFun() { C* pC = g_pC; // 初始化thunk pC->Init(StaticFun, pC); // 获得thunk代码地址 pFUN pFun = (pFUN)&(pC->m_thunk); // 开始执行thunk代码,调用StaticFun pFun(); cout << "C::CallBackFun" << endl; } static void StaticFun() { cout << "C::StaticFun" << endl; } }; int main() { C objC; g_pC = &objC; C::CallBackFun(); return 0; }
C::StaticFun C::CallBackFun在这里,StaticFun是通过thunk调用的,而thunk是在Init成员函数中初始化的。程序的执行是类似这个样子
#pragma pack(push,1) struct _WndProcThunk { DWORD m_mov; // mov dword ptr [esp 0x4], pThis (esp 0x4 is hWnd) DWORD m_this; BYTE m_jmp; // jmp WndProc DWORD m_relproc; // 相对jmp }; #pragma pack(pop)并且,在初始化的时刻,写入操作码“mov dword ptr [esp 4], pThis”。是类似这个样子:
void Init(WNDPROC proc, void* pThis) { thunk.m_mov = 0x042444C7; //C7 44 24 04 thunk.m_this = (DWORD)pThis; thunk.m_jmp = 0xe9; thunk.m_relproc = (int)proc - ((int)this sizeof(_WndProcThunk)); FlushInstructionCache(GetCurrentProcess(), &thunk, sizeof(thunk)); }并且,在初始化thunk代码之后,获得thunk的地址并向thunk代码设置新的回调函数。然后,thunk代码会调用WindowProc,但是现在第一个参数就不是HWND了,事实上它是this指针。所以我们可以将它安全的转换为ZWindow*,并调用ProcessWindowMessage函数。
static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { ZWindow* pThis = (ZWindow*)hWnd; if (uMsg == WM_NCDESTROY) PostQuitMessage(0); if (!pThis->ProcessWindowMessage(pThis->m_hWnd, uMsg, wParam, lParam)) return ::DefWindowProc(pThis->m_hWnd, uMsg, wParam, lParam); else return 0; }现在,每个窗口正确的窗口过程就可以被调用了。整个的过程如下图所示: