如何将方法作为回调传递给 Windows API 调用(后续)?

发布于 2025-01-04 21:14:05 字数 915 浏览 1 评论 0原文

这篇文章是相关问题Ran发布。

接受的答案坚持使用通常的普通旧函数。

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

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

坚信应该有一种方法来使用一种“参数”适配器(重新措辞,摆脱不需要的 Self 隐式引用并提供指向兼容的适配回调函数的指针),我最终找到了这个 文章,标题为回调类 彼得·莫里斯

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

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

我的问题:

知道TCallbackThunk是基于回调函数签名的,如果像彼得莫里斯那样做的话,上面提到的帖子的答案是什么?

This post is a follow-up of a related question posted here by Ran.

The accepted answer sticks to the use of the usual a plain old function.

This excerpt particularly catch my attention:

An instance method has an extra, implicit, parameter containing the
instance reference, i.e. Self.

With the firm conviction that there should be a way to use a kind of "parameters" adapter (to rephrase get rid of the uneeded Self implicit reference and provide a pointer to a complying adapted callback function), I end up finding this article entitled Callback a class by Peter Morris.

To sum up, he uses thunking technique as adaptation trick. (Disclaimer: I never tested the code).

I know it's not very clean as a solution but it allows OO design with all the supposed benefits.

My Question:

Knowing that TCallbackThunk is based on the callback function signature, what would be the answer of the above refered post if doing it as Peter Morris did is the way to go?

.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

魔法唧唧 2025-01-11 21:14:05

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

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

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 指令是什么。

You don't really need to go through all that work since EnumWindows (the function in the referenced question) provides a data parameter. You can put whatever value you want there, such as the object reference demonstrated in the answer. Morris's technique is better suited for callback functions that don't provide any general-purpose data parameter.

To adapt the answer to use Morris's code, you'll first need to make sure the signature of the callback method matches the signature of the API's callback function. Since we're calling EnumWindows, we need a two-argument function returning Bool. The calling convention needs to be stdcall (because Morris's code assumes it, and it's difficult to thunk any other calling convention).

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

Next, we set up the TCallbackThunk data structure with all the machine code and the jump offset referring to the intended callback method.

However, we don't use the way Morris described. His code puts the data structure on the stack. That means we're putting executable code on the stack. Modern processors and operating systems don't allow that anymore — the OS will halt your program. We could get around that by calling VirtualProtect to modify the permissions of the current stack page, allowing it to be executed, but that makes the whole page executable, and we don't want to leave the program open for attack. Instead, we'll allocate a block of memory especially for the thunk record, separate from the stack.

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;

Note that those are 32-bit x86 instructions in that record. I have no idea what the corresponding x86_64 instructions would be.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文