Pergunta

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?

.

Foi útil?

Solução

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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top