1.1.5. Функции обратного вызова
Прежде чем двигаться дальше, необходимо разобраться с тем, что такое функции обратного вызова (callback functions: этот термин иногда также переводят "функции косвенного вызова"). Эти функции в программе описываются, но обычно не вызываются напрямую, хотя ничто не запрещает сделать это. В этом они похожи на те методы класса, которые связаны с событиями.
Ничто не мешает вызывать напрямую, например, метод FormCreate, но делать это приходится крайне редко. С другой стороны, даже если этот метод не вызывается явно, он все равно выполняется, потому что VCL автоматически вызывает его без прямого указания программиста. Еще одно общее свойство — конкретное имя метода при косвенном вызове не важно. Можно изменить его, но если этот метод по-прежнему будет связан с событием OnCreate, он так же будет успешно вызываться. Разница заключается только в том, что такие методы вызываются внутренними механизмами VCL, а функции обратного вызова — самой системой Windows. Соответственно, на эти функции налагаются следующие требования: во-первых, они должны быть именно функциями, а не методами класса; во-вторых, они должны быть написаны в соответствии с моделью вызова stdcall (MSDN предлагает использовать модель callback, которая в имеющихся версиях Windows является синонимом stdcall). Что же касается того, как программист сообщает системе о том, что он написал функцию обратного вызова, то это в каждом случае будет по-своему.
В качестве примера рассмотрим перечисление окон с помощью функции EnumWindows. В справке она описана так:
BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam);
Соответственно, в Windows.pas она имеет вид
function EnumWindows(lpEnumFunc: TFNWndEnumProc; lParam: LPARAM): BOOL; stdcall;
Параметр lpEnumFunc должен содержать указатель на функцию обратного вызова. Прототип этой функции описан так:
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam);
Функции с таким именем в Windows API не существует. Это так называемый прототип функции, согласно которому следует описывать функцию обратного вызова. На самом деле этот прототип предоставляет большую свободу, чем это может показаться на первый взгляд. Во-первых, имя может быть любым. Во-вторых, система не накладывает строгих ограничений на имена и типы параметров — они могут быть любыми, при условии, что новые типы совпадают по размерам с теми, которые указываются (тип TFNWndEnumProc, описанный в модуле Windows — это не процедурный тип, а просто нетипизированный указатель, поэтому компилятор Delphi не будет контролировать соответствие передаваемой функции обратного вызова ее прототипу). Что касается типа функции и типа первого параметра, то они имеют определенный смысл, и изменение их типа вряд ли может быть полезным. Но второй параметр предназначен специально для передачи значения, которое программист волен использовать но своему усмотрению, система просто передает через него в функцию обратного вызова то значение, которое имел параметр lParam при вызове функции EnumWindows. А программисту может показаться удобнее работать не с типом lParam (т.е. LongInt), а, например, с указателем или же с массивом из четырех байтов. Лишь бы были именно четыре байта, а не восемь, шестнадцать или еще какое-то число. Можно даже превратить этот параметр в параметр-переменную, т.к. при этом функции будут передаваться все те же четыре байта — адрес переменной. Впрочем, тем, кто не очень хорошо разбирается с тем, как используется стек для передачи параметров при различных моделях вызова, лучше не экспериментировать с изменением типа параметра, а строго следовать заявленному прототипу, при необходимости выполняя требуемые преобразования внутри функции обратного вызова.
Функция EnumWindows работает так: после вызова она начинает по очереди перебирать все имеющиеся в данный момент окна верхнего уровня, т.е. те, у которых нет родителя. Для каждого такого окна вызывается заданная функция обратного вызова, в качестве первого параметра ей передается дескриптор данного окна (каждый раз, естественно, новый), в качестве второго — то, что было передано самой функции EnumWindows в качестве второго параметра (каждый раз одно и то же). Получая по очереди дескрипторы всех окон верхнего уровня, функция обратного вызова может выполнить с каждым из них определенное действие (закрыть, минимизировать и т.п.). Или можно проверять все эти окна на соответствие какому-то условию, пытаясь найти нужное. А значение, возвращаемое функцией обратного вызова, влияет на работу EnumWindows. Если она возвращает False, значит, все, что нужно, уже сделано, можно не перебирать остальные окна.