使用thunks (x32和x64)的c++ WinAPI包裝器物件
介紹 本文概述了一種稱為“thunking”的技術,即在c++物件中例項化WinAPI的方法。雖然有各種方法可以實現這種實現,但本文描述了一種攔截WndProc呼叫並將this指標附加到函式呼叫的第五個引數的方法。它使用一個thunk和一個函式呼叫,並且有x32和x64的程式碼。 背景 WinAPI實現是在c++和OOP流行之前引入的。已經進行了諸如ATL之類的嘗試,使它更加面向類和物件。主要的障礙是訊息處理過程(通常稱為WndProc)不是作為應用程式的一部分呼叫的,而是由Windows本身從外部呼叫的。這要求函式是全域性的,在c++成員函式的情況下,它必須宣告為靜態的。結果是,當應用程式進入WndProc時,它沒有指向物件的特定例項的指標,可以呼叫任何其他處理程式函式。 這意味著任何c++ OOP方法都必須從靜態成員函式中確定訊息處理應該傳遞到哪個物件方法。 一些選項包括: 僅為單個視窗例項設計。訊息處理器可以是全域性的,也可以是名稱空間範圍的。可以使用cbClsExtra或cbWndExtra提供的額外記憶體位來儲存指向正確物件的指標。在視窗中新增一個指向物件的屬性,並使用GetProp來檢索它。維護一個引用物件指標的查詢表。使用一種被稱為“thunk”的方法。 每一種都有好的一面和壞的一面。 您只能使用單個視窗,並且程式碼不能重用。對於簡單的應用程式來說,這可能很好,但是如果要在物件中進行封裝,您最好放棄它,而堅持使用標準模板。這種方法“很慢”,並且每次有訊息經過時,都需要開銷來呼叫從額外的位中獲取指標。此外,它降低了程式碼的可重用性,因為它依賴於這些值在視窗的生命週期中不會被重寫或用於其他目的。另一方面,它是一個簡單明瞭的實現。比第2項慢,並且引入了類似的開銷,但是消除了資料被覆蓋的可能性(儘管您需要確保屬性有一個惟一的名稱,以便它不會與任何其他新增的屬性衝突)。在這裡,隨著查詢表的增長,我們會遇到效能和開銷問題,每次呼叫訊息處理器函式時都需要進行查詢。它允許將函式作為私有靜態成員。這有點難以實現,但是相對於其他方法提供了較低的開銷和更好的效能,並允許增強靈活性,適合於任何OOP設計風格。 事實是,很多應用程式真的不需要任何花哨的東西,可以使用更傳統的方法。然而,如果您想要構建一個低開銷的可擴充套件框架,那麼方法5提供了最佳選擇,本文將概述如何實際實現這樣的設計。 使用的程式碼 thunk是位於記憶體中的一段執行程式碼。它有可能在執行時更改執行程式碼。其思想是將一小段程式碼放入記憶體中,然後讓它在其他地方執行和修改正在執行的程式碼。出於我們的目的,我們希望捕獲訊息處理成員函式的可執行地址,並用初始註冊的函式替換它,並用該函式對物件地址進行編碼,以便它能夠正確地呼叫訊息處理佇列中的下一個正確的非靜態成員函式。 首先,讓我們為這個專案建立模板。我們需要一個包含wWinMain函式的主檔案。 隱藏,複製Code
// appmain.cpp : Defines the entry point for the application. // #include "stdafx.h" #include "AppWin.h" int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) { UNREFERENCED_PARAMETER(hPrevInstance); UNREFERENCED_PARAMETER(lpCmdLine); }
現在我們的AppWin.h和AppWin.cpp檔案並建立一個空的類結構。 隱藏,複製Code
// AppWin.h : header file for the AppWinClass // #pragma once #include "resource.h" class AppWinClass { public: AppWinClass(){} ~AppWinClass(){} private: }; // AppWin.cpp : implementation of AppWinClass // #include "stdafx.h" #include "AppWin.h"AppWinClass::AppWinClass() {} AppWinClass::~AppWinClass() {}
我們需要用建立視窗所需的所有必要元素來設定物件。第一個元素註冊了WNDCLASSEX結構。WNDCLASSEX中的一些元素應該允許通過例項化物件的程式碼來更改,但有些欄位我們希望保留給物件來控制。 這裡的一個選項是用我們允許使用者定義自己的元素定義一個“結構”,然後將其傳遞給一個函式,以複製到將被註冊的WNDCLASSEX結構中,或者將元素作為函式呼叫的一部分傳遞。如果我們使用“結構體”,資料元素可以在其他地方重用。當然,一個結構會佔用記憶體,如果我們只使用元素一次,那麼效率不是很高。您可以簡單地將元素作為函式呼叫的一部分傳遞,並將作用域縮小為該函式和更有效率。但是我們需要傳遞至少20引數,然後執行檢查每個他們的價值。 在這裡,我們將在我們的宣告預設值建立函式,然後宣佈我們班外的“結構”,如果使用者想要調整違約,他們可以和他們可以管理的生命週期結構。使用者只是宣告函式是否通過結構和更新預設值或違約。所以,我們宣告以下功能: 隱藏,複製Code
int AppWinClass::Create(HINSTANCE hInstance, int nCmdShow, AppWinStruct* varStruct)
例項控制代碼是用來在整個建立過程和nCmdShow傳遞的一部分顯示視窗的電話。 所以我們開始函式通過檢查如果我們收到AppWinStruct如果沒有,我們負載WNCLASSEX結構與違約,否則我們接受AppWinStruct提供。 隱藏,收縮,複製Code
int AppWinClass::Create(HINSTANCE hInstance, int nCmdShow = NULL, AppWinStruct* varStruct = nullptr) { WNDCLASSEX wcex; //initialize our WNDCLASSEX wcex.cbSize = sizeof(WNDCLASSEX); wcex.hInstance = hInstance; wcex.lpfnWndProc = ; if (!varStruct) //default values { varStruct = new AppWinStruct; wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hIcon = LoadIcon(nullptr, IDI_APPLICATION); wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wcex.lpszMenuName = nullptr; wcex.lpszClassName = L"Window"; wcex.hIconSm = NULL; } else { //user defined wcex.style = varStruct->style; wcex.cbClsExtra = varStruct->cbClsExtra; wcex.cbWndExtra = varStruct->cbWndExtra; wcex.hIcon = varStruct->hIcon; wcex.hCursor = varStruct->hCursor; wcex.hbrBackground = varStruct->hbrBackground; wcex.lpszMenuName = varStruct->lpszMenuName; wcex.lpszClassName = varStruct->lpszClassName; wcex.hIconSm = varStruct->hIconSm; }
注意,我們是我們申報wcex.lpfnWndProc失蹤。這個變數將註冊我們的訊息處理函式。因為設定,該函式必須是靜態的,因此將無法呼叫物件的特定功能來處理訊息處理特定的訊息。一個典型的指向函式頭看起來像這樣: 隱藏,複製Code
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
最終,我們將使用一個鐺基本上超載5引數的函式呼叫我們將插入一個指向objext是這個。在我們這麼做之前,我們宣告指向函式。這只是一個標準的指向函式提供油漆的處理和銷燬訊息——就足以讓一個視窗。 隱藏,收縮,複製Code
// AppWin.h class AppWinClass { . . . private: static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, DWORD_PTR pThis); //AppWin.cpp LRESULT CALLBACK AppWinClass::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, DWORD_PTR pThis) { switch (message) { case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, &ps); // TODO: Add any drawing code that uses hdc here... EndPaint(hWnd, &ps); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; }
在這裡,我們已經宣佈我們這個指標引數,包括五分之一。Windows將稱之為四個標準傳遞的引數。所以我們需要中斷呼叫堆疊上的函式呼叫和地點5引數,將類物件的指標。這是鐺的由來。 又一聲有點堆上的可執行程式碼。而不是呼叫視窗訊息過程,我們將呼叫鐺就好像它是一個函式。函式變數是推到堆疊所有鐺鐺打電話前需要做的是將一個變數新增到堆疊,然後跳轉到原目標函式。 兩個筆記。因為DEP(資料執行預防),我們必須分配一些堆標記為可執行這個過程。否則,DEP將防止程式碼執行和丟擲異常。我們使用HeapCreate HEAP_CREATE_ENABLE_EXECUTE設定。HeapCreate將至少保留一個4 k頁面的記憶體和鐺很小。因為我們不想建立一個新頁面為每個新鐺每一個物件的例項,我們將宣告一個變數來儲存堆處理堆可以重用。 隱藏,複製Code
// AppWin.h class AppWinClass { . . . private: static HANDLE eheapaddr; static int objInstances;
我們將使用我們的類建構函式來建立堆。 隱藏,複製Code
//AppWin.cpp HANDLE AppWinClass::eheapaddr = NULL; int AppWinClass::objInstances = 0; AppWinClass::AppWinClass() { objInstances = ++objInstances; if (!eheapaddr) { try { eheapaddr = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE | HEAP_GENERATE_EXCEPTIONS, 0, 0); } catch (...) { throw; } } try { thunk = new(eheapaddr) winThunk; } catch (...) { throw; } }
我們初始化靜態eheapaddr(可執行堆地址)和objInstances(我們的標記數我們物件的例項的數量)為0。在建構函式中,我們首先增加objInstances。我們不想破壞我們堆到所有其他的例項物件都消失了。現在,我們檢查eheapaddr是否已經初始化,如果沒有,我們給它HeapCreate返回的控制代碼的值。我們稱之為HeapCreate並指定,我們想使這堆上執行的程式碼,我們需要生成異常如果分配失敗。我們然後封裝在一個try catch語句,該語句將重新丟擲的異常HeapCreate並允許物件的呼叫圖。 我們也會在堆上分配我們的鐺。我們也會想要覆蓋的新運營商鐺類,以便它可以被分配到我們從HeapCreate堆,我們可以通過處理。我們也會把它變成一個try catch語句,以防alloc失敗(因為我們為HeapCreate HEAP_GENERATE_EXCEPTIONS HeapAlloc也會產生例外)。 我們將摧毀這堆當物件被刪除,因此我們將更新後的解構函式: 隱藏,複製Code
//AppWin.cpp AppWinClass::~AppWinClass() { if (objInstances == 1) { HeapDestroy(eheapaddr); eheapaddr = NULL; objInstances = 0; } else { objInstances = --objInstances; delete thunk; } }
簡單地檢查如果我們過去的物件例項化,如果是這樣,破壞堆和eheapaddr重置為零。否則遞減objInstances。注意:eheapaddr和obInstances不需要設定為0作為我們整個物件即將消失。我們需要呼叫delete操作符鐺確保它釋放自己從我們的堆。 注意:InterlockedInstances()可以被用來提供一個更好的遞增和遞減一個多執行緒的方法而不是靜態的計數器。 現在我們可以宣告鐺類。因為x32和x64是不同的在他們如何處理堆疊和函式呼叫,我們需要包裝宣言#如果定義語句。我們使用_M_IX86 x32一點一點應用和_M_AMD64 x64的應用。 我們的想法是我們建立一個結構,並將變數在一個特定的順序。當我們打個電話”功能,我們轉而呼叫結構的頂部記憶體,並將開始執行儲存在頂部變數中的程式碼。 我們使用#pragma pack(push,#)宣告來正確對齊位元組以便執行,否則編譯器可能會填充變數(無論如何使用x64集)。 對於x32,我們需要7個變數。然後,我們為它們分配等效於x86彙編程式碼的十六進位制程式碼。大會看起來如下: 隱藏,複製Code
push dword ptr [esp] ;push return address mov dword ptr [esp+0x4], pThis ;esp+0x4 is the location of the first element in the function header ;and pThis is the value of the pointer to our object’s "this" jmp WndProc ;where WndProc is the message processing function
因為在程式執行之前我們不知道pThis或WndProc的值,所以我們需要在執行時收集它們。所以我們在結構中建立一個函式來初始化這些變數,我們將傳遞訊息處理函式和pThis的位置。 我們還需要重新整理指令快取,以確保我們的新程式碼可用,並且指令快取不會嘗試執行舊程式碼。如果重新整理成功(返回0),我們返回真,否則我們返回假,讓程式知道我們有問題。 關於我們的32位程式碼的一些說明。按照呼叫約定,我們需要為呼叫函式保留堆疊框架(記住,它正在呼叫一個它認為有4個變數的函式)。呼叫函式的返回地址位於堆疊的底部。所以我們遵從esp(它指向我們的返回地址)和push (push [esp])遞減esp,新增一個儲存返回地址的新“層”,從而為第五個變數騰出空間。現在,我們將物件指標值+4位元組移動到堆疊上(覆蓋返回值的原始位置),它將成為函式呼叫中的第一個值(從概念上講,我們將函式引數推到右邊)。在Init中m_mov被給予相當於mov dword ptr [esp+0x4]的十六進位制。然後我們將pThis的值賦給m_this來完成mov指令。m_jmp獲得相當於jmp操作碼的十六進位制。現在我們做一些計算,以找到我們需要跳轉到的地址,並將其分配給m_relproc(相對位置,我們的過程)。 我們還需要重寫struct的new和delete,以便在可執行堆上正確地分配物件。 還要注意的是,因特爾使用“小尾端”格式,因此指令位元組必須顛倒(高階位元組優先)[也適用於x64]。 隱藏,收縮,複製Code
// AppWin.h #if defined(_M_IX86) #pragma pack(push,1) struct winThunk { unsigned short m_push1; //push dword ptr [esp] ;push return address unsigned short m_push2; unsigned short m_mov1; //mov dword ptr [esp+0x4], pThis ;set our new parameter by replacing old return address unsigned char m_mov2; //(esp+0x4 is first parameter) unsigned long m_this; //ptr to our object unsigned char m_jmp; //jmp WndProc unsigned long m_relproc; //relative jmp static HANDLE eheapaddr; //heap address this thunk will be initialized to bool Init(void* pThis, DWORD_PTR proc) { m_push1 = 0x34ff; //ff 34 24 push DWORD PTR [esp] m_push2 = 0xc724; m_mov1 = 0x2444; // c7 44 24 04 mov dword ptr [esp+0x4], m_mov2 = 0x04; m_this = PtrToUlong(pThis); m_jmp = 0xe9; //jmp //calculate relative address of proc to jump to m_relproc = unsigned long((INT_PTR)proc - ((INT_PTR)this + sizeof(winThunk))); // write block from data cache and flush from instruction cache if (FlushInstructionCache(GetCurrentProcess(), this, sizeof(winThunk))) { //succeeded return true; } else {//error return false; } } //some thunks will dynamically allocate the memory for the code WNDPROC GetThunkAddress() { return (WNDPROC)this; } void* operator new(size_t, HANDLE heapaddr) { eheapaddr = heapaddr; //since we can't pass a value with delete operator, we need to store //our heap address so we can use it later when we need to free this thunk return HeapAlloc(heapaddr, 0, sizeof(winThunk)); } void operator delete(void* pThunk) { HeapFree(eheapaddr, 0, pThunk); } }; #pragma pack(pop)
x64版本遵循同樣的原則,但是我們需要考慮x64在處理堆疊方面的一些差異,並彌補一些對齊問題。Windows x64 ABI使用以下範例為函式呼叫推入變數(注意,它沒有推入或彈出——它類似於fastcall)。第一個引數被移動到rcx。第二個引數被移動到rdx。第三個引數被移動到r8。第四個引數被移動到r9。下面的引數被推入堆疊,但是有一個技巧。ABI在堆疊上為儲存這4個引數保留空間(稱為陰影空間)。因此,在堆疊的頂部保留了4個8位元組的空間。堆疊的頂部還有返回地址。因此第五個引數被放在堆疊上rsp+28的位置。 隱藏,複製Code
--- Bottom of stack --- RSP + size (higher addresses) arg N arg N - 1 arg N - 2 ... arg 6 arg 5 [rsp+28h] (shadow space for arg 4) [rsp+20h] (shadow space for arg 3) [rsp+18h] (shadow space for arg 2) [rsp+10h] (shadow space for arg 1) [rsp+8h] (return address) [rsp] ---- Top of stack ----- RSP (lower addresses)
對於非靜態函式呼叫,它對前5個引數執行以下操作。它將其推到rcx,然後推到edx(第一個引數),然後推到r8(第二個引數),然後推到r9(第三個引數),然後推到rsp+0x28(第四個引數),然後推到rsp+0x30(第五個引數)。對於非靜態引數,第一個引數是rcx,然後是rdx(第二個引數),然後是r8(第三個引數),然後是r9(第四個引數),然後是rsp+0x28(第五個引數)。所以我們需要把值放在rsp+0x28。 我們遇到的一個問題是,其中一條指令集(mov [esp+28], rax)是一條5位元組的指令,編譯器試圖在1、2、4、8、16位元組邊界上對齊所有指令。所以我們需要手動對齊。這需要新增一個no operation (nop)[90]命令。否則,同樣的原則也適用。注意,因為pThis和proc的地址佔用64位變數,所以我們需要使用movabs運算元來使用rax。 隱藏,收縮,複製Code
#elif defined(_M_AMD64) #pragma pack(push,2) struct winThunk { unsigned short RaxMov; //movabs rax, pThis unsigned long long RaxImm; unsigned long RspMov; //mov [rsp+28], rax unsigned short RspMov1; unsigned short Rax2Mov; //movabs rax, proc unsigned long long ProcImm; unsigned short RaxJmp; //jmp rax static HANDLE eheapaddr; //heap address this thunk will be initialized too bool Init(void *pThis, DWORD_PTR proc) { RaxMov = 0xb848; //movabs rax (48 B8), pThis RaxImm = (unsigned long long)pThis; // RspMov = 0x24448948; //mov qword ptr [rsp+28h], rax (48 89 44 24 28) RspMov1 = 0x9028; //to properly byte align the instruction we add a nop (no operation) (90) Rax2Mov = 0xb848; //movabs rax (48 B8), proc ProcImm = (unsigned long long)proc; RaxJmp = 0xe0ff; //jmp rax (FF EO) if (FlushInstructionCache(GetCurrentProcess(), this, sizeof(winThunk))) { //error return FALSE; } else {//succeeded return TRUE; } } //some thunks will dynamically allocate the memory for the code WNDPROC GetThunkAddress() { return (WNDPROC)this; } void* operator new(size_t, HANDLE heapaddr) { eheapaddr = heapaddr; //since we can't pass a value with delete operator we need to store //our heap address so we can use it later when we need to free this thunk return HeapAlloc(heapaddr, 0, sizeof(winThunk)); } void operator delete(void* pThunk) { HeapFree(eheapaddr, 0, pThunk); } }; #pragma pack(pop) #endif
現在我們有了訊息處理程式和thunk。現在可以將值賦給lpfnWndProc。 注意——我們使用兩個不同的呼叫引數,一個用於32位,一個用於64位。在我們的32位程式碼中,我們的指標是第一個引數。在我們的64位程式碼中,它是第五個引數。我們需要用一些編譯器指令包裝程式碼來解決這個問題。 AppWin.h 隱藏,複製Code
#if defined(_M_IX86) static LRESULT CALLBACK WndProc(DWORD_PTR, HWND, UINT, WPARAM, LPARAM); #elif defined(_M_AMD64) static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM, DWORD_PTR); #endif
AppWin.cpp 隱藏,複製Code
#if defined(_M_IX86) LRESULT CALLBACK AppWinClass::WndProc(DWORD_PTR This, HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) #elif defined(_M_AMD64) LRESULT CALLBACK AppWinClass::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, DWORD_PTR This) #endif
但是lpfnWndProc將引用我們的thunk,而不是我們的訊息處理器功能。因此,我們用適當的值初始化thunk。 隱藏,複製Code
//AppWin.cpp int AppWinClass::Create(HINSTANCE hInstance, int nCmdShow, AppWinStruct* varStruct) { thunk->Init(this, (DWORD_PTR)WndProc); //init our thunk
一些笨蛋可能會動態分配他們的記憶體,所以我們使用GetThunkAddress函式,它只是返回笨蛋的確定這個指標。我們使用WNDPROC對呼叫進行強制轉換,因為這是我們的windows類所期望的。 現在我們regi完成我們的WNDCLASSEX結構。我們將宣告一個公共變數classatom來儲存RegisterClassEx的返回值,以便將來使用。我們呼叫RegisterClassEx。 現在我們呼叫CreateWindowEx來傳遞變數。如果設定了WS_VISIBLE位,那麼我們不需要呼叫ShowWindow,因此我們檢查它。我們執行一個UpdateWindow,然後進入訊息迴圈。做完了。 *一個額外的注意。我在WndProc宣告中使用DWORD_PTR This。在我看來,這是一個更好的幫助,以幫助證明這一原則。但是,為了避免無用的轉換,將其宣告為AppWinClass This。 AppWin.h 隱藏,收縮,複製Code
// AppWin.h : header file for the AppWinClass // #pragma once #include "resource.h" #if defined(_M_IX86) #pragma pack(push,1) struct winThunk { unsigned short m_push1; //push dword ptr [esp] ;push return address unsigned short m_push2; unsigned short m_mov1; //mov dword ptr [esp+0x4], pThis ;set our new parameter by replacing old return address unsigned char m_mov2; //(esp+0x4 is first parameter) unsigned long m_this; //ptr to our object unsigned char m_jmp; //jmp WndProc unsigned long m_relproc; //relative jmp static HANDLE eheapaddr; //heap address this thunk will be initialized to bool Init(void* pThis, DWORD_PTR proc) { m_push1 = 0x34ff; //ff 34 24 push DWORD PTR [esp] m_push2 = 0xc724; m_mov1 = 0x2444; // c7 44 24 04 mov dword ptr [esp+0x4], m_mov2 = 0x04; m_this = PtrToUlong(pThis); m_jmp = 0xe9; //jmp //calculate relative address of proc to jump to m_relproc = unsigned long((INT_PTR)proc - ((INT_PTR)this + sizeof(winThunk))); // write block from data cache and flush from instruction cache if (FlushInstructionCache(GetCurrentProcess(), this, sizeof(winThunk))) { //succeeded return TRUE; } else { //error return FALSE; } } //some thunks will dynamically allocate the memory for the code WNDPROC GetThunkAddress() { return (WNDPROC)this; } void* operator new(size_t, HANDLE heapaddr) { eheapaddr = heapaddr; //since we can't pass a value with delete operator we need to store //our heap address so we can use it later when we need to free this thunk return HeapAlloc(heapaddr, 0, sizeof(winThunk)); } void operator delete(void* pThunk) { HeapFree(eheapaddr, 0, pThunk); } }; #pragma pack(pop) #elif defined(_M_AMD64) #pragma pack(push,2) struct winThunk { unsigned short RaxMov; //movabs rax, pThis unsigned long long RaxImm; unsigned long RspMov; //mov [rsp+28], rax unsigned short RspMov1; unsigned short Rax2Mov; //movabs rax, proc unsigned long long ProcImm; unsigned short RaxJmp; //jmp rax static HANDLE eheapaddr; //heap address this thunk will be initialized too bool Init(void *pThis, DWORD_PTR proc) { RaxMov = 0xb848; //movabs rax (48 B8), pThis RaxImm = (unsigned long long)pThis; // RspMov = 0x24448948; //mov qword ptr [rsp+28h], rax (48 89 44 24 28) RspMov1 = 0x9028; //to properly byte align the instruction we add a nop (no operation) (90) Rax2Mov = 0xb848; //movabs rax (48 B8), proc ProcImm = (unsigned long long)proc; RaxJmp = 0xe0ff; //jmp rax (FF EO) if (FlushInstructionCache(GetCurrentProcess(), this, sizeof(winThunk))) { //error return FALSE; } else {//succeeded return TRUE; } } //some thunks will dynamically allocate the memory for the code WNDPROC GetThunkAddress() { return (WNDPROC)this; } void* operator new(size_t, HANDLE heapaddr) { return HeapAlloc(heapaddr, 0, sizeof(winThunk)); } void operator delete(void* pThunk, HANDLE heapaddr) { HeapFree(heapaddr, 0, pThunk); } }; #pragma pack(pop) #endif struct AppWinStruct { //structure to hold variables used to instantiate the window LPCTSTR lpszClassName = L"Window"; LPCTSTR lpClassName = L"Window"; LPCTSTR lpWindowName = L"Window"; DWORD dwExStyle = WS_EX_OVERLAPPEDWINDOW; DWORD dwStyle = WS_OVERLAPPEDWINDOW | WS_VISIBLE; UINT style = CS_HREDRAW | CS_VREDRAW; int cbClsExtra = 0; int cbWndExtra = 0; HICON hIcon = LoadIcon(nullptr, IDI_APPLICATION); HCURSOR hCursor = LoadCursor(nullptr, IDC_ARROW); HBRUSH hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); LPCTSTR lpszMenuName = nullptr; HICON hIconSm = NULL; int xpos = CW_USEDEFAULT; int ypos = CW_USEDEFAULT; int nWidth = CW_USEDEFAULT; int nHeight = CW_USEDEFAULT; HWND hWndParent = NULL; HMENU hMenu = NULL; LPVOID lpParam = NULL; }; class AppWinClass { public: ATOM classatom = NULL; AppWinClass(); //constructor ~AppWinClass(); //descructor int Create(HINSTANCE, int, AppWinStruct*); int GetMsg(HINSTANCE); private: static HANDLE eheapaddr; static int objInstances; winThunk* thunk; #if defined(_M_IX86) static LRESULT CALLBACK WndProc(DWORD_PTR, HWND, UINT, WPARAM, LPARAM); #elif defined(_M_AMD64) static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM, DWORD_PTR); #endif };
AppWin.cpp 隱藏,收縮,複製Code
// AppWin.cpp : implementation of AppWinClass // #include "stdafx.h" #include "AppWin.h" HANDLE AppWinClass::eheapaddr = NULL; int AppWinClass::objInstances = 0; AppWinClass::AppWinClass() { objInstances = ++objInstances; if (!eheapaddr) { try { eheapaddr = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE | HEAP_GENERATE_EXCEPTIONS, 0, 0); } catch (...) { throw; } } try { thunk = new(eheapaddr) winThunk; } catch (...) { throw; } } AppWinClass::~AppWinClass() { if (objInstances == 1) { HeapDestroy(eheapaddr); eheapaddr = NULL; objInstances = 0; } else { objInstances = --objInstances; } } int AppWinClass::Create(HINSTANCE hInstance, int nCmdShow, AppWinStruct* varStruct) { HWND hWnd = NULL; DWORD showwin = NULL; thunk->Init(this, (DWORD_PTR)WndProc); //init our thunk WNDPROC pProc = thunk->GetThunkAddress(); //get our thunk's address //and assign it pProc (pointer to process) WNDCLASSEX wcex; //initialize our WNDCLASSEX wcex.cbSize = sizeof(WNDCLASSEX); wcex.hInstance = hInstance; wcex.lpfnWndProc = pProc; //our thunk if (!varStruct) //default values { varStruct = new AppWinStruct; wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hIcon = LoadIcon(nullptr, IDI_APPLICATION); wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wcex.lpszMenuName = nullptr; wcex.lpszClassName = L"Window"; wcex.hIconSm = NULL; //register wcex classatom = RegisterClassEx(&wcex); //create our window hWnd = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW, L"Window", L"Window", WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, hInstance, nullptr); showwin = WS_VISIBLE; //we set WS_VISIBLE so we do not need to call ShowWindow } else { //user defined wcex.style = varStruct->style; wcex.cbClsExtra = varStruct->cbClsExtra; wcex.cbWndExtra = varStruct->cbWndExtra; wcex.hIcon = varStruct->hIcon; wcex.hCursor = varStruct->hCursor; wcex.hbrBackground = varStruct->hbrBackground; wcex.lpszMenuName = varStruct->lpszMenuName; wcex.lpszClassName = varStruct->lpszClassName; wcex.hIconSm = varStruct->hIconSm; //register wcex classatom = RegisterClassEx(&wcex); //create our window hWnd = CreateWindowEx(varStruct->dwExStyle, varStruct->lpClassName, varStruct->lpWindowName, varStruct->dwStyle, varStruct->xpos, varStruct->ypos, varStruct->nWidth, varStruct->nHeight, varStruct->hWndParent, varStruct->hMenu, hInstance, varStruct->lpParam); showwin = (varStruct->dwStyle & (WS_VISIBLE)); //check if the WS_VISIBLE bit was set } if (!hWnd) { return FALSE; } //check if the WS_VISIBLE style bit was set and if so we don't need to call ShowWindow if (showwin != WS_VISIBLE) { ShowWindow(hWnd, nCmdShow); } UpdateWindow(hWnd); return 0; } #if defined(_M_IX86) LRESULT CALLBACK AppWinClass::WndProc (DWORD_PTR This, HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) #elif defined(_M_AMD64) LRESULT CALLBACK AppWinClass::WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, DWORD_PTR This) #endif { AppWinClass* pThis = (AppWinClass*)This; switch (message) { case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, &ps); // TODO: Add any drawing code that uses hdc here... EndPaint(hWnd, &ps); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } int AppWinClass::GetMsg(HINSTANCE hInstance) { HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_APPWIN)); MSG msg; // Main message loop: while (GetMessage(&msg, nullptr, 0, 0)) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return (int)msg.wParam; }
歷史 版本1.1 修正了在物件的最後一個例項上沒有將objInstances設定回零的錯誤。新增兩個音符。 版本1.5 修改了thunk的32位約定,以正確地保留棧,就像pVerer在註釋中建議的那樣——這導致需要用一些#if定義的約定包裝WndProc函式,現在32位和64位程式碼用不同的方式呼叫這個函式。 版本1.6 對thunk的delete操作符做了一些小修改 版本1.8 將x64 ABI部分更新為更正確和更清晰地討論約定。它的描述有一個錯誤。還更新了x64 thunk程式碼,並使用movabs運算元簡化了64位直接操作。 1.8.1版本 使用Microsoft typedefs的USHORT、ULONG etc修改為c++格式(例如,USHORT變成了unsigned short),否則在由vs2017 RC編譯後,程式碼將無法正常執行 本文轉載於:http://www.diyabc.com/frontweb/news4939.html