x64的简单窗口回调thunk

时间:2013-10-28 08:48:28

标签: c++ callback x86-64 thunk

你们中的许多人都熟悉ATL thunk用于创建窗口。使这项工作CStdCallThunk的类正在针对WindowProc调用。在本构中,它将全局回调转换为C ++对象的成员函数。

这种类型的thunk不适用于需要第一个参数完整的SetWindowsHookEx callback。对于32位窗口,我在CAuxThunk中找到了一个整洁的解决方案,它是ATL/AUX库的一部分。不幸的是,这不适用于本机64位可执行文件

我想知道是否有任何x64程序集大师可以修补此CAuxThunk以适用于64位窗口,或者提出任何等效的thunk会将此__stdcall回调转换为成员函数?

LRESULT CALLBACK CallWndProc(int nCode, WPARAM wParam, LPARAM lParam)

谢谢,
尼科斯

3 个答案:

答案 0 :(得分:3)

我描述了一种在this answer中生成任何 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);
}

并以某种方式调用它以防止编译器内联调用:

int main() {
    LRESULT (CALLBACK *volatile fp)(int, WPARAM, LPARAM) = CallWndProc;
    fp(0, 0, 0);
}

在发行版中编译并查看生成的程序集(在Visual Studio中,在调试期间查看程序集窗口并打开“显示代码字节”):

4D 8B C8                       mov         r9,r8  
4C 8B C2                       mov         r8,rdx  
8B D1                          mov         edx,ecx  
48 B9 22 11 22 11 22 11 22 11  mov         rcx,1122112211221122h  
48 B8 44 33 44 33 44 33 44 33  mov         rax,3344334433443344h  
48 FF E0                       jmp         rax  

这将是你的thunk,44 33 44 33 44 33 44 33替换为指向你的成员的指针(&YourClass::YourMemberFunc)和22 11 22 11 22 11 22 11替换为指向实际对象实例的指针,在运行时时间。

解释thunk中发生了什么:

在x64调用约定中(Windows下只有一个),前四个参数按顺序从左到右传递到rcx, rdx, r8, r9寄存器中。所以当我们的thunk被调用时,我们有

rcx = nCode, rdx = wParam, r8 = lParam

对于成员函数,有一个隐含的第一个参数保持this指针,因此在输入YourMemberFunc时我们必须有

rcx = this, rdx = nCode, r8 = wParam, r9 = lParam

编译器生成的代码完全执行此调整:它会移动r8 -> r9, rdx -> r8, ecx -> edx,然后将占位符this = 1122112211221122分配给rcx。现在它已经设置了参数,并且可以继续间接跳转到函数本身。 rax用于保存返回值,因此不必跨函数调用保留它。这就是为什么它在这里暂时保存目标地址的原因,这为尾部调用优化提供了机会(一次调用替换了一个调用/返回对)。

为什么我们要进行间接通话?因为否则我们会得到相对的跳跃。但是我们不能使用硬编码的相对跳转,因为thunk将被复制到内存中的不同地址!因此,我们求助于在运行时设置绝对地址,而是进行间接跳转。

HTH

答案 1 :(得分:0)

因为在64位模式下,所有类型(stdcall,cdecl和thiscall)的低级约定都是相同的,所以你不需要汇编代码来实现这一点。只需创建一个调用相应成员函数的静态函数。您需要确定要使用的正确this指针,例如将hwnd中的lparam与对象相关联。如果你只有一个回调,那当然不会有问题。类似的东西:

LRESULT CALLBACK static CallWndProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    Window w = GetWindowByHwnd(((CWPSTRUCT*)lParam)->hwnd);
    return w->CallWndProc(nCode, wParam, lParam);
}

答案 2 :(得分:0)

已更新,见下文

看一下SetWindowLongPtr http://msdn.microsoft.com/en-us/library/windows/desktop/ms644898(v=vs.85).aspx

这允许您将指针与Window关联。鉴于WindowProc看起来像这样

LRESULT CALLBACK WindowProc( _In_ HWND hwnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam );

您可以使用带有GetWindowLongPtr的hwnd参数来检索回调中的this指针。

更新

查看How to thunk a function in x86 and x64? (Like std::bind in C++, but dynamic)

这似乎就是你要找的东西