Marco's Web Center
Delphi provides a complete encapsulation of the low-level Windows API using Object Pascal and the Visual Component Library (VCL), so it is rarely necessary to build Windows applications using plain Pascal and calling Windows API functions directly. Nonetheless, programmers who want to use some special techniques not supported by the VCL still have that option in Delphi. You would only want to take this approach for very special cases, such as the development of new Delphi components based on unusual API calls, and I don't want to cover the details. Instead, we'll look at a few elements of Delphi's interaction with the operating system and a couple of techniques that Delphi programmers can benefit from.
Windows HandlesAmong the data types introduced by Windows in Delphi, handles represent the most important group. The name of this data type is THandle, and the type is defined in the Windows unit as:
type THandle = LongWord;
Handle data types are implemented as numbers, but they are not used as such. In Windows, a handle is a reference to an internal data structure of the system. For example, when you work with a window (or a Delphi form), the system gives you a handle to the window. The system informs you that the window you are working with is window number 142, for example. From that point on, your application can ask the system to operate on window number 142ómoving it, resizing it, reducing it to an icon, and so on. Many Windows API functions, in fact, have a handle as the first parameter. This doesn't apply only to functions operating on windows; other Windows API functions have as their first parameter a GDI handle, a menu handle, an instance handle, a bitmap handle, or one of the many other handle types.
In other words, a handle is an internal code you can use to refer to a specific element handled by the system, including a window, a bitmap, an icon, a memory block, a cursor, a font, a menu, and so on. In Delphi, you seldom need to use handles directly, since they are hidden inside forms, bitmaps, and other Delphi objects. They become useful when you want to call a Windows API function that is not supported by Delphi.
To complete this description, here is a simple example demonstrating Windows handles. The WHandle program has a simple form, containing just a button. In the code, I respond to the OnCreate event of the form and the OnClick event of the button, as indicated by the following textual definition of the main form:
object FormWHandle: TFormWHandle Caption = 'Window Handle' OnCreate = FormCreate object BtnCallAPI: TButton Caption = 'Call API' OnClick = BtnCallAPIClick end end
As soon as the form is created, the program retrieves the handle of the window corresponding to the form, by accessing the Handle property of the form itself. We call IntToStr to convert the numeric value of the handle into a string, and we append that to the caption of the form, as you can see in Figure 9.1:
procedure TFormWHandle.FormCreate(Sender: TObject); begin Caption := Caption + ' ' + IntToStr (Handle); end;
Because FormCreate is a method of the form's class, it can access other properties and methods of the same class directly. Therefore, in this procedure we can simply refer to the Caption of the form and its Handle property directly.
Figure 9.1: The WHandle example shows the handle of the form window. Every time you run this program you'll get a different value.
If you run this program several times you'll generally get different values for the handle. This value, in fact, is determined by Windows and is sent back to the application. (Handles are never determined by the program, and they have no predefined values; they are determined by the system, which generates new values each time you run a program.)
When the user presses the button, the program simply calls a Windows API function, SetWindowText, which changes the text or caption of the window passed as the first parameter. To be more precise, the first parameter of this API function is the handle of the window we want to modify:
procedure TFormWHandle.BtnCallAPIClick(Sender: TObject); begin SetWindowText (Handle, 'Hi'); end;
This code has the same effect as the previous event handler, which changed the text of the window by giving a new value to the Caption property of the form. In this case calling an API function makes no sense, because there is a simpler Delphi technique. Some API functions, however, have no correspondence in Delphi, as we'll see in more advanced examples later in the book.
Another important element for Windows programming is represented by external declarations. Originally used to link the Pascal code to external functions that were written in assembly language, the external declaration is used in Windows programming to call a function from a DLL (a dynamic link library). In Delphi, there are a number of such declarations in the Windows unit:
// forward declaration function LineTo (DC: HDC; X, Y: Integer): BOOL; stdcall; // external declaration (instead of actual code) function LineTo; external 'gdi32.dll' name 'LineTo';
This declaration means that the code of the function LineTo is stored in the GDI32.DLL dynamic library (one of the most important Windows system libraries) with the same name we are using in our code. Inside an external declaration, in fact, we can specify that our function refer to a function of a DLL that originally had a different name.
You seldom need to write declarations like the one just illustrated, since they are already listed in the Windows unit and many other Delphi system units. The only reason you might need to write this external declaration code is to call functions from a custom DLL, or to call undocumented Windows functions.
Note: In the 16-bit version of Delphi, the external declaration used the name of the library without the extension, and was followed by the name directive (as in the code above) or by an alternative index directive, followed by the ordinal number of the function inside the DLL. The change reflects a system change in the way libraries are accessed: Although Win32 still allows access to DLL functions by number, Microsoft has stated this won't be supported in the future. Notice also that the Windows unit replaces the WinProcs and WinTypes units of the 16-bit version of Delphi.
A Windows Callback Function
We've seen in Chapter 6 that Objet Pascal supports procedural types. A common use of procedural types is to provide callback functions to a Windows API function.
First of all, what is a callback function? The idea is that some API function performs a given action over a number of internal elements of the system, such as all of the windows of a certain kind. Such a function, also called an enumerated function, requires as a parameter the action to be performed on each of the elements, which is passed as a function or procedure compatible with a given procedural type. Windows uses callback functions in other circumstances, but we'll limit our study to this simple case.
Now consider the EnumWindows API function, which has the following prototype (copied from the Win32 Help file):
BOOL EnumWindows( WNDENUMPROC lpEnumFunc, // address of callback function LPARAM lParam // application-defined value );
Of course, this is the C language definition. We can look inside the Windows unit to retrieve the corresponding Pascal language definition:
function EnumWindows ( lpEnumFunc: TFNWndEnumProc; lParam: LPARAM): BOOL; stdcall;
Consulting the help file, we find that the function passed as a parameter should be of the following type (again in C):
BOOL CALLBACK EnumWindowsProc ( HWND hwnd, // handle of parent window LPARAM lParam // application-defined value );
This corresponds to the following Delphi procedural type definition:
type EnumWindowsProc = function (Hwnd: THandle; Param: Pointer): Boolean; stdcall;
The first parameter is the handle of each main window in turn, while the second is the value we've passed when calling the EnumWindows function. Actually in Pascal the TFNWndEnumProc type is not properly defined; it is simply a pointer. This means we need to provide a function with the proper parameters and then use it as a pointer, taking the address of the function instead of calling it. Unfortunately, this also means that the compiler will provide no help in case of an error in the type of one of the parameters.
Windows requires programmers to follow the stdcall calling convention every time we call a Windows API function or pass a callback function to the system. Delphi, by default, uses a different and more efficient calling convention, indicated by the register keyword.
Here is the definition of a proper compatible function, which reads the title of the window into a string, then adds it to a ListBox of a given form:
function GetTitle (Hwnd: THandle; Param: Pointer): Boolean; stdcall; var Text: string; begin SetLength (Text, 100); GetWindowText (Hwnd, PChar (Text), 100); FormCallBack.ListBox1.Items.Add ( IntToStr (Hwnd) + ': ' + Text); Result := True; end;
The form has a ListBox covering almost its whole area, along with a small panel on the top hosting a button. When the button is pressed, the EnumWindows API function is called, and the GetTitle function is passed as its parameter:
procedure TFormCallback.BtnTitlesClick(Sender: TObject); var EWProc: EnumWindowsProc; begin ListBox1.Items.Clear; EWProc := GetTitle; EnumWindows (@EWProc, 0); end;
I could have called the function without storing the value in a temporary procedural type variable first, but I wanted to make clear what is going on in this example. The effect of this program is actually quite interesting, as you can see in Figure 9.2. The Callback example shows a list of all the existing main windows running in the system. Most of them are hidden windows you usually never see (and many actually have no caption).
Figure 9.2: The output of the Callback example, listing the current main windows (visible and hidden).
A Minimal Windows Program
To complete the coverage of Windows programming and the Pascal language, I want to show you a very simple but complete application built without using the VCL. The program simply takes the command-line parameter (stored by the system in the cmdLine global variable) and then extracts information from it with the ParamCount and ParamStr Pascal functions. The first of these functions returns the number of parameters; the second returns the parameter in a given position.
Although users seldom specify command-line parameters in a graphical user interface environment, the Windows command-line parameters are important to the system. For example, once you have defined an association between a file extension and an application, you can simply run a program by selecting an associated file. In practice, when you double-click on a file, Windows starts the associated program and passes the selected file as a command-line parameter.
Here is the complete source code of the project (a DPR file, not a PAS file):
program Strparam; uses Windows; begin // show the full string MessageBox (0, cmdLine, 'StrParam Command Line', MB_OK); // show the first parameter if ParamCount > 0 then MessageBox (0, PChar (ParamStr (1)), '1st StrParam Parameter', MB_OK) else MessageBox (0, PChar ('No parameters'), '1st StrParam Parameter', MB_OK); end.
The output code uses the MessageBox API function, simply to avoid getting the entire VCL into the project. A pure Windows program as the one above, in fact, has the advantage of a very small memory footprint: The executable file of the program is about 16 Kbytes.
To provide a command-line parameter to this program, you can use Delphi's Run > Parameters menu command. Another technique is to open the Windows Explorer, locate the directory that contains the executable file of the program, and drag the file you want to run onto the executable file. The Windows Explorer will start the program using the name of the dropped file as a command-line parameter. Figure 9.3 shows both the Explorer and the corresponding output.
Figure 9.3: You can provide a command-line parameter to the StrParam example by dropping a file over the executable file in the Windows Explorer.
In this chapter we've seen a low-level introduction to Windows programming, discussing handles and a very simple Windows program. For normal Windows programming tasks, you'll generally use the visual development support provided by Delphi and based on the VCL. But this is beyond the scope of this book, which is the Pascal language.
Next chapter covers variants, a very strange addition to Pascal type system, introduced to provide full OLE support.
Next Chapter: Variants
|© Copyright Marco Cantù, 1995-2014, All rights reserved|