Delphi and the Windows API

When Delphi was introduced, it brought a new era to Windows programming. Never before was it so easy to create robust, full-featured applications for the Windows environment with such short development times. Now in its sixth incarnation, Delphi has been the development tool for innumerable shareware and freeware applications, internal business and proprietary system applications, several well-known commercial applications, and even a commercial game or two. Delphi's power and ease of use make it a wonderful choice for a development platform that can stand up to C++ and Visual Basic in almost every situation.

One of Delphi's strengths is the Visual Component Library, Borland's object model. This object model has allowed the Delphi development team to encapsulate the vast majority of Windows programming tedium into easy-to-use components. Earlier Windows programming languages required the developer to write large amounts of code just to squeeze a minimal amount of functionality out of Windows. The mere act of creating a window and accepting menu selections could take pages of code to create. Delphi's excellent encapsulation of this dreary requirement of Windows programming has turned what once was a chore into a fun, exciting experience.

Windows Data Types

Windows API functions use a number of data types that may be unfamiliar to the casual Delphi programmer. These data types are all taken from the original C header files that define the Windows API function syntax. For the most part, these new data types are simply Pascal data types that have been renamed to make them similar to the original data types used in legacy Windows programming languages. This was done so that experienced Windows programmers would understand the parameter types and function return values, and the function prototypes would match the syntax shown in existing Windows API documentation to avoid confusion. The following table outlines the most common Windows data types and their correlating Object Pascal data type.

Table 1-1: Windows data types

Windows

Object Pascal

Description

Data Type

Data Type

LPSTR

PAnsiChar

String pointer

LPCSTR

PAnsiChar

String pointer

DWORD

LongWord

Whole numbers

BOOL

LongBool

Boolean values

PBOOL

~BOOL

Pointer to a Boolean value

PByte

~Byte

Pointer to a byte value

PINT

^Integer

Pointer to an integer value

PSingle

^Single

Pointer to a single (floating-point) value

PWORD

~Word

Pointer to a 16-bit value

PDWORD

^DWORD

Pointer to a 32-bit value

LPDWORD

PDWORD

Pointer to a 32-bit value

UCHAR

Byte

8-bit values (can represent characters)

PUCHAR

~Byte

Pointer to 8-bit values

SHORT

Smallint

Signed 16-bit whole numbers

UINT

LongWord

Unsigned 32-bit whole numbers

PUINT

~UINT

Pointer to unsigned 32-bit whole numbers

ULONG

Cardinal

Unsigned 32-bit whole numbers

PULONG

^ULONG

Pointer to unsigned 32-bit whole numbers

PLongint

^Longint

Pointer to 32-bit values

PInteger

^Integer

Pointer to 32-bit values

PSmallInt

^Smallint

Pointer to 16-bit values

PDouble

^Double

Pointer to double (floating-point) values

LCID

DWORD

A local identifier

LANGID

Word

A language identifier

THandle

LongWord

An object handle. Many Windows API functions return a value of type THandle, which identifies that object within Window's internal object tracking tables.

PHandle

^THandle

A pointer to a handle

WPARAM

Longint

A 32-bit message parameter. Under earlier versions of Windows, this was a 16-bit data type.

LPARAM

Longint

A 32-bit message parameter

LRESULT

Longint

A 32-bit function return value

HWND

LongWord

A handle to a window. All windowed controls, child windows, main windows, etc., have a corresponding window handle that identifies them within Windows' internal tracking tables.

HHOOK

LongWord

A handle to an installed Windows system hook

Windows

Object Pascal

Description

Data Type

Data Type

ATOM

Word

An index into the local or global atom table for a string

HGLOBAL

THandle

A handle identifying a globally allocated dynamic memory object. Under 32-bit Windows, there is no distinction between globally and locally allocated memory.

HLOCAL

THandle

A handle identifying a locally allocated dynamic memory object. Under 32-bit Windows, there is no distinction between globally and locally allocated memory.

FARPROC

Pointer

A pointer to a procedure, usually used as a parameter type in functions that require a callback function

HGDIOBJ

LongWord

A handle to a GDI object. Pens, device contexts, brushes, etc., all have a handle of this type that identifies them within Window's internal tracking tables.

HBITMAP

LongWord

A handle to a Windows bitmap object

HBRUSH

LongWord

A handle to a Windows brush object

HDC

LongWord

A handle to a device context

HENHMETAFILE

LongWord

A handle to a Windows enhanced metafile object

HFONT

LongWord

A handle to a Windows logical font object

HICON

LongWord

A handle to a Windows icon object

HMENU

LongWord

A handle to a Windows menu object

HMETAFILE

LongWord

