Как передать метод в качестве обратного вызова для вызова Windows API (продолжение)? - PullRequest
4 голосов
/ 13 февраля 2012

Это сообщение является продолжением связанного вопроса , опубликованного здесь Ран .

принятый ответ придерживается использованияиз обычной простой старой функции.

Этот фрагмент особенно привлек мое внимание:

Метод экземпляра имеет дополнительный, неявный параметр, содержащий ссылку на экземпляр, то есть Self.

С твердым убеждением, что должен быть способ использовать своего рода адаптер «параметров» (перефразировать, избавиться от ненужной неявной ссылки Self и предоставить указатель на соответствующую адаптированную функцию обратного вызова), яв конечном итоге вы найдете эту статью , озаглавленную Обратный вызов класса от Питер Моррис .

Подводя итог, он использует технику тункинга в качестве уловки адаптации.(Отказ от ответственности: я никогда не проверял код).

Я знаю, что он не очень чистый как решение, но он позволяет проектирование ОО со всеми предполагаемыми преимуществами.

Мой вопрос:

Зная, что TCallbackThunk основан на сигнатуре функции обратного вызова, каков будет ответ из упомянутого выше поста, если делать это так, как это сделал Питер Моррис?

.

1 Ответ

3 голосов
/ 13 февраля 2012

На самом деле вам не нужно проходить всю эту работу, поскольку EnumWindows (функция в указанном вопросе) предоставляет параметр данных.Вы можете поместить туда любое значение, например, ссылку на объект, указанную в ответе.Техника Морриса лучше подходит для функций обратного вызова, которые не предоставляют какого-либо параметра данных общего назначения.

Чтобы адаптировать ответ для использования кода Морриса, сначала необходимо убедиться, что сигнатура метода обратного вызова совпадаетподпись функции обратного вызова API.Поскольку мы вызываем EnumWindows, нам нужна функция с двумя аргументами, возвращающая Bool.Соглашение о вызовах должно быть stdcall (потому что код Морриса предполагает это, и трудно согласовать любое другое соглашение о вызовах).

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.

...