Last Updated:

Callback Functions in Delphi

Nothing prevents you from calling directly, for example, the FormCreate method, but this is extremely rare. On the other hand, even if this method is not called explicitly, it is still executed because VCL automatically calls it without direct instructions from the programmer.

 

Another common property is that the specific name of the method is not important when called indirectly. You can change it, but if this method is still associated with the OnCreate event, it will also be called successfully. The only difference is that such methods are called by the internal mechanisms of the VCL, and the callback functions are called by the Windows system itself. Accordingly, these functions are subject to the following requirements: first, they must be functions, not methods of the class; second, they must be written according to the stdcall call model (MSDN suggests using the callback model, which in existing versions of Windows is synonymous with stdcall). As for how the programmer tells the system that he wrote a callback function, this will be different in each case.


As an example, consider enumerating windows using the EnumWindows function. The help describes it as follows:

 

BOOL EnumWindows (WNDENUMPROC lpEnumFunc, LPARAM lParam);
Accordingly, in Windows.pas it has the form function EnumWindows (lpEnumFunc: TFNWndEnumProc; lParam: LPARAM): BOOL; stdcall;

The lpEnumFunc parameter must contain a pointer to the callback function. The prototype of this function is described as follows:


BOOL CALLBACK EnumWindowsProc (HWND hwnd, LPARAM lParam);


There is no function with this name in the Windows API. This is the so-called function prototype, according to which the callback function should be described. In fact, this prototype provides more freedom than it may seem at first glance.

 

First, the name can be anything. Second, the system does not impose strict restrictions on the names and types of parameters - they can be anything, provided that the new types are the same size as those specified (the TFNWndEnumProc type described in the Windows module is not a procedural type, but simply an untyped pointer, so the Delphi compiler will not control whether the passed callback function matches its prototype). As for the type of function and the type of the first parameter, they make some sense, and changing their type can hardly be useful. But the second parameter is designed specifically to pass a value that the programmer is free to use at his discretion, the system simply passes through it to the callback function the value that the lParam parameter had when calling the EnumWindows function. And the programmer may find it more convenient to work not with the lParam type (i.e. LongInt), but, for example, with a pointer or with an array of four bytes.

If only there were four bytes, and not eight, sixteen or some other number. You can even turn this parameter into a variable parameter, because in this case the function will be passed all the same four bytes - the address of the variable. However, those who are not very familiar with how the stack is used to pass parameters under different call models, it is better not to experiment with changing the type of the parameter, but strictly follow the declared prototype, if necessary, performing the required transformations inside the callback function.


The EnumWindows function works like this: after the call, it begins to iterate through all the currently available top-level windows, i.e. those that do not have a parent. For each such window, the specified callback function is called, the handle of this window is passed to it as the first parameter (each time, of course, a new one), as the second - something that wouldLo passed to the EnumWindows function itself as the second parameter (the same each time). By receiving descriptors of all top-level windows in turn, the callback function can perform a specific action on each of them (close, minimize, and so on). Or you can check all these windows for compliance with some condition, trying to find the right one. And the value returned by the callback function affects the operation of EnumWindows. If it returns False, then everything you need has already been done, you can not go through the rest of the windows.


The final code for when the second parameter is of type Pointer is illustrated in Listing 1.3.


Code Listing 1.3. Call the EnumWindows function with the function callback


function MyCallbackFunction (Wnd: HWND; P: Pointer): BOOL; stdcall;
begin
{ doing something}
end;
...............
var
MyPointer: Pointer;
...............
EnumWindows (@MyCallbackFunction, LongInt (MyPointer));

Whatever we do with the type of the second parameter of the callback function, the type of the corresponding EnumWindows parameter does not change. Therefore, you must explicitly cast the passed parameter to the LongInt type. Type inverse conversion when myCallbackFunction is called is automatic.


The use of EnumWindows and callback functions is demonstrated by the EnumWnd sample.


Note that the callback functions will be called before the EnumWindows function completes. However, this is not a parallelization of the work. To illustrate this, consider a situation where a program calls some function A, which in turn calls function B. Function B will obviously start working before function A. The same thing will happen to the callback function passed to EnumWindows: it will be called from the EnumWindows code in the same way as function B from the code of function A. Therefore, the code of the callback function will be controlled (and not once, because EnumWindows will call this function in a loop) until EnumWindows finishes.


However, this rule does not apply in all situations. In some cases, the system remembers the address of the callback function passed to it in order to use it later. An example of such a function is a window procedure: its address is passed to the system once when the class is registered, and then the system repeatedly calls this function if necessary.


In 16-bit versions of Windows, calling callback functions was complicated by the fact that they required special code. called the prologue. The prologue was created using the MakeProcInstance function and was deleted after completion using FreeProcInstance. Therefore, calling EnumWindows would look like this. as shown in Listing 1.4.


Code Listing 1.4. Call the EnumWindows function in 16-bit versions of Windows
var


MyProcInstanc: TFarProc;
...............
MyProcInstance := MakeProcInstance (@MyCallBackFunction, HInstance);
EnumWindows (MyProcInstance, LongInt (MyPointer));
FreeProcInstance (MyProcInstance);


In Delphi, this code will work because MakeProcInstance and FreeProcInstance are retained for compatibility. But they don't do anything (which is easy to see by looking at the original Windows.pas file), so you can do without them. However,These functions are sometimes still used, apparently simply out of habit. Another way in which 16-bit versions can make a prologue is to describe the function with the export directive. This directive is retained for compatibility in Delphi, but in 32-bit versions it also does nothing (despite the fact that the help, for example, for Delphi 3 claims the opposite; in the Help for Delphi 4 this error is no longer present).