4

这篇文章是Ran在此处发布的相关问题的后续。

接受的答案坚持使用通常的普通旧功能。

这段摘录特别引起了我的注意:

一个实例方法有一个额外的、隐含的、包含实例引用的参数,即Self。

坚信应该有一种方法可以使用一种“参数”适配器(改写为摆脱不需要的 Self 隐式引用并提供指向符合要求的回调函数的指针),我最终找到了这篇题为Callback的文章彼得莫里斯一堂课

综上所述,他使用thunking技术作为适应技巧。(免责声明:我从未测试过代码)。

我知道它作为一种解决方案不是很干净,但它允许 OO 设计具有所有假定的好处。

我的问题:

知道TCallbackThunk是基于回调函数签名的,如果像 Peter Morris 那样做是要走的路,上面提到的帖子的答案是什么?

.

4

1 回答 1

3

您实际上并不需要完成所有这些工作,因为EnumWindows(引用问题中的函数)提供了一个数据参数。您可以在那里放置您想要的任何值,例如答案中演示的对象引用。Morris 的技术更适合不提供任何通用数据参数的回调函数。

要调整答案以使用 Morris 的代码,您首先需要确保回调方法的签名与 API 回调函数的签名相匹配。由于我们正在调用EnumWindows,我们需要一个返回 Bool 的两个参数的函数。调用约定必须是 stdcall (因为 Morris 的代码假定它,并且很难对任何其他调用约定进行 thunk)。

function TAutoClickOKThread.cbEnumWindowsClickOK(
  Wnd: HWnd; Param: LParam): Bool; stdcall;
begin
  // ...
end;

接下来,我们使用所有机器代码和跳转偏移量设置TCallbackThunk数据结构,参考预期的回调方法。

然而,我们不使用莫里斯描述的方式。他的代码将数据结构放在堆栈上。这意味着我们将可执行代码放在堆栈上。现代处理器和操作系统不再允许这样做——操作系统会停止你的程序。我们可以通过调用VirtualProtect修改当前堆栈页面的权限来解决这个问题,允许它被执行,但这会使整个页面可执行,并且我们不想让程序受到攻击。相反,我们将分配一块内存,专门用于 thunk 记录,与堆栈分开。

procedure TAutoClickOKThread.Execute;
var
  Callback: PCallbackThunk;
begin
  Callback := VirtualAlloc(nil, SizeOf(Callback^),
    Mem_Commit, Page_Execute_ReadWrite);
  try
    Callback.POPEDX := $5A;
    Callback.MOVEAX := $B8;
    Callback.SelfPtr := Self;
    Callback.PUSHEAX := $50;
    Callback.PUSHEDX := $52;
    Callback.JMP := $E9;
    Callback.JmpOffset := Integer(@TAutoClickOKThread.cbEnumWindowsClickOK)
      - Integer(@Callback.JMP) - 5;

    EnumWindows(Callback, 0);
  finally
    VirtualFree(Callback);
  end;
end;

请注意,这些是该记录中的 32 位 x86 指令。我不知道相应的 x86_64 指令是什么。

于 2012-02-13T15:56:19.233 回答