Функция окна windows приложения называется функцией обратного вызова потому что

Функции обратного вызова

Другое очень важное понятие в Windows-программировании — это понятие функции обратного вызова (callback-функции). Функцией обратного вызова называется функция приложения, которая никогда не вызывается напрямую другими функциями или процедурами этого приложения (хотя ничто не за­прещает это делать), а вызывается операционной системой Windows. Это по­зволяет Windows общаться с приложением напрямую посредством различных параметров, определенных как функции обратного вызова. К этим функциям выдвигаются требования: во-первых, эти функции должны быть именно функциями, а не методами класса (хотя это иногда можно обойти); во-вторых, эти функции должны быть написаны в соответствии с моделью вызова stdcall.

В качестве примера такой функции может послужить функция EnumWindows. В справочной системе она описана следующим образом:

BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam);

В Windows.pas она имеет вид:

function EnumWindows (lpEnumFunc: TFNWndEnumProc; lParam: LPARAM : BOOL;

stdcall

В качестве первого параметра должен быть указатель на функцию обратно­го вызова. Синтаксис прототипа этой функции описан так (поскольку это только прототип, то реальное имя может быть любым):

BOOL CALLBACK EnumWindowsProc (HWND hwnd LPARAM lParam);

Любым может быть и тип самой функции тип второго параметра, который разработчик может использовать по своему усмотрению, лишь бы его длина не превышала 32 бит.

Пример функции обратного вызова для случая, когда второй параметр имеет тип Longint, будет в Delphi выглядеть так:

function MyCallbackFunction (Wnd: Hwnd; P: Longint) :Bool; stdcall; begin

{ что-то делается } end;

var

MyVar: Longint;

EnumWindows(@MyCallbackFunction, Longint(MyVar));

Callback-функция (англ. call — вызов, англ. back — обратный) или функция обратного вызова в программировании — передача исполняемого кода в качестве одного из параметров другого кода.

См. также Разработка API (контракта) для своей DLL.

Введение в callback-функции

К примеру, если вы хотите установить таймер с использованием Windows API, вы можете вызвать функцию SetTimer, передав в неё указатель на свою функцию, которая и будет callback-функцией. Система будет вызывать вашу функцию каждый раз, когда срабатывает таймер:

procedure MyTimerHandler(Wnd: HWND; uMsg: UINT; idEvent: UINT_PTR; dwTime: DWORD); stdcall;
begin
  // Будет вызвана через 100 мс.
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  SetTimer(Handle, 1, 100, @MyTimerHandler);
end;

Вот ещё пример: если вы хотите найти все окна на рабочем столе, вы можете использовать функцию EnumWindows:

function MyEnumFunc(Wnd: HWND; lpData: LPARAM): Bool; stdcall;
begin
  // Вызывается для каждого найденного окна в системе
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  EnumWindows(@MyEnumFunc, 0);
end;

Поскольку функция обратного вызова обычно выполняет ту же задачу, что и код, который её устанавливает, то получается, что обоим кускам кода нужно работать с одними и теми же данными. Следовательно, данные от устанавливающего кода необходимо как-то передать в функцию обратного вызова. Для этой цели в функциях обратного вызова предусматриваются т.н. user-параметры: это либо указатель, либо целое число (обязательно типа Native(U)Int, но не (U)Int), который никак не используются самим API и прозрачно передаются в callback-функцию. Либо (в редких случаях) это может быть какое-то значение, уникально идентифицирующее вызов функции.

К примеру, в SetTimer есть idEvent, а в EnumWindows есть lpData. Мы можем использовать эти параметры, чтобы передать произвольные данные. Вот, к примеру, как можно найти все окна заданного класса:

type
  PEnumArgs = ^TEnumArgs;
  TEnumArgs = record
    ClassName: String;
    Windows: TStrings;
  end;

function FindWindowsOfClass(Wnd: HWND; lpData: LPARAM): Bool; stdcall;
var
  Args: PEnumArgs;
  WndClassName, WndText: String;
begin
  Args := Pointer(lpData);

  SetLength(WndClassName, Length(Args.ClassName) + 2);
  SetLength(WndClassName, GetClassName(Wnd, PChar(WndClassName), Length(WndClassName)));
  if WndClassName = Args.ClassName then
  begin
    SetLength(WndText, GetWindowTextLength(Wnd) + 1);
    SetLength(WndText, GetWindowText(Wnd, PChar(WndText), Length(WndText)));
    Args.Windows.Add(Format('%8x : %s', [Wnd, WndText]));
  end;

  Result := True;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Args: TEnumArgs;
begin
  // В Edit можно вводить значения типа 
  // 'TForm1', 'IME', 'MSTaskListWClass', 'Shell_TrayWnd', 'TTOTAL_CMD', 'Chrome_WidgetWin_1'
  Args.ClassName := Edit1.Text;  
  Args.Windows   := Memo1.Lines;

  Memo1.Lines.BeginUpdate;
  try
    Memo1.Lines.Clear;
    EnumWindows(@FindWindowsOfClass, LPARAM(@Args));
  finally
    Memo1.Lines.EndUpdate;
  end;
end;

Примечание: неким аналогом user-параметров являются свойства Tag и Data, хотя их использование не всегда бывает идеологически верным (правильно: создать класс-наследник).

Static и callback-методы вместо callback-функций

Поскольку обычно современные приложения строятся как совокупность классов — было бы неплохо изолировать функцию обратного вызова: сделать её не глобальной, а членом класса. Это легко сделать следующим образом, используя статические классовые методы:

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    Memo1: TMemo;
    Button1: TButton;

  // Переходник
  strict private
    type
      PInternalEnumArgs = ^TInternalEnumArgs;
      TInternalEnumArgs = record
        Self: TForm1;
        Data: Pointer;
      end;
    class function InternalEnumWindowsCallback(Wnd: HWND; lpData: LPARAM): Bool; stdcall; static;

  // Hi-level интерфейс
  protected
    function EnumWindowsCallback(const AWnd: HWND; const lpData: Pointer): Boolean; virtual;
    function EnumWindows(const lpData: Pointer = nil): Boolean;
  end;

// ...

function TForm1.EnumWindows(const lpData: Pointer): Boolean;
var
  Args: TInternalEnumArgs;
begin
  Args.Self := Self;
  Args.Data := lpData;

  Result := WinAPI.Windows.EnumWindows(@InternalEnumWindowsCallback, @Args);
end;

class function TForm1.InternalEnumWindowsCallback(Wnd: HWND; lpData: LPARAM): Bool;
var
  Args: PInternalEnumArgs;
begin
  Args := Pointer(lpData);

  Result := Args.Self.EnumWindowsCallback(Wnd, Args.Data);
end;

function TForm1.EnumWindowsCallback(const AWnd: HWND; const lpData: Pointer): Boolean;
var
  WndClassName, WndText: String;
begin
  // Ваш код - он может работать как с lpData, так и с членами класса.
  // К примеру:

  SetLength(WndClassName, Length(Edit1.Text) + 2);
  SetLength(WndClassName, GetClassName(Wnd, PChar(WndClassName), Length(WndClassName)));
  if WndClassName = Edit1.Text then
  begin
    SetLength(WndText, GetWindowTextLength(Wnd) + 1);
    SetLength(WndText, GetWindowText(Wnd, PChar(WndText), Length(WndText)));
    Memo1.Lines.Add(Format('%8x : %s', [Wnd, WndText]));
  end;

  Result := True;
end;

(подробнее про ключевое слово static можно почитать тут)

Здесь InternalEnumWindowsCallback служит в качестве переходника-адаптера, который конвертирует функцию stdcall в метод класса. Настоящий же callback (который и выполняет всю работу) содержится в обычном методе класса EnumWindowsCallback. Обратите внимание, что поскольку callback является методом, то он имеет доступ к свойствам и методам класса. Поэтому, как правило, callback-методы не используют user-параметры, а сами параметры читают напрямую из класса. Само собой, такой подход тем проще по сравнению с callback-функциями, чем больше параметров нужно передать и чем больше результата надо вывести — поскольку с callback-методами нам не нужно передавать каждое значение вручную через промежуточную запись. Тем не менее, в примере выше я сохранил параметр lpData для свободного использования: не исключена ситуация, когда какие-либо данные будет проще передать через параметр, а не свойства класса — как правило, это бывают локальные данные, которые рассчитываются внутри метода класса, но не сохраняются в полях класса. А если бы нам не нужно было сохранять функциональность lpData, то мы могли бы убрать тип TInternalEnumArgs, передавая Self напрямую как lpData.

Если дизайн API разрабатывает не слишком опытный программист, он может просто не подумать про необходимость наличия user-параметров для callback-функций (а равно как и множество других вещей). В результате у вас может быть на руках такой код:

type
  TCallback = function(FoundData: TData): BOOL; cdecl;

function RegisterCallback(Callback: TCallback): Integer; cdecl;

Как видите, здесь нет user-параметра, а единственный параметр для callback-функции представляет собственно данные для функции.

Решение в лоб: глобальные переменные

Само собой, поскольку разработчик API — не вы, то и изменить прототип функции обратного вызова вы не можете. Что же делать? Поскольку мы не можем передавать данные через локальный параметр, то остаётся только вариант с глобальным параметром:

var
  GMemo: TStrings;

function MyCallback(FoundData: TData): BOOL; cdecl;
begin
  GMemo.Lines.Add(DataToString(FoundData));
end;

function TForm1.Button1Click(Sender: TObject);
begin
  GMemo := Memo1;
  RegisterCallback(MyCallback);
end;

Конечно, мы можем сделать callback-функцию членом класса, как мы делали это выше (с помощью статических классовых методов), но надо понимать, что в этом варианте API callback-метод должен быть классовым, а не обычным, а переменная тоже должна быть классовой, а не полем класса — поскольку мы не можем передать Self через user-параметр. А следовательно, это будут всё те же глобальные функция и переменная, но немного замаскированные. Соответственно, обращаться к свойствам и методам класса из callback-а мы не сможем.

Данное решение можно рассматривать как «удовлетворительное», поскольку оно в принципе работает, но использует глобальные переменные — что плохо. Иногда это может быть допустимо, но часто нам необходимо вызывать callback-функции многопоточно или даже просто в несколько разных вызовов в рамках одного потока. В этом случае кажется, что надёжного способа идентификации нет?

Правильное решение: динамический код

Тем не менее, опытный программист может предложить гарантировано рабочий вариант, «добавляющий» user-параметр к существующему «плохому» API. Это вовсе не невыполнимая задача, как может показаться, но её решение достаточно нетривиально. Суть идеи в том, что user-параметр можно заменить на саму функцию обратного вызова, которая будет уникальна для каждого использования. Таким образом, вызов функции будет идентифицироваться не по параметру, а по тому, какая из функций вызвана.

Постановка задачи

Само собой, в предварительно скомпилированном файле невозможно иметь произвольное число функций для произвольных user-параметров. И именно поэтому это решение требует генерации кода на лету. К счастью, это не слишком сложная задача, поскольку вы можете воспользоваться услугами компилятора Delphi для генерации шаблона. Более того — вы можете даже не знать ассемблер. Но вам нужно иметь некоторое представление об устройстве памяти Windows.

Итак, пусть у нас есть следующее:

type
  TData                   = Integer; // просто для примера; это может быть что угодно: указатель, запись и т.п.
  TCallback               = function(FoundData: TData): BOOL; cdecl;
  TRegisterCallbackFunc   = function(Callback: TCallback): Integer; cdecl;
  TUnregisterCallbackFunc = procedure(Callback: TCallback); cdecl;
var
  RegisterCallback: TRegisterCallbackFunc;      // импортируется из DLL
  UnregisterCallback: TUnregisterCallbackFunc;  // импортируется из DLL

Это — наш «плохой» API. Поскольку сторонний API обычно располагается в отдельной DLL, то я сделал пример с переменной-функцией, а не обычной функцией. В любом случае, если у вас есть обычная функция (API лежит в отдельном модуле, есть dcu, но не pas), то этот случай легко сводится к примеру выше. Итак, наша задача: добавить в этот API поддержку user-параметра.

Создание мастер-шаблона

Шаг первый: пишем следующий код:

function RealCallback(FoundData: TData; Data: Pointer): BOOL; cdecl;
begin
  Result := True;
end;

type
  TRealCallbackFunc = function(FoundData: TData; Data: Pointer): BOOL; cdecl;
var
  GRealCallback: TRealCallbackFunc = RealCallback;

function InternalCallback(FoundData: TData): BOOL; cdecl;
begin
  Result := GRealCallback(FoundData, Pointer($12345678));
end;

Здесь: InternalCallback — это callback-функция, прототип которой полностью соответствует API. Именно её мы будем устанавливать в качестве callback-а. RealCallback — это модифицированная callback-функция, которая отличается от API лишь наличием дополнительного параметра: это и есть наш user-параметр. Хотя прототип RealCallback может быть произвольным, но для упрощения нашей жизни желательно, чтобы он был бы максимально похожим на InternalCallback. Сама InternalCallback должна просто вызывать RealCallback, передавая фиксированный указатель в качестве параметра. Значение $12345678 выбрано по той простой причине, что его легко будет увидеть. Вы можете использовать любое другое «волшебное» значение.

Функция RealCallback из InternalCallback вызывается не напрямую, а опосредованно — через глобальную переменную GRealCallback. Я поясню ниже зачем это сделано.

Итак, добавьте прямой вызов InternalCallback в ваш код:

procedure TForm1.Button1Click(Sender: TObject);
begin
  InternalCallback(0); // здесь: TData = Integer (для этого примера)
end;

и установите на него точку останова. Запустите проект, нажмите кнопку, встаньте на точку останова, зайдите в функцию InternalCallback (F7) и откройте CPU-отладчик (Ctrl + Alt + C или View / Debug Windows / CPU View). Вы увидите такой код:

Unit1.pas.38: begin
005B5C7C 55               push ebp
005B5C7D 8BEC             mov ebp,esp
005B5C7F 51               push ecx
Unit1.pas.39: Result := GRealCallback(FoundData, Pointer($12345678));
005B5C80 6878563412       push $12345678
005B5C85 8B4508           mov eax,[ebp+$08]
005B5C88 50               push eax
005B5C89 FF15A0395C00     call dword ptr [$005c39a0]
005B5C8F 83C408           add esp,$08
005B5C92 8945FC           mov [ebp-$04],eax
Unit1.pas.40: end;
005B5C95 8B45FC           mov eax,[ebp-$04]
005B5C98 59               pop ecx
005B5C99 5D               pop ebp
005B5C9A C3               ret

Выделите этот код и скопируйте куда-нибудь (не нужно его запускать!). Вы можете увидеть, что весь код состоит из трёх строк: begin (он же — пролог), вызов функции, end (он же — эпилог). Каждая строка помечена комментарием. Если вы не знаете ассемблер, то всё, что вам нужно знать: первый столбец это адреса инструкций. Эти адреса принадлежат вашему exe (ведь именно код exe мы вызвали по Button1Click). Второй столбец: hex-коды байтов кода. Т.е. это машинный код в чистом виде. Третий столбец — это ассемблерный код, соответствующий машинному коду. Самое замечательное в этом листинге — нам не нужен ассемблерный код, нам нужен лишь машинный код. Сейчас я поясню почему…

Теперь смотрим: в этой функции есть всего два переменных значения:

  1. Адрес вызываемой функции (RealCallback)
  2. Значение user-параметра

Весь остальной текст статичен и не зависит ни он чего, т.е. он будет ровно тем же самым в любых случаях: для любых user-параметров, для любых callback-функций. Это означает, что если мы хотим сами генерировать функции, подобные InternalCallback, то мы можем просто скопировать весь код целиком и просто подставить в него два числа: адрес функции и адрес параметра.

User-параметр легко увидеть, поскольку мы использовали волшебное значение $12345678. Адрес функции увидеть сложнее (если вы не знакомы с ассемблером), но можно догадаться, что он зашифрован в этой строке:

005B5C89 FF15A0395C00     call dword ptr [$005c39a0]

Почему?

  1. Слово call намекает на «вызов».
  2. Адрес $005C39A0 явно лежит недалеко от адресов $005B5C7C-$005B5C9A, т.е. это какой-то код в exe.

Поскольку наш Паскаль-код вызывает функцию не напрямую, а через глобальную переменную, то легко предположить, что $005C39A0 — это адрес не самой функции, а адрес указателя на функцию.

Примечание: вот почему я использовал конструкцию с опосредованным вызовом функции вместо прямого: потому что в этой конструкции вызов задаётся как «вызвать функцию по этому адресу». Здесь явно присутствует «этот адрес» — что означает, что его можно легко поменять. Если бы вызов был прямым, то машинный код говорил бы «вызвать функцию, которая лежит перед этой через N байтов». Задавать адрес функции в таком варианте было бы намного сложнее.

Вот и всё, что касается разбора шаблона. Обратите внимание, что мы не использовали никаких особенных знаний, кроме умения пользоваться отладчиком и здравого смысла.

Тогда приступаем к кодированию. Для начала надо скопировать наш шаблон в массив байтов для дальнейшего использования в коде программы. Для этого я сначала выписал весь машинный код (второй столбец), одновременно заменив адрес указателя на функцию на (новое) волшебное значение для упрощения отладки:

55
8BEC
51
6878563412    // $12345678 - user-параметр
8B4508
50
FF15FFEEDDCC  // $CCDDEEFF - указатель на функцию
83C408
8945FC
8B45FC
59
5D
C3

Напоминаю, что не нужно руками выписывать эти коды — выделите в CPU-отладчике мышью нужные строки и нажмите Ctrl + C — это скопирует выделенные строки в буфер обмена. Затем перейдите в редактор кода и вставьте текст из буфера, после чего удалите три строки с комментариями, а также первый и последний столбцы, оставив только машинный код.

Обратите внимание, что x86 является little-endian архитектурой — что означает, что все числа «записываются наоборот».

После этого я убрал разделители строк, объединив весь машинный код в длинный поток байтов:

558BEC5168785634128B450850FF15FFEEDDCC
83C4088945FC8B45FC595DC3

(я разбил на две строки для упрощения читабельности в блоге, в редакторе это одна строка)

После чего я вставил #$ через каждые два символа и оформил эту константу как строку:

const
  CallbackTemplate: RawByteString =
    #$55#$8B#$EC#$51#$68#$78#$56#$34#$12#$8B#$45#$08#$50#$FF#$15#$FF#$EE#$DD#$CC +
    #$83#$C4#$08#$89#$45#$FC#$8B#$45#$FC#$59#$5D#$C3;

Почему строку? Ну, это проще и короче, чем настоящий массив байтов: не надо вставлять пробелы, запятые, не надо указывать размерность массива. Кроме того, для строк есть готовая функция поиска и замены (мы увидим позднее почему это важно). Строка однобайтовая, без кодировки (RawByteString — это AnsiString в старых версиях Delphi) — поэтому эта строка является массивом байтов.

Теперь неплохо бы проверить, что мы нигде не ошиблись. Измените обработчик нажатия на кнопку следующим образом:

procedure TForm1.Button1Click(Sender: TObject);
begin
  TCallback(Pointer(CallbackTemplate))(0);
end;

Этой странной строкой мы говорим, что указатель CallbackTemplate (а любая строка — это указатель; для статического массива вам потребовалось бы брать указатель явно через @) следует трактовать не как строку, а как функцию типа TCallback. Ну и эту функцию, стало быть, надо вызвать. Вот более длинная версия того же кода:

procedure TForm1.Button1Click(Sender: TObject);
var
  Template: AnsiString;
  Ptr: Pointer;
  CB: TCallback;
begin
  Template := CallbackTemplate;
  Ptr := Pointer(Template);
  CB := TCallback(Ptr);
  CB(0);
end;

Установите точку останова на вызов функции (в любом варианте — длинном или коротком), запустите программу, нажмите кнопку, встаньте на точке останова. Не нажимайте F7: поскольку для функции закодированной в CallbackTemplate отсутствует исходный код, то компилятор выполнит всю функцию целиком за один проход — что приведёт к Access Violation, поскольку оба наших указателя ($12345678 и $CCDDEEFF) указывают в космос. Вместо этого вызовите CPU отладчик (Ctrl + Alt + C или View / Debug Windows / CPU View) и несколько раз нажмите F7 уже в нём — пока вас не перебросит к знакомому коду:

005B5C74 55               push ebp
005B5C75 8BEC             mov ebp,esp
005B5C77 51               push ecx
005B5C78 6878563412       push $12345678
005B5C7D 8B4508           mov eax,[ebp+$08]
005B5C80 50               push eax
005B5C81 FF15FFEEDDCC     call dword ptr [$ccddeeff]
005B5C87 83C408           add esp,$08
005B5C8A 8945FC           mov [ebp-$04],eax
005B5C8D 8B45FC           mov eax,[ebp-$04]
005B5C90 59               pop ecx
005B5C91 5D               pop ebp
005B5C92 C3               ret 

Убедитесь, что этот фрагмент точно совпадает с исходным кодом (разве что с изменённым адресом на $CCDDEEFF). Если совпадает, то вы всё сделали верно, шаблон готов. Если нет — исправьте и будьте в дальнейшем более внимательны.

Динамическая генерация кода

Следующий шаг — нам необходимо создавать реальные функции обратного вызова (с реальными адресами) по шаблону CallbackTemplate. Собственно, сделать это очень просто — достаточно просто заменить адреса функций и код готов. Есть только небольшая особенность: в архитектуре x86 любой исполняемый код должен располагаться в странице памяти, имеющей атрибут выполнения (EXECUTE). Если мы просто выделим память (GetMem/AllocMem или просто используем строку, массив и другие данные), то это будут «данные»: у них будет доступ на чтение (READ), запись (WRITE), но не выполнение (EXECUTE). Поэтому попытка вызова этого кода приведёт к Access Violation.

Примечание: на ранних процессорах архитектуры x86 атрибуты «чтение» и «выполнение» были эквивалентными. Поэтому, хотя технически ставить равенство между ними никогда не было верным, некоторые воспользовались этой особенностью реализации и передавали управление на код в сегментах данных. Повторим: это никогда не было корректным, но это работало на старых процессорах. Теперь этот код будет вылетать. См. также: DEP.

Вспомним как работает менеджер памяти: он дробит страницы памяти на блоки, которые программа «выделяет» из памяти. Отсюда следует, что мы не можем просто взять память средствами языка Delphi и изменить ей атрибуты: эта память будет расположена в одной странице с какими-то другими данными, и, меняя доступ к странице, мы поменяем доступ к каким-то ещё данным. По этой причине нам необходимо выделять память напрямую у системы, минуя посредников.

Суммируя сказанное, вот подходящий код:

unit FixCallbacks;

interface

// Функция динамически генерирует код для вызова
// функции ACallback с параметром AUserParam по известному шаблону машинного кода ATemplate
// Шаблон ATemplate должен использовать значения $12345678 и $CCDDEEFF как заглушки
// для передачи AUser и указателя на ACallback соответственно
// Результат работы функции можно передавать в "плохой" API
function AllocTemplate(const ATemplate: RawByteString; const ACallback, AUserParam: Pointer): Pointer;

// Функция освобождает шаблон, созданный функцией AllocTemplate
// В неё необходимо передать те же параметры, что и в AllocTemplate
// Функция вернёт указатель, который необходимо передать в функцию дерегистрации "плохого" API
function DisposeTemplate(const ACallback, AUser: Pointer): Pointer;

implementation

uses
  Winapi.Windows, 
  System.SysUtils, 
  System.Classes;

var
  // Список всех динамически сгенерированных кусков кода
  KnownTemplates: TThreadList;

function AllocTemplate(const ATemplate: RawByteString; const ACallback, AUserParam: Pointer): Pointer;

  procedure StrReplace(var ATemplate: RawByteString; const ASource, ADest: NativeUInt);
  var
    X: Integer;
  begin
    for X := 1 to Length(ATemplate) - SizeOf(ASource) do
      if PNativeUInt(@ATemplate[X])^ = ASource then
      begin
        PNativeUInt(@ATemplate[X])^ := ADest;
        Break;
      end;
  end;

var
  OrgPtr: Pointer;
  OrgSize: Cardinal;
  Ptr: PPointer;
  ActualTemplate: RawByteString;
  Dummy: Cardinal;
begin
  // Шаг первый: выделяем ресурсы

  // Взяли шаблон
  ActualTemplate := ATemplate;
  // Выделили память для динамической генерации кода
  // Добавили размер указателя, т.к. нам нужно куда-то сохранять указатель на ACallback
  // Второй указатель нужен для удобства: в него мы сохраним user-параметр (см. DisposeTemplate ниже)
  // Атрибуты страницы: чтение + запись - поскольку сначала нам нужно записать туда код
  OrgSize := Length(ATemplate) + SizeOf(Pointer) * 2;
  OrgPtr := VirtualAlloc(nil, OrgSize, MEM_COMMIT or MEM_RESERVE, PAGE_READWRITE);
  Win32Check(Assigned(OrgPtr));
  Ptr := OrgPtr;

  // Шаг второй: готовим данные

  // Подменяем в шаблоне заглушки на реальные данные
  // Блок начнётся с самого указателя на функцию, а машинный код пойдёт за ним,
  // поэтому в качестве "указателя на указатель на функцию" будет выступать сам Ptr
  StrReplace(ActualTemplate, $12345678, NativeUInt(AUserParam));
  StrReplace(ActualTemplate, $CCDDEEFF, NativeUInt(Ptr));

  // Шаг третий: копируем данные

  // Раз блок начинается с указателя на функцию, то сначала сохраняем указатель
  Ptr^ := ACallback;
  Inc(Ptr);
  // Затем - user-параметр
  Ptr^ := AUserParam;
  Inc(Ptr);
  // Наконец, копируем готовый машинный код в место его выполнения (сразу за указателем)
  Move(Pointer(ActualTemplate)^, Ptr^, Length(ActualTemplate));

  // Шаг четвёртый: делаем код кодом

  // Меняем атрибуты страницы на "чтение + выполнение"
  // "Чтение" необходимо по той причине, что мы храним в этом же блоке указатель на функцию -
  // а его (указатель) читает код. Соответственно, без доступа на чтение код вылетит с AV.
  Win32Check(VirtualProtect(OrgPtr, OrgSize, PAGE_EXECUTE_READ, Dummy));
  // Не забываем про многоядерные процессоры: надо указать, что мы модифицировали исполняемый код
  Win32Check(FlushInstructionCache(GetCurrentProcess, OrgPtr, OrgSize));

  // Шаг пятый: возвращаем результат

  Result := Ptr;
  // Сохраняем результат в списке сгенерированных callback-функций.
  // Это необходимо, чтобы DisposeTemplate могла найти динамический шаблон по статическому
  KnownTemplates.Add(Ptr);
end;

function DisposeTemplate(const ACallback, AUser: Pointer): Pointer;
// AllocTemplate возвращает указатель на машинный код -
// что на SizeOf(Pointer) * 2 ( = SizeOf(TSavedTemplate)) байт дальше начала блока памяти.
// Соответственно, нам сначала нужно найти реальный указатель блока памяти.
type
  PSavedTemplate = ^TSavedTemplate;
  TSavedTemplate = packed record
    Callback, User: Pointer;
  end;
var
  List: TList;
  X: Integer;
  SavedTemplate: PSavedTemplate;
begin
  Result := nil;
  if ACallback = nil then
    Exit;

  SavedTemplate := nil;

  // Ищем динамический шаблон по статическому
  // Необходимо заблокировать список на время работы, т.к. у нас не атомарная операция
  List := KnownTemplates.LockList;
  try
    for X := 0 to List.Count - 1 do
    begin
      SavedTemplate := List[X];

      // Ещё раз: мы храним сдвинутые указатели, начало блока находится на два указателя ранее
      Dec(SavedTemplate);

      // Нашли шаблон?
      if (SavedTemplate.Callback = ACallback) and
         (SavedTemplate.User = AUser) then
      begin
        // Вернём указатель на динамический шаблон для его разрегистрации
        Result := List[X];
        // Сначала удаляем его из списка...
        List.Delete(X);
        Break;
      end
      else
        SavedTemplate := nil;
    end;
  finally
    KnownTemplates.UnlockList;
  end;

  if SavedTemplate = nil then
    Exit;

  // ...а затем - освобождаем память
  Win32Check(VirtualFree(SavedTemplate, 0, MEM_RELEASE));
end;

initialization
  KnownTemplates := TThreadList.Create;
finalization
  FreeAndNil(KnownTemplates);
end.

Данный модуль предлагает две универсальные функции для динамической генерации кода переходников callback-функций. Логика кода достаточно понятно расписана в комментариях, поэтому я не буду её описывать отдельно.

С этими функциями мы можем переделать интерфейсный модуль для «плохого» API следующим образом:

// ...

interface

// ...

type
  TData                   = Integer; // осталось как было

  // Новый тип - как замена TCallback, но с добавленным lpUser
  TRealCallback           = function(FoundData: TData; const lpUser: Pointer): BOOL; cdecl;

// Новые функции - как замена старых, но с добавленным lpUser
function  RegisterCallback(const Callback: TRealCallback; const lpUser: Pointer): Integer;
procedure UnregisterCallback(const Callback: TRealCallback; const lpUser: Pointer);

implementation

uses 
  FixCallbacks; // подключаем "волшебные" функции
 
// Старые декларации скрыли в implementation
type
  TCallback               = function(FoundData: TData): BOOL; cdecl;
  TRegisterCallbackFunc   = function(Callback: TCallback): Integer; cdecl;
  TUnregisterCallbackFunc = procedure(Callback: TCallback); cdecl;
var
  InternalRegisterCallback: TRegisterCallbackFunc;
  InternalUnregisterCallback: TUnregisterCallbackFunc;

function RegisterCallback(const Callback: TRealCallback; const lpUser: Pointer): Integer;
var
  UniqueCallback: TCallback;
begin
  UniqueCallback := TCallback(AllocTemplate(CallbackTemplate, @Callback, lpUser));
  Result := InternalRegisterCallback(UniqueCallback);
end;

procedure UnregisterCallback(const Callback: TRealCallback; const lpUser: Pointer);
begin
  InternalUnregisterCallback(TCallback(DisposeTemplate(@Callback, lpUser)));
end;

// ...

И тогда мы можем написать такой код:

function MyCallback(FoundData: TData; const lpUser: Pointer): BOOL; cdecl;
var
  Self: TForm1;
begin
  Self := TForm1(lpUser);
  Self.Memo1.Lines.Add(IntToStr(FoundData));

  Result := True;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  RegisterCallback(MyCallback, Pointer(Self));
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  UnregisterCallback(MyCallback, Pointer(Self));
end;

Вуаля! Магия!

Обратите внимание, что функции AllocTemplate и DisposeTemplate являются универсальными и никак не зависят от вашего кода. Чтобы проиллюстрировать эту универсальность — давайте перепишем наш пример поиска окон заданного класса через EnumWindows так, чтобы он не использовал бы user-параметр функции EnumWindows. Для этого нам нужно составить шаблон. Пишем код:

function RealFindWindowsOfClass(Wnd: HWND; lpData: LPARAM; User: Pointer): Bool; stdcall;
begin
  Result := True;
end;

type
  TFindWindowsOfClassFunc = function(Wnd: HWND; lpData: LPARAM; User: Pointer): Bool; stdcall;
var
  GFindWindowsOfClass: TFindWindowsOfClassFunc = RealFindWindowsOfClass;

function FindWindowsOfClass(Wnd: HWND; lpData: LPARAM): Bool; stdcall;
begin
  Result := GFindWindowsOfClass(Wnd, lpData, Pointer($12345678));
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  FindWindowsOfClass(0, 0);
end;

Получаем такой ассемблерный листинг:

Unit44.pas.40: begin
005B5CE4 55               push ebp
005B5CE5 8BEC             mov ebp,esp
005B5CE7 51               push ecx
Unit44.pas.41: Result := GFindWindowsOfClass(Wnd, lpData, Pointer($12345678));
005B5CE8 6878563412       push $12345678
005B5CED 8B450C           mov eax,[ebp+$0c]
005B5CF0 50               push eax
005B5CF1 8B4508           mov eax,[ebp+$08]
005B5CF4 50               push eax
005B5CF5 FF15A0395C00     call dword ptr [$005c39a0]
005B5CFB 8945FC           mov [ebp-$04],eax
Unit44.pas.42: end;
005B5CFE 8B45FC           mov eax,[ebp-$04]
005B5D01 59               pop ecx
005B5D02 5D               pop ebp
005B5D03 C20800           ret $0008

Вырезаем машинный код и делаем его константой (не забыв заменить 005C39A0 на CCDDEEFF):

const
  EnumWndTemplate: RawByteString =
   #$55#$8B#$EC#$51#$68#$78#$56#$34#$12#$8B#$45#$0C#$50#$8B#$45#$08#$50#$FF#$15#$FF#$EE#$DD#$CC +
   #$89#$45#$FC#$8B#$45#$FC#$59#$5D#$C2#$08#$00;

Всё готово, можно вызывать:

function FindWindowsOfClass(Wnd: HWND; lpData: LPARAM; User: Pointer): Bool; stdcall;
var
  Self: TForm1;
  WndClassName, WndText: String;
begin
  Self := TForm1(User);

  SetLength(WndClassName, Length(Self.Edit1.Text) + 2);
  SetLength(WndClassName, GetClassName(Wnd, PChar(WndClassName), Length(WndClassName)));
  if WndClassName = Self.Edit1.Text then
  begin
    SetLength(WndText, GetWindowTextLength(Wnd) + 1);
    SetLength(WndText, GetWindowText(Wnd, PChar(WndText), Length(WndText)));
    Self.Memo1.Lines.Add(Format('%8x : %s', [Wnd, WndText]));
  end;

  Result := True;
end;

procedure TForm1.Button1Click(Sender: TObject);
const
  EnumWndTemplate: RawByteString =
   #$55#$8B#$EC#$51#$68#$78#$56#$34#$12#$8B#$45#$0C#$50#$8B#$45#$08#$50#$FF#$15#$FF#$EE#$DD#$CC +
   #$89#$45#$FC#$8B#$45#$FC#$59#$5D#$C2#$08#$00;
var
  CustomizedCallback: Pointer;
begin
  CustomizedCallback := AllocTemplate(EnumWndTemplate, @FindWindowsOfClass, Pointer(Self));
  try
    Memo1.Lines.BeginUpdate;
    try
      Memo1.Lines.Clear;
      EnumWindows(CustomizedCallback, 0);
    finally
      Memo1.Lines.EndUpdate;
    end;
  finally
    DisposeTemplate(@FindWindowsOfClass, Pointer(Self));
  end;
end;

Вот и всё, что я хотел сегодня сказать.

ВАЖНОЕ ПРИМЕЧАНИЕ

Подход, описанный в этой статье, является хаком. Это означает, что это — костыль, обходной путь, способ заставить код работать хоть как-то. Это не оправдание для написания «плохого» кода API. Если у вас есть контроль над прототипами callback-функций — измените их! Введите поддержку user-параметров.

См. также Разработка API (контракта) для своей DLL.

P.S. Глобальный список в KnownTemplates не является обязательным. Вполне можно обойтись без него. Для этого вызывающий должен сохранять результат вызова AllocTemplate, а затем передавать его в DisposeTemplate (вместо тех же аргументов, как это сделано сейчас). Тогда список был бы не нужен, потому что DisposeTemplate смогла бы извлечь указатель на освобождаемый блок из своего аргумента. Этот сценарий удобен в случае кода как в примере с EnumWindows: нам достаточно передать CustomizedCallback в DisposeTemplate — и это совершенно не накладно. Почему же я сделал список? Потому что такой подход накладывает обязанность хранения указателя на вызывающего. Посмотрите сценарий с интерфейсным модулей для «плохого» API. Если мы хотим в точности сохранить интерфейс кода, то мы не можем передать вызывающему указатель. Вот почему нам и потребовался список. Если же вы готовы пойти на изменение интерфейса заголовочных модулей «плохого» API, то вы вполне можете отказаться от списка. Тогда DisposeTemplate будет выглядеть так:

// ATemplate - указатель, возвращённый функцией AllocTemplate
procedure DisposeTemplate(ATemplate: Pointer);
begin
  if ATemplate = nil then
    Exit;

  ATemplate := Pointer(NativeUInt(ATemplate) - SizeOf(Pointer) * 2);
  if ATemplate = nil then
    Exit;

  Win32Check(VirtualFree(ATemplate, 0, MEM_RELEASE));
end;

А из функции AllocTemplate надо будет убрать строку с добавлением в KnownTemplates, после чего все упоминания KnownTemplates можно также удалить (а заодно — удалить модуль Classes из uses).

45 4.2. Функция окна Все Windows-программы должны содержать специальную функцию, которая вызывается не самой программой, а операционной системой, когда Windows передает сообщение программе. Эту функцию назьшают функцией окна, или процедурой окна Согласно терминологии Windows, функции, вызываемые системой, называются функциями обратного вызова. Именно через нее осуществляется взаимодействие между программой и системой. Имя функции окна может быть любое, например, WindowsFunc, тип функции — LRESULT CALLBACK. Первое слово LRESULT означает, что функция должна вернуть в качестве результата длинное целое (long) — код завершения, а CALLBACK означает, что это функция обратного вызова. LRESULT CALLBACK WindowsFunс(HWND hWnd, UINT message, WPAFAM wParam, LPARAM IParam) Функция окна имеет 4 параметра, которые характеризуют передаваемое сообщение: дескриптор окна, тип сообщения, последние два зависят от типа сообщения. В этой функции должна быть реализована обработка всех сообщений Windows. Обычно она состоит из оператора switch, в котором на каждое сообщение предусмотрена соответствующая реакция. 4.3. Сообщения Windows Каждое сообщение характеризуется 4 параметрами: дескриптор окна, тип сообщения и два дополнительных параметра, которые зависят от типа сообщения. Первый параметр window handle — это дескриптор окна, которому адресовано сообщение. Он представляет собой уникальный номер, идентифицирующий окно. Второй параметр определяет тип сообщения — message type. Тип сообщения — это один из идентификаторов, определенных в заголовочных файлах Windows. Идентификаторы начинаются с префикса WM_ (Windows Message). Наиболее часто посылаемые сообщения: WM DESTROY (при закрытии окна), WMPAINT (когда окно требует обновления), WM COMMAND (при выборе команды меню), WM CHAR (при нажатии клавиши клавиатуры), WM LBUTTONDOWN (при нажатии левой кнопки мыши), WM SIZE (при изменении размеров окна) и др. Последние два параметра содержат дополнительную информацию, необходимую для интерпретации сообщения, например, для сообщений WM LBUTTONDOWN и WM RBUTTONDOWN, передаются координаты курсора мыши, для WM CHAR — код клавиши, WM COMMAND — идентификатор выбранного пункта меню. Когда сообщение посылается окну программы, все перечисленные параметры сообщения передаются функции окна.

Made with FlippingBook

RkJQdWJsaXNoZXIy MTY0OTYy

From Wikipedia, the free encyclopedia

A callback is often back on the level of the original caller.

In computer programming, a callback is a function that is stored as data (a reference) and designed to be called by another function – often back to the original abstraction layer.

A function that accepts a callback parameter may be designed to call back before returning to its caller which is known as synchronous or blocking. The function that accepts a callback may be designed to store the callback so that it can be called back after returning which is known as asynchronous, non-blocking or deferred.

Programming languages support callbacks in different ways such as function pointers, lambda expressions and blocks.

To aid understanding the concept, the following is an analogy from real life.

A customer visits a store to place an order. This is like the first call.

The customer gives to a clerk a list of items, a check to cover their cost and delivery instructions. These are the parameters of the first call including the callback which is the delivery instructions. It is understood that the check will be cashed and that the instructions will be followed.

When the staff are able, they deliver the items as instructed which is like calling the callback.

Notably, the delivery need not be made by the clerk who took the order. A callback need not be called by the function that accepted the callback as a parameter.

Also, the delivery need not be made directly to the customer. A callback need not be to the calling function. In fact, a function would generally not pass itself as a callback.
Some find the use of back to be misleading since the call is (generally) not back to the original caller as it is for a telephone call.

A blocking callback runs in the execution context of the function that passes the callback. A deferred callback can run in a different context such as during interrupt or from a thread. As such, a deferred callback can be used for synchronization and delegating work to another thread.

A callback can be used for event handling. Often, consuming code registers a callback for a particular type of event. When that event occurs, the callback is called.

Callbacks are often used to program the graphical user interface (GUI) of a program that runs in a windowing system. The application supplies a reference to a custom callback function for the windowing system to call. The windowing system calls this function to notify the application of events like mouse clicks and key presses.

Asynchronous action

[edit]

A callback can be used to implement asynchronous processing. A caller requests an action and provides a callback to be called when the action completes which might be long after the request is made.

A callback can be used to implement polymorphism. In the following pseudocode, say_hi can take either write_status or write_error.

def write_status(string message):
    write(stdout, message)
def write_error(string message):
    write(stderr, message)
def say_hi(write):
    write("Hello world")

The callback technology is implemented differently by programming language.

In assembly, C, C++, Pascal, Modula2 and other languages, a callback function is stored internally as a function pointer. Using the same storage allows different languages to directly share callbacks without a design-time or runtime interoperability layer. For example, the Windows API is accessible via multiple languages, compilers and assemblers.

C++ also allows objects to provide an implementation of the function call operation. The Standard Template Library accepts these objects (called functors) as parameters.

Many dynamic languages, such as JavaScript, Lua, Python, Perl[1][2] and PHP, allow a function object to be passed.

CLI languages such as C# and VB.NET provide a type-safe encapsulating function reference known as delegate.

Events and event handlers, as used in .NET languages, provide for callbacks.

Functional languages generally support first-class functions, which can be passed as callbacks to other functions, stored as data or returned from functions.

Many languages, including Perl, Python, Ruby, Smalltalk, C++ (11+), C# and VB.NET (new versions) and most functional languages, support lambda expressions, unnamed functions with inline syntax, that generally acts as callbacks..

In some languages, including Scheme, ML, JavaScript, Perl, Python, Smalltalk, PHP (since 5.3.0),[3] C++ (11+), Java (since 8),[4] and many others, a lambda can be a closure, i.e. can access variables locally defined in the context in which the lambda is defined.

In an object-oriented programming language such as Java versions before function-valued arguments, the behavior of a callback can be achieved by passing an object that implements an interface. The methods of this object are callbacks.

In PL/I and ALGOL 60 a callback procedure may need to be able to access local variables in containing blocks, so it is called through an entry variable containing both the entry point and context information. [5]

Callbacks have a wide variety of uses, for example in error signaling: a Unix program might not want to terminate immediately when it receives SIGTERM, so to make sure that its termination is handled properly, it would register the cleanup function as a callback. Callbacks may also be used to control whether a function acts or not: Xlib allows custom predicates to be specified to determine whether a program wishes to handle an event.

In the following C code, function print_number uses parameter get_number as a blocking callback. print_number is called with get_answer_to_most_important_question which acts as a callback function. When run the output is: «Value: 42».

#include <stdio.h>
#include <stdlib.h>

void print_number(int (*get_number)(void)) {
    int val = get_number();
    printf("Value: %d\n", val);
}

int get_answer_to_most_important_question(void) {
    return 42;
}

int main(void) {
    print_number(get_answer_to_most_important_question);
    return 0;
}

In C++, functor can be used in addition to function pointer.

In the following C# code,
method Helper.Method uses parameter callback as a blocking callback. Helper.Method is called with Log which acts as a callback function. When run, the following is written to the console: «Callback was: Hello world».

public class MainClass
{
    static void Main(string[] args)
    {
        Helper helper = new Helper();
        helper.Method(Log);
    }

    static void Log(string str)
    {
        Console.WriteLine($"Callback was: {str}");
    }
}

public class Helper
{
    public void Method(Action<string> callback)
    {
        callback("Hello world");
    }
}

In the following Kotlin code, function askAndAnswer uses parameter getAnswer as a blocking callback. askAndAnswer is called with getAnswerToMostImportantQuestion which acts as a callback function. Running this will tell the user that the answer to their question is «42».

fun main() {
    print("Enter the most important question: ")
    val question = readLine()
    askAndAnswer(question, ::getAnswerToMostImportantQuestion)
}

fun getAnswerToMostImportantQuestion(): Int {
    return 42
}

fun askAndAnswer(question: String?, getAnswer: () -> Int) {
    println("Question: $question")
    println("Answer: ${getAnswer()}")
}

In the following JavaScript code, function calculate uses parameter operate as a blocking callback. calculate is called with multiply and then with sum which act as callback functions.

function calculate(a, b, operate) {
    return operate(a, b);
}
function multiply(a, b) {
    return a * b;
}
function sum(a, b) {
    return a + b;
}
// outputs 20
alert(calculate(10, 2, multiply));
// outputs 12
alert(calculate(10, 2, sum));

The collection method .each() of the jQuery library uses the function passed to it as a blocking callback. It calls the callback for each item of the collection. For example:

$("li").each(function(index) {
  console.log(index + ": " + $(this).text());
});

Deferred callbacks are commonly used for handling events from the user, the client and timers. Examples can be found in addEventListener, Ajax and XMLHttpRequest.
[6]

In addition to using callbacks in JavaScript source code, C functions that take a function are supported via js-ctypes.[7]

The following REBOL/Red code demonstrates callback use.

  • As alert requires a string, form produces a string from the result of calculate
  • The get-word! values (i.e., :calc-product and :calc-sum) trigger the interpreter to return the code of the function rather than evaluate with the function.
  • The datatype! references in a block! [float! integer!] restrict the type of values passed as arguments.
Red [Title: "Callback example"]

calculate: func [
    num1 [number!] 
    num2 [number!] 
    callback-function [function!]
][
    callback-function num1 num2
]

calc-product: func [
    num1 [number!] 
    num2 [number!]
][
    num1 * num2
]

calc-sum: func [
    num1 [number!] 
    num2 [number!]
][
    num1 + num2
]

; alerts 75, the product of 5 and 15
alert form calculate 5 15 :calc-product

; alerts 20, the sum of 5 and 15
alert form calculate 5 15 :calc-sum

Rust have the Fn, FnMut and FnOnce traits.[8]

fn call_with_one<F>(func: F) -> usize
    where F: Fn(usize) -> usize {
    func(1)
}

let double = |x| x * 2;
assert_eq!(call_with_one(double), 2);

In this Lua code, function calculate accepts the operation parameter which is used as a blocking callback. calculate is called with both add and multiply, and then uses an anonymous function to divide.

function calculate(a, b, operation)
    return operation(a, b)
end

function multiply(a, b)
    return a * b
end

function add(a, b)
    return a + b
end

print(calculate(10, 20, multiply)) -- outputs 200
print(calculate(10, 20, add)) -- outputs 30
-- an example of a callback using an anonymous function
print(calculate(10, 20, function(a, b)
    return a / b -- outputs 0.5
end))

In the following Python code, function calculate accepts a parameter operate that is used as a blocking callback. calculate is called with square which acts as a callback function.

def square(val):
    return val ** 2
def calculate(operate, val):
    return operate(val)
# outputs: 25
calculate(square, 5)

In the following Julia code, function calculate accepts a parameter operate that is used as a blocking callback. calculate is called with square which acts as a callback function.

julia> square(val) = val^2
square (generic function with 1 method)
julia> calculate(operate, val) = operate(val)
calculate (generic function with 1 method)
julia> calculate(square, 5)
25
  • Command pattern
  • Continuation-passing style
  • Event loop
  • Event-driven programming
  • Implicit invocation
  • Inversion of control
  • libsigc++, a callback library for C++
  • Signals and slots
  • User exit
  1. ^ «Perl Cookbook — 11.4. Taking References to Functions». 2 July 1999. Retrieved 2008-03-03.
  2. ^ «Advanced Perl Programming — 4.2 Using Subroutine References». 2 July 1999. Retrieved 2008-03-03.
  3. ^ «PHP Language Reference — Anonymous functions». Retrieved 2011-06-08.
  4. ^ «What’s New in JDK 8». oracle.com.
  5. ^ Belzer, Jack; Holzman, Albert G; Kent, Allen, eds. (1979). Encyclopedia of Computer Science and Technology: Volume 12. Marcel Dekker, inc. p. 164. ISBN 0-8247-2262-0. Retrieved January 28, 2024.
  6. ^ «Creating JavaScript callbacks in components». Archive. UDN Web Docs (Documentation page). sec. JavaScript functions as callbacks. Archived from the original on 2021-12-16. Retrieved 2021-12-16.
  7. ^ Holley, Bobby; Shepherd, Eric (eds.). «Declaring and Using Callbacks». Docs. Mozilla Developer Network (Documentation page). Archived from the original on 2019-01-17. Retrieved 2021-12-16.
  8. ^ «Fn in std::ops — Rust». doc.rust-lang.org. Retrieved 18 January 2025.
  • Basic Instincts: Implementing Callback Notifications Using Delegates — MSDN Magazine, December 2002
  • Implement callback routines in Java
  • Implement Script Callback Framework in ASP.NET 1.x — Code Project, 2 August 2004
  • Interfacing C++ member functions with C libraries (archived from the original on July 6, 2011)
  • Style Case Study #2: Generic Callbacks

Что это за тип функции такой CALLBACK? Обычно такие функции называются функциями обратного вызова. Это просто. Если Вашу функцию должен вызывать Windows, то вы должны указать ей тип передачи параметров как CALLBACK. Этот тип вызова описан в WinDef.H как:

#define CALLBACK    __stdcall

То есть тип передачи параметров PASCAL. Обычный вызов функций осуществляется в стиле WIN32 API. Как же Windows узнает, что эту функцию можно выполнить ? Вы сами, зная то или нет, передаете ее в параметрах. Если вы создаете окно в Win 32, то и передаете функцию окна. Windows эту функцию вызывает когда управляет окном. Все просто. Сказали системе, что если нужно обратиться к окну вот тебе функция. После этого Windows знает, что если нужно перерисовать окно, то он хвать эту функцию и передает ей в параметры WM_PAINT. Идея довольно простая. Операционная система должна уметь вызывать некоторые функции в приложении, чтобы освободить вас как программиста от слежения за программой. Кто писал для ДОС знает как это не удобно думать о том, какое окно видно на экране, а какое нет. Пусть операционная система заботится. Итак, в любой программе для Windows (кстати и не только в понимании графического интерфейса) есть функции, которые вызовет операционная система. Как пример главная функция окна. Эта функция должна быть в программе правильно оформлена, а именно CALLBACK. Обычно мы ее передаем в виде параметров при вызове фнукций WIN32 API.

Понравилась статья? Поделить с друзьями:
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
  • Прекращена работа программы windows media player
  • Microsoft windows 10 professional x32 x64 all lng esd
  • Windows 10 update error 0x80070002
  • Tp link tg 3269 драйвер windows 10
  • Список переменных среды windows 10