A handle to a Windows metafile object

HINST

THandle

A handle to an instance object

HMODULE

HINST

A handle to a module

HPALETTE

LongWord

A handle to a Windows color palette

HPEN

LongWord

A handle to a Windows pen object

HRGN

LongWord

A handle to a Windows region object

HRSRC

THandle

A handle to a Windows resource object

HKL

LongWord

A handle to a keyboard layout

HFILE

LongWord

A handle to an open file

HCURSOR

HICON;

A handle to a Windows mouse cursor object

COLORREF

DWORD;

A Windows color reference value, containing values for the red, green, and blue components of a color

Handles

An important concept in Windows programming is the concept of an object handle. Many functions return a handle to an object that the function created or loaded from a resource. Functions like CreateWindowEx return a window handle. Other functions, like CreateFile, return a handle to an open file or, like HeapCreate, return a handle to a newly allocated heap. Internally, Windows keeps track of all these handles, and the handle serves as the link through the operating system between the object and the application. Using these handles, an application can easily refer to any of these objects, and the operating system instantly knows which object a piece of code wants to manipulate.

Constants

The Windows API functions declare literally thousands upon thousands of different constants to be used as parameter values. Constants for everything from color values to return values have been defined in the Windows.pas, Types.pas, ActiveX.pas, ShlObj.pas, ComObj.pas, and System.pas files. The constants that are defined for each API function are listed with that function within the text. However, the Windows.pas file may yield more information concerning the constants for any particular function, and it is a good rule of thumb to check this Delphi source code file when using complicated functions.

Strings

All Windows API functions that use strings require a pointer to a null-terminated string type. Windows is written in C, which does not have the Pascal string type. Earlier versions of Delphi required the application to allocate a string buffer and convert the string type to a PChar. However, since Delphi 3, we have a string conversion mechanism that allows a string to be used as a PChar by simply typecasting it (i.e., PChar(MyString), where MyString is declared as MyString: string). For the most part, this conversion will work with almost all Windows API functions that require a string parameter.

Importing Windows Functions

The Windows API is huge. It defines functions for almost every kind of utility or comparison or action that a programmer could think of. Due to the sheer volume of Windows API functions, some functions simply fell through the cracks and were not imported by the Delphi source code. Since all Windows API functions are simply functions exported from a DLL, importing a new Windows API function is a relatively simple process, if the function parameters are known.

