C++ x64的简单windows回调thunks
你们中的许多人都熟悉ATL thunks,例如用于创建窗口的ATL thunks。使该调用生效的类CStdCallThunk以该调用为目标。在本质上,它将全局回调转换成C++对象的成员函数。 这种类型的thunk不适用于需要第一个参数完整的。对于32位windows,我在库的一部分CAuxThunk找到了一个整洁的解决方案。不幸的是,这不适用于本机64位可执行文件 我想知道是否有任何x64程序集专家可以将这个CAuxThunk修补到64位windows上,或者想出任何等效的thunk,将this u stdcall回调变成一个成员函数C++ x64的简单windows回调thunks,c++,callback,x86-64,thunk,C++,Callback,X86 64,Thunk,你们中的许多人都熟悉ATL thunks,例如用于创建窗口的ATL thunks。使该调用生效的类CStdCallThunk以该调用为目标。在本质上,它将全局回调转换成C++对象的成员函数。 这种类型的thunk不适用于需要第一个参数完整的。对于32位windows,我在库的一部分CAuxThunk找到了一个整洁的解决方案。不幸的是,这不适用于本机64位可执行文件 我想知道是否有任何x64程序集专家可以将这个CAuxThunk修补到64位windows上,或者想出任何等效的thunk,将this
LRESULT CALLBACK CallWndProc(int nCode, WPARAM wParam, LPARAM lParam)
谢谢,nikos因为在64位模式下,所有类型(stdcall、cdecl和thiscall)的低级约定都是相同的,所以不需要汇编代码来实现这一点。只需创建一个调用适当成员函数的静态函数。您需要找出要使用的
指针,例如通过将lparam
中的hwnd
与对象关联。如果您只有一个回调,那么这当然没有问题。比如:
LRESULT CALLBACK static CallWndProc(int nCode, WPARAM wParam, LPARAM lParam)
{
Window w = GetWindowByHwnd(((CWPSTRUCT*)lParam)->hwnd);
return w->CallWndProc(nCode, wParam, lParam);
}
因为在64位模式下,所有类型(stdcall、cdecl和thiscall)的低级约定都是相同的,所以不需要汇编代码来实现这一点。只需创建一个调用适当成员函数的静态函数。您需要找出要使用的指针,例如通过将lparam
中的hwnd
与对象关联。如果您只有一个回调,那么这当然没有问题。比如:
LRESULT CALLBACK static CallWndProc(int nCode, WPARAM wParam, LPARAM lParam)
{
Window w = GetWindowByHwnd(((CWPSTRUCT*)lParam)->hwnd);
return w->CallWndProc(nCode, wParam, lParam);
}
更新见下文
请看一下SetWindowLongPtr
这允许您将指针与窗口相关联。鉴于WindowProc看起来像这样
LRESULT回调WindowProc(
_在HWND HWND,
_在uuint uMsg,
_在WPARAM WPARAM,
_In_lparamlparam
);代码>
您可以将hwnd参数与GetWindowLongPtr一起使用,以检索回调中的this指针
更新
看看
这似乎就是您要查找的内容,请参见下文
请看一下SetWindowLongPtr
这允许您将指针与窗口相关联。鉴于WindowProc看起来像这样
LRESULT回调WindowProc(
_在HWND HWND,
_在uuint uMsg,
_在WPARAM WPARAM,
_In_lparamlparam
);代码>
您可以将hwnd参数与GetWindowLongPtr一起使用,以检索回调中的this指针
更新
看看
这似乎就是您要寻找的我描述了一种生成所需thunk代码的通用方法。让我们为你的案子重做一遍,只是为了好玩
假设您的类定义为:
struct YourClass {
LRESULT YourMemberFunc(int nCode, WPARAM wParam, LPARAM lParam);
};
<>基本上,你在C++中用占位符写你的thunk,用于实际地址:
LRESULT CALLBACK CallWndProc(int nCode, WPARAM wParam, LPARAM lParam) {
YourClass *x = reinterpret_cast<YourClass*>(0x1122112211221122);
__int64 im = 0x3344334433443344;
LRESULT (YourClass::*m)(int,WPARAM,LPARAM) = *reinterpret_cast<LRESULT (YourClass::**)(int,WPARAM,LPARAM)>(&im);
return (x->*m)(nCode, wParam, lParam);
}
在发行版中编译并查看生成的程序集(在Visual Studio中,在调试期间查看程序集窗口并启用“显示代码字节”):
这将是您的thunk,在运行时,44 33 44 44 33
替换为指向您的成员的指针(&YourClass::yourmberfunc
),而22 11 22 11
替换为指向实际对象实例的指针
对thunk中发生的事情的解释:
在x64调用约定(Windows下只有一个)中,前四个参数按从左到右的顺序传递到rcx、rdx、r8、r9
寄存器中。所以当我们的恶棍被叫到的时候
rcx = nCode, rdx = wParam, r8 = lParam
对于成员函数,有一个隐式的第一个参数保存着这个
指针,因此当输入yourmberfunc
时,我们必须
rcx = this, rdx = nCode, r8 = wParam, r9 = lParam
编译器生成的代码正是这样做的:它移动r8->r9,rdx->r8,ecx->edx
,然后将占位符this=112211221221122
分配给rcx
。现在它已经设置了参数,并且可以继续间接跳转到函数本身rax
用于保存返回值,因此不必跨函数调用保留它。这就是为什么这里使用它来临时保存目标地址,这为尾部调用优化提供了机会(调用/返回对替换为单跳转)
为什么我们要打间接电话?因为否则我们会得到一个相对的跳跃。但是我们不能使用硬编码的相对跳转,因为thunk将被复制到内存中的不同地址!因此,我们求助于在运行时设置绝对地址,并执行间接跳转
HTH我描述了一种生成所需thunk代码的通用方法。让我们为你的案子重做一遍,只是为了好玩
假设您的类定义为:
struct YourClass {
LRESULT YourMemberFunc(int nCode, WPARAM wParam, LPARAM lParam);
};
<>基本上,你在C++中用占位符写你的thunk,用于实际地址:
LRESULT CALLBACK CallWndProc(int nCode, WPARAM wParam, LPARAM lParam) {
YourClass *x = reinterpret_cast<YourClass*>(0x1122112211221122);
__int64 im = 0x3344334433443344;
LRESULT (YourClass::*m)(int,WPARAM,LPARAM) = *reinterpret_cast<LRESULT (YourClass::**)(int,WPARAM,LPARAM)>(&im);
return (x->*m)(nCode, wParam, lParam);
}
在发行版中编译并查看生成的程序集(在Visual Studio中,在调试期间查看程序集窗口并启用“显示代码字节”):
这将是您的thunk,在运行时,44 33 44 44 33
替换为指向您的成员的指针(&YourClass::yourmberfunc
),而22 11 22 11
替换为指向实际对象实例的指针
对thunk中发生的事情的解释:
在x64调用约定(Windows下只有一个)中,前四个参数按从左到右的顺序传递到rcx、rdx、r8、r9
寄存器中。所以当我们的恶棍被叫到的时候
rcx = nCode, rdx = wParam, r8 = lParam
对于成员函数,有一个隐式的第一个参数保存着这个
指针,因此当输入yourmberfunc
时,我们必须
rcx = this, rdx = nCode, r8 = wParam, r9 = lParam
编译器生成的代码正是这样做的:它移位r8->r9,rdx->