使用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