Importing a new Windows API function is exactly like importing any other function from a DLL. For example, in earlier versions of Delphi, the BroadcastSystemMessage function was not imported by the Delphi source code. (It is now imported and available for use, but we'll use this function as an example.) In order to import this function for use within an application, it is simply declared as a function from within a DLL as:

Note: Use the stdcall directive, appended to the end of the function prototype, when importing Windows API functions.

Incorrectly Imported Functions

Some functions have been incorrectly imported by the Delphi source code. These exceptions are noted in the individual function descriptions. For the most part, the functions that have been imported incorrectly deal with the ability to pass NIL as a value to a pointer parameter, usually to retrieve the required size of a buffer so the buffer can be dynamically allocated to the exact length before calling the function to retrieve the real data. In Delphi, some of these functions have been imported with parameters defined as VAR or CONST. These types of parameters can accept a pointer to a buffer but can never be set to NIL, thus limiting the use of the function within the Delphi environment. As is the case with almost anything in Delphi, it is a simple matter to fix. Simply reimport the function as if it did not exist, as outlined above. Functions that have been imported incorrectly are identified in their individual function descriptions throughout this book.

Callback Functions

Another very important concept in Windows programming is that of a callback function. A callback function is a function within the developer's application that is never called directly by any other function or procedure within that application. Instead, it is called by the Windows operating system. This allows Windows to communicate directly with the application, passing it various parameters as defined by the individual callback function. Most of the enumeration functions require some form of application-defined callback function that receives the enumerated information.

Individual callback functions have specific parameters that must be declared exactly by the application. This is required so that Windows passes the correct information to the application in the correct order. A good example of a function that uses a callback function is EnumWindows. The EnumWindows function parses through all top-level windows on the screen, passing the handle of each window to an application-defined callback function. This continues until all top-level windows have been enumerated or function BroadcastSystemMessage(Flags: DWORD; Recipients: PDWORD;

uiMessage: UINT; wParam: WPARAM; lParam: LPARAM): Longint; stdcall;

implementation function BroadcastSystemMessage; external user32 name 'BroadcastSystemMessage';

As long as the parameters required by the function and the DLL containing the function are known, any Windows API function can be imported and used by a Delphi application. It is important to note that the stdcall directive must be appended to the prototype for the function, as this defines the standard mechanism by which Windows passes parameters to a function on the stack.

the callback function returns FALSE. The callback function used by EnumWindows is defined as:

EnumWindowsProc(

hWnd: HWND; {a handle to a top-level window}

lParam: LPARAM {the application-defined data}

A function matching this function prototype is created within the application, and a pointer to the function is passed as one of the parameters to the EnumWindows function. The Windows operating system calls this callback function for each top-level window, passing the window's handle in one of the callback function's parameters. It is important to note that the stdcall directive must be appended to the prototype for the callback function, as this defines the standard mechanism by which Windows passes parameters to a function on the stack. For example, the above callback function would be prototyped as:

EnumWindowsProc(hWnd: HWND; lParam: LPARAM); stdcall;

Without the stdcall directive, Windows will not be able to access the callback function. This powerful software mechanism, in many cases, allows an application to retrieve information about the system that is only stored internally by Windows and would otherwise be unreachable. For a complete example of callback function usage, see the EnumWindows function and many other functions throughout the book.

Function Parameters

The vast majority of Windows API functions simply take the static parameters handed to them and perform some function based on the value of the parameters. However, certain functions return values that must be stored in a buffer, and that buffer is passed to the function in the form of a pointer. In most cases, when the function description specifies that it returns some value in a buffer, null-terminated string buffer, or pointer to a data structure, these buffers and data structures must be allocated by the application before the function is called.

In many cases, a parameter may state that it can contain one or more values from some table. These values are defined as constants, and they are combined using the Boolean OR operator. The actual value passed to the function usually identifies a bitmask, where the state of each bit has some significance to the function. This is why the constants can be combined using Boolean operations. For example, the CreateWindowEx function has a parameter called dwStyle which can accept a number of constants combined with the Boolean OR operator. To pass more than one constant to the function, the parameter would be set to something like "WS_CAPTION or WS_CHILD or WS_CLIPCHILDREN." This would create a child window that includes a caption bar and would clip around its child windows during painting.

Conversely, when a function states that it returns one or more values that are defined as specific constants, the return value can be combined with one of the constants using the Boolean AND operator to determine if that constant is contained within the return value. If the result of the combination equals the value of the constant, then that constant is included in the return value.

Originally, software only needed a single byte to define a character within a character set. This allowed for up to 256 characters, which was more than plenty for the entire alphabet, numbers, punctuation symbols, and common mathematical symbols. However, due to the shrinking of the global community and the subsequent internationalization of Windows and Windows software, a new method of identifying characters was needed. Many languages have well over 256 characters used for writing, much more than a single byte can describe. Therefore, Unicode was invented. A Unicode character is 16 bits long and can therefore identify 65,535 characters within a language's alphabet. To accommodate the new character set type, many Windows API functions come in two flavors: ANSI and Unicode. When browsing the Windows.pas source code, many functions are defined with an A or W appended to the end of the function name, identifying them as an ANSI function or Wide character (Unicode) function. The functions within this book cover only the ANSI functions. However, the Unicode functions usually differ only in the type of string information passed to a function, and the text within this book should adequately describe the Unicode function's behavior.

Unicode £

Delphi vs. the Windows API

The Delphi development team did a world-class job of encapsulating the vast majority of important Windows API functionality into the VCL. However, due to the vastness of the Windows API, it would be impossible and impractical to wrap every API function in an Object Pascal object. To achieve certain goals or solve specific problems, a developer may be forced to use lower-level Windows API functions that are simply not encapsulated by a Delphi object. It may also be necessary to extend the functionality of a Delphi object, and if this object encapsulates some part of the Windows API, it will be the API that the developer will likely have to use to extend the functionality by any great amount.

Indeed, there are literally hundreds of APIs out there that dramatically extend Windows' functionality, and due to the sheer numbers of API functions and the ever changing, ever expanding functionality being introduced by Microsoft, it would be nearly impossible to actively import every last function from every available API. Therefore, it is important that the well-prepared and capable Delphi programmer is familiar with hardcore Windows programming, as it is highly likely that you'll be called upon sometime in your Delphi career to make use of some Windows API functionality that is not encapsulated by the VCL.

There may even be situations where it is impractical to use the Delphi components that encapsulate Windows functionality. The VCL makes Windows programming easy, but by its very nature, Delphi applications tend to be a minimum 350 KB in size. Bypassing the VCL and using direct Windows API calls, on the other hand, can yield a

Delphi application as small as 10 KB. Every situation is different, and fortunately, as Delphi programmers, we have a lot of flexibility in this area. Using direct Windows API calls may not always be necessary, but when it is, it's good to know that we have that option available to us.

Chapter 2

0 0

Post a comment