相比于MFC的消息机制WTL/ATL的实现更加优雅。后者将win32 API与面向对象技术完美地结合起来去掉了庞杂的MFC依赖生成的软件体积更小运行速度更快。在其中如何将窗口函数转变为对窗口对象成员函数的调用是WTL/ATL消息机制的核心。以下利用trunk技术模拟了这一过程仅供参考(创建对话框窗口可以用CreateDialogParam/DialogBoxParam或者CreateDialogIndirectParam/ DialogBoxIndirectParam传递this指针)#include Windows.h #include assert.h #include tchar.h #pragma pack(push,1) struct _StdCallThunk32//32 Bits for intel/amd architecture { DWORD m_mov; ULONG_PTR m_this; BYTE m_jmp; ULONG_PTR m_relproc; void Init(ULONG_PTR wPtr, ULONG_PTR proc) { m_mov 0x042444C7;//mov dword ptr [esp4], pThis m_this wPtr; m_jmp 0xe9;//jmp pRelFunc m_relproc proc; } }; struct _StdCallThunk64//64 Bits for intel/amd architecture { unsigned char mov_rax_1[2]; unsigned char object[sizeof(ULONG_PTR)]; unsigned char mov_rax_to_rcx[3]; unsigned char mov_rax_2[2]; unsigned char procedure[sizeof(ULONG_PTR)]; unsigned char jump_rax[3]; void Init(ULONG_PTR wPtr, ULONG_PTR proc) { mov_rax_1[0] 0x48;//mov rax, pThis mov_rax_1[1] 0xb8; memcpy(object, wPtr, sizeof(ULONG_PTR)); mov_rax_to_rcx[0] 0x48;//mov rcx,rax mov_rax_to_rcx[1] 0x89; mov_rax_to_rcx[2] 0xc1; mov_rax_2[0] 0x48;//mov rax, pFunc mov_rax_2[1] 0xb8; memcpy(procedure, proc, sizeof(ULONG_PTR)); jump_rax[0] 0x48;//jmp rax jump_rax[1] 0xff; jump_rax[2] 0xe0; } }; #pragma pack(pop) #if defined(_M_X64) typedef struct _StdCallThunk64 StdCallThunk; #elif defined(_M_IX86) typedef struct _StdCallThunk32 StdCallThunk; #endif class Window { public: Window(); ~Window(); public: BOOL Create(); BOOL Attach(HWND hWnd); protected: LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam); protected: HWND m_hWnd; StdCallThunk* m_pThunk; LONG_PTR m_OldWndProc; protected: static LRESULT CALLBACK TempWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); static LRESULT CALLBACK StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); }; Window::Window() :m_hWnd(NULL), m_pThunk(nullptr),m_OldWndProc(NULL) { } Window::~Window() { if(m_OldWndProc ! NULL)//restore old winproc { #if defined(_WIN64) SetWindowLongPtr(m_hWnd, GWLP_WNDPROC, (ULONG_PTR)m_OldWndProc); #elif defined(_WIN32) SetWindowLong(m_hWnd, GWLP_WNDPROC, (ULONG)m_OldWndProc); #endif } if (m_pThunk ! nullptr) VirtualFree(m_pThunk, sizeof(StdCallThunk), MEM_RELEASE); } BOOL Window::Create() { LPCTSTR lpszClassName _T(ClassName); HINSTANCE hInstance GetModuleHandle(NULL); WNDCLASSEX wcex { sizeof(WNDCLASSEX) }; wcex.lpfnWndProc TempWndProc; wcex.hInstance hInstance; wcex.lpszClassName lpszClassName; wcex.hbrBackground (HBRUSH)(COLOR_WINDOW 1); ATOM regRes RegisterClassEx(wcex); if (m_pThunk nullptr) m_pThunk (StdCallThunk*)VirtualAlloc(NULL, sizeof(StdCallThunk), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); HWND hWnd CreateWindow(lpszClassName, NULL, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, this); //m_hWnd hWnd;//No need to assign value to m_hWnd again. bacause it is already set. if (m_hWnd NULL) { return FALSE; } ShowWindow(m_hWnd, SW_SHOW); UpdateWindow(m_hWnd); return TRUE; } BOOL Window::Attach(HWND hWnd) { if (m_hWnd ! NULL || hWnd NULL) return FALSE; if (m_pThunk nullptr) m_pThunk (StdCallThunk*)VirtualAlloc(NULL, sizeof(StdCallThunk), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (m_pThunk nullptr) return FALSE; #if defined(_WIN64) ULONG_PTR rel (LONG_PTR)Window::StaticWndProc; m_OldWndProc SetWindowLongPtr(hWnd, GWLP_WNDPROC, (ULONG_PTR)m_pThunk); #elif defined(_WIN32) ULONG_PTR rel (DWORD)Window::StaticWndProc - ((DWORD)m_pThunk sizeof(StdCallThunk)); m_OldWndProc SetWindowLong(hWnd, GWLP_WNDPROC, (ULONG)m_pThunk); #endif m_pThunk-Init((ULONG_PTR)this, rel); m_hWnd hWnd; SendMessage(hWnd, WM_NULL, 0, 0); ShowWindow(hWnd, SW_SHOW); UpdateWindow(hWnd); return TRUE; } LRESULT Window::WndProc(UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_LBUTTONUP: MessageBox(m_hWnd, _T(LButtonUp), _T(Message), MB_OK | MB_ICONINFORMATION); break; case WM_RBUTTONUP: MessageBox(m_hWnd, _T(RButtonUp), _T(Message), MB_OK | MB_ICONINFORMATION); break; case WM_SYSCOMMAND: if (wParam SC_CLOSE) MessageBox(m_hWnd, _T(Close Window Request), _T(Message), MB_OK | MB_ICONINFORMATION); break; case WM_DESTROY: PostQuitMessage(0); break; default: break; } return DefWindowProc(m_hWnd, message, wParam, lParam); } LRESULT CALLBACK Window::TempWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { Window* pThis NULL; if (message WM_NCCREATE)//First Msg WM_GETMINMAXINFO, Second Msg WM_NCCREATE { LPCREATESTRUCT lpcs (LPCREATESTRUCT)lParam; pThis (Window*)lpcs-lpCreateParams; } if(message WM_INITDIALOG)//Use CreateDialogParam or DialogBoxParam to create a dialog, you can use this message to get pThis. { pThis (Window*)lParam; } if(pThis NULL) return DefWindowProc(hWnd, message, wParam, lParam); WNDPROC pWndProc (WNDPROC)pThis-m_pThunk; #if defined(_WIN64) ULONG_PTR rel (LONG_PTR)Window::StaticWndProc; pThis-m_OldWndProc SetWindowLongPtr(hWnd, GWLP_WNDPROC, (ULONG_PTR)pWndProc); #elif defined(_WIN32) ULONG_PTR rel (DWORD)Window::StaticWndProc - ((DWORD)pThis-m_pThunk sizeof(StdCallThunk)); pThis-m_OldWndProc SetWindowLong(hWnd, GWLP_WNDPROC, (ULONG)pWndProc); #endif pThis-m_pThunk-Init((ULONG_PTR)pThis, rel); //FlushInstructionCache(GetCurrentProcess(), pThis-m_pThunk, sizeof(StdCallThunk)); pThis-m_hWnd hWnd; return pWndProc(hWnd, message, wParam, lParam); } LRESULT CALLBACK Window::StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { return ((Window*)hWnd)-WndProc(message, wParam, lParam);//ret指令会弹出之前压入栈的返回地址并跳转到该地址。由于trunk中是直接JMP过来的并未压栈(与call指令不同)所以这里会直接跳到调用trunk的地方。 } int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { Window wnd; wnd.Create(); MSG msg; while (GetMessage(msg, NULL, 0, 0)) { TranslateMessage(msg); DispatchMessage(msg); } return (int)msg.wParam; }附录WTL 向导与VS2015集成方法① 下载最新的WTL_10_10320库(Windows Template Library (WTL) - Browse Files at SourceForge.net)② 解压得到WTL/AppWizard/在VS的安装目录下找到VCWizards/AppWiz, 在其中创建一个WTL文件夹将WTL/AppWizard目录下的所有文件拷贝到该新建的WTL文件夹下。③ 修改VCWizards/AppWiz/WTL/Files/HTML/1033/下面的三个文件AppType.htm, default.htm, UIFeatures.htm 修改位置 ① document.styleSheets(0).imports(0).href strURL; ① document.scripts(INCLUDE_SCRIPT).src strScriptPath; ② document.scripts(INCLUDE_COMMON).src strCommonPath; 修改结果 ① document.styleSheets(0).imports(0).href window.external.FindSymbol(SCRIPT_COMMON_PATH) /NewStyles.css; ② document.scripts(INCLUDE_SCRIPT).src window.external.FindSymbol(SCRIPT_COMMON_PATH) /Script.js; ③ document.scripts(INCLUDE_COMMON).src window.external.FindSymbol(SCRIPT_COMMON_PATH) /Common.js; default.htm 除了上述改动还需要将一个未封闭的html节点删除以保证语法正确(删除以下两行) !-- This closing tag is here only to avoid security warning -- /OBJECT④ 管理权限打开command命令窗口进入VCWizards/AppWiz/WTL目录下执行指令cscript Setup.js⑤ 重新启动visual studio, 找到visual c 类型分组可看到WTL工程模板就能利用WTL 向导创建WTL项目了。注1 如果在第⑤步没有看到WTL工程模板则表明安装有问题可能需要修改注册表或者修改VCWizards/AppWiz/WTL/Setup.js脚本请参考其他资料。注2如果在第⑤步WTL向导报错提示脚本执行错误类似setDirection未定义参数无效等信息此时可以修改VCWizards/AppWiz/WTL/Files/HTML/1033/下面三个文件的编码为UFT-8 BOM. 如果改编码还是报错检查VCWizards/AppWiz/WTL/Files/HTML/1033/下面三个文件的属性将它们的unblock勾选上再次尝试。
WTL 之trunk技术学习
相比于MFC的消息机制WTL/ATL的实现更加优雅。后者将win32 API与面向对象技术完美地结合起来去掉了庞杂的MFC依赖生成的软件体积更小运行速度更快。在其中如何将窗口函数转变为对窗口对象成员函数的调用是WTL/ATL消息机制的核心。以下利用trunk技术模拟了这一过程仅供参考(创建对话框窗口可以用CreateDialogParam/DialogBoxParam或者CreateDialogIndirectParam/ DialogBoxIndirectParam传递this指针)#include Windows.h #include assert.h #include tchar.h #pragma pack(push,1) struct _StdCallThunk32//32 Bits for intel/amd architecture { DWORD m_mov; ULONG_PTR m_this; BYTE m_jmp; ULONG_PTR m_relproc; void Init(ULONG_PTR wPtr, ULONG_PTR proc) { m_mov 0x042444C7;//mov dword ptr [esp4], pThis m_this wPtr; m_jmp 0xe9;//jmp pRelFunc m_relproc proc; } }; struct _StdCallThunk64//64 Bits for intel/amd architecture { unsigned char mov_rax_1[2]; unsigned char object[sizeof(ULONG_PTR)]; unsigned char mov_rax_to_rcx[3]; unsigned char mov_rax_2[2]; unsigned char procedure[sizeof(ULONG_PTR)]; unsigned char jump_rax[3]; void Init(ULONG_PTR wPtr, ULONG_PTR proc) { mov_rax_1[0] 0x48;//mov rax, pThis mov_rax_1[1] 0xb8; memcpy(object, wPtr, sizeof(ULONG_PTR)); mov_rax_to_rcx[0] 0x48;//mov rcx,rax mov_rax_to_rcx[1] 0x89; mov_rax_to_rcx[2] 0xc1; mov_rax_2[0] 0x48;//mov rax, pFunc mov_rax_2[1] 0xb8; memcpy(procedure, proc, sizeof(ULONG_PTR)); jump_rax[0] 0x48;//jmp rax jump_rax[1] 0xff; jump_rax[2] 0xe0; } }; #pragma pack(pop) #if defined(_M_X64) typedef struct _StdCallThunk64 StdCallThunk; #elif defined(_M_IX86) typedef struct _StdCallThunk32 StdCallThunk; #endif class Window { public: Window(); ~Window(); public: BOOL Create(); BOOL Attach(HWND hWnd); protected: LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam); protected: HWND m_hWnd; StdCallThunk* m_pThunk; LONG_PTR m_OldWndProc; protected: static LRESULT CALLBACK TempWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); static LRESULT CALLBACK StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); }; Window::Window() :m_hWnd(NULL), m_pThunk(nullptr),m_OldWndProc(NULL) { } Window::~Window() { if(m_OldWndProc ! NULL)//restore old winproc { #if defined(_WIN64) SetWindowLongPtr(m_hWnd, GWLP_WNDPROC, (ULONG_PTR)m_OldWndProc); #elif defined(_WIN32) SetWindowLong(m_hWnd, GWLP_WNDPROC, (ULONG)m_OldWndProc); #endif } if (m_pThunk ! nullptr) VirtualFree(m_pThunk, sizeof(StdCallThunk), MEM_RELEASE); } BOOL Window::Create() { LPCTSTR lpszClassName _T(ClassName); HINSTANCE hInstance GetModuleHandle(NULL); WNDCLASSEX wcex { sizeof(WNDCLASSEX) }; wcex.lpfnWndProc TempWndProc; wcex.hInstance hInstance; wcex.lpszClassName lpszClassName; wcex.hbrBackground (HBRUSH)(COLOR_WINDOW 1); ATOM regRes RegisterClassEx(wcex); if (m_pThunk nullptr) m_pThunk (StdCallThunk*)VirtualAlloc(NULL, sizeof(StdCallThunk), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); HWND hWnd CreateWindow(lpszClassName, NULL, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, this); //m_hWnd hWnd;//No need to assign value to m_hWnd again. bacause it is already set. if (m_hWnd NULL) { return FALSE; } ShowWindow(m_hWnd, SW_SHOW); UpdateWindow(m_hWnd); return TRUE; } BOOL Window::Attach(HWND hWnd) { if (m_hWnd ! NULL || hWnd NULL) return FALSE; if (m_pThunk nullptr) m_pThunk (StdCallThunk*)VirtualAlloc(NULL, sizeof(StdCallThunk), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (m_pThunk nullptr) return FALSE; #if defined(_WIN64) ULONG_PTR rel (LONG_PTR)Window::StaticWndProc; m_OldWndProc SetWindowLongPtr(hWnd, GWLP_WNDPROC, (ULONG_PTR)m_pThunk); #elif defined(_WIN32) ULONG_PTR rel (DWORD)Window::StaticWndProc - ((DWORD)m_pThunk sizeof(StdCallThunk)); m_OldWndProc SetWindowLong(hWnd, GWLP_WNDPROC, (ULONG)m_pThunk); #endif m_pThunk-Init((ULONG_PTR)this, rel); m_hWnd hWnd; SendMessage(hWnd, WM_NULL, 0, 0); ShowWindow(hWnd, SW_SHOW); UpdateWindow(hWnd); return TRUE; } LRESULT Window::WndProc(UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_LBUTTONUP: MessageBox(m_hWnd, _T(LButtonUp), _T(Message), MB_OK | MB_ICONINFORMATION); break; case WM_RBUTTONUP: MessageBox(m_hWnd, _T(RButtonUp), _T(Message), MB_OK | MB_ICONINFORMATION); break; case WM_SYSCOMMAND: if (wParam SC_CLOSE) MessageBox(m_hWnd, _T(Close Window Request), _T(Message), MB_OK | MB_ICONINFORMATION); break; case WM_DESTROY: PostQuitMessage(0); break; default: break; } return DefWindowProc(m_hWnd, message, wParam, lParam); } LRESULT CALLBACK Window::TempWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { Window* pThis NULL; if (message WM_NCCREATE)//First Msg WM_GETMINMAXINFO, Second Msg WM_NCCREATE { LPCREATESTRUCT lpcs (LPCREATESTRUCT)lParam; pThis (Window*)lpcs-lpCreateParams; } if(message WM_INITDIALOG)//Use CreateDialogParam or DialogBoxParam to create a dialog, you can use this message to get pThis. { pThis (Window*)lParam; } if(pThis NULL) return DefWindowProc(hWnd, message, wParam, lParam); WNDPROC pWndProc (WNDPROC)pThis-m_pThunk; #if defined(_WIN64) ULONG_PTR rel (LONG_PTR)Window::StaticWndProc; pThis-m_OldWndProc SetWindowLongPtr(hWnd, GWLP_WNDPROC, (ULONG_PTR)pWndProc); #elif defined(_WIN32) ULONG_PTR rel (DWORD)Window::StaticWndProc - ((DWORD)pThis-m_pThunk sizeof(StdCallThunk)); pThis-m_OldWndProc SetWindowLong(hWnd, GWLP_WNDPROC, (ULONG)pWndProc); #endif pThis-m_pThunk-Init((ULONG_PTR)pThis, rel); //FlushInstructionCache(GetCurrentProcess(), pThis-m_pThunk, sizeof(StdCallThunk)); pThis-m_hWnd hWnd; return pWndProc(hWnd, message, wParam, lParam); } LRESULT CALLBACK Window::StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { return ((Window*)hWnd)-WndProc(message, wParam, lParam);//ret指令会弹出之前压入栈的返回地址并跳转到该地址。由于trunk中是直接JMP过来的并未压栈(与call指令不同)所以这里会直接跳到调用trunk的地方。 } int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { Window wnd; wnd.Create(); MSG msg; while (GetMessage(msg, NULL, 0, 0)) { TranslateMessage(msg); DispatchMessage(msg); } return (int)msg.wParam; }附录WTL 向导与VS2015集成方法① 下载最新的WTL_10_10320库(Windows Template Library (WTL) - Browse Files at SourceForge.net)② 解压得到WTL/AppWizard/在VS的安装目录下找到VCWizards/AppWiz, 在其中创建一个WTL文件夹将WTL/AppWizard目录下的所有文件拷贝到该新建的WTL文件夹下。③ 修改VCWizards/AppWiz/WTL/Files/HTML/1033/下面的三个文件AppType.htm, default.htm, UIFeatures.htm 修改位置 ① document.styleSheets(0).imports(0).href strURL; ① document.scripts(INCLUDE_SCRIPT).src strScriptPath; ② document.scripts(INCLUDE_COMMON).src strCommonPath; 修改结果 ① document.styleSheets(0).imports(0).href window.external.FindSymbol(SCRIPT_COMMON_PATH) /NewStyles.css; ② document.scripts(INCLUDE_SCRIPT).src window.external.FindSymbol(SCRIPT_COMMON_PATH) /Script.js; ③ document.scripts(INCLUDE_COMMON).src window.external.FindSymbol(SCRIPT_COMMON_PATH) /Common.js; default.htm 除了上述改动还需要将一个未封闭的html节点删除以保证语法正确(删除以下两行) !-- This closing tag is here only to avoid security warning -- /OBJECT④ 管理权限打开command命令窗口进入VCWizards/AppWiz/WTL目录下执行指令cscript Setup.js⑤ 重新启动visual studio, 找到visual c 类型分组可看到WTL工程模板就能利用WTL 向导创建WTL项目了。注1 如果在第⑤步没有看到WTL工程模板则表明安装有问题可能需要修改注册表或者修改VCWizards/AppWiz/WTL/Setup.js脚本请参考其他资料。注2如果在第⑤步WTL向导报错提示脚本执行错误类似setDirection未定义参数无效等信息此时可以修改VCWizards/AppWiz/WTL/Files/HTML/1033/下面三个文件的编码为UFT-8 BOM. 如果改编码还是报错检查VCWizards/AppWiz/WTL/Files/HTML/1033/下面三个文件的属性将它们的unblock勾选上再次尝试。