API
Using The Windows API In Delphi
- Introduction
- What Is The Windows API?
- Getting A Handle On Things
- Calling The API
- And now a 16-bit API
- Error status
- And now a 32-bit API
- Doing something useful
- Summary
- About Brian Long
If you find this article useful then please consider making a donation. It will be appreciated however big or small it might be and will encourage Brian to continue researching and writing about interesting subjects in the future.
Introduction
If you are currently a Borland Delphi developer, but you came from a background involving FoxPro or dBASE or Paradox or some other fourth generation language, there is a good chance that you find the Windows API scary. This is understandable - it is the underlying framework of the operating system and is documented for C and C++ programmers to understand. It's a bit like when the majority of PC developers used to program under DOS. In those days interrupts were the underlying operating system framework and they were scary. They were documented for assembler programmers. It's a small mercy that the Windows API is targeted at a 3GL audience (C/C++) and not a 2GL audience (assembler).
With sufficient readable information and a bit of practice it should be possible to reduce your fear of the API to a level where you merely find it daunting, but manageable. I hope to help you along that path with the information in this paper.
What Is The Windows API?
This is a sensible question as not everyone has the luxury of knowing these things. What is the Windows API and where does it come from? Well, Windows is cobbled together from a large number of Dynamic Link Libraries (DLLs) with a few drivers thrown in for good measure. Applications run on top of all this. When one writes a DLL, one is at liberty to, and indeed encouraged to, write functionality that can be accessed from outside the DLL. The functionality is written as functions and procedures (subroutines that either do or don't return values) and is said to be exported so that other binaries (applications or other DLLs) can gain access to it. So DLLs export functions and procedures (I'll refer to these generically as functions from now on).
The Windows API is a collection of functions that are exported by the DLLs that make up Windows. It is not the entire collection. Microsoft have chosen to reserve a large number of these functions for internal use, and they are undocumented. We should forget about those. The majority of Windows programmers are interested solely in those exported routines that Microsoft did document. It is those that are collectively termed the Windows API.
So, if the API is a collection of subroutines, what is the big deal? We deal with routines all the time in Delphi: Format, ShowMessage, Beep, Date etc. Where does the fear factor come in with the API? Well it is all to do the information that gets passed to and from these functions and which ones need to be used to do a given job. Because Windows is written in C (and C++) all the data types are types that mean something to C. And the parameters are passed into functions in a fashion that C programmers are familiar with. Of course a very likely reason you are using Delphi is that C seems far too difficult a language to get to grips with, what with all that memory management, pointers, macros, calling conventions and the like. As a consequence all these C declarations for APIs that we find in the Delphi help files are, well, more than off-putting.
Another issue is the sheer volume of them. There are hundreds of them, in fact probably thousands if you include all the add-on APIs that keep springing up: MAPI, TAPI, ISAPI, the cryptography API, and the list goes on and on.
The other factor that contributes to people shying away from them is the apparent complexity of the API. To do one thing, you have to call many APIs in appropriate combinations, with many parameters, some of which are records, whose data fields often need sensible initialisation. When I say "apparent complexity" it is worth noting that while in many cases in life appearances can be deceiving, in this case it is not. The API is complex. Oh dear, I'm scaring you away again. Have faith, we will get there (hopefully).
You need to look into a subject area before things tend to work. The API is designed with the intent of giving you totally flexible control (up to a point) in building up a Windows application. To give complete flexibility, there is an inherent level of complexity - typified by many parameters to a call, or many calls to achieve a task. Tools like Delphi are a joy to work with because someone else (tucked away in Borland R&D) has worked through all the heart-burn associated with calling all the necessary Windows API routines and wrapped them all up in objects and functions.
You do very little in Delphi for a big pay-off. You choose to make a new form in a Delphi project and it magically appears with you having to do hardly anything. Under the hood lots and lots of APIs are called to achieve this. Any Windows application has no choice but to call dozens and dozens of API calls in order to work. That is what a Windows application is - a binary file that calls API after API after API. The different combinations of many calls cause applications to look different and do different things, but the calls are always there at some level.
Before we get carried away on some eulogy, let's start looking at a few fundamentals.
Getting A Handle On Things
The vast majority of Windows routines take or return a "handle" of some sort. So what is a handle? It is a number. What is it for? It is there to help Windows do its housekeeping.
Windows maintains a large number of different structures in memory on behalf of Windows applications. Every window you see on the screen (and this includes controls such as buttons and listboxes) has an underlying data structure internal to Windows that describes it. Every icon has one too. So does every cursor, every memory bitmap, every block of memory, every drawing surface and so on.
Each time a window is created with an appropriate API call, another window structure is manufactured and stored in an appropriate storage location. When you ask Windows to do something to your window with an API, such as set focus to it, you have to identify the window in some way, to avoid confusing it with all the others. You uniquely identify your window by referring to its window handle. When Windows created the window (and the internal window data structure), it returned you a window handle. The same thing happens when any other item is manufactured in Windows - a handle is returned to identify it in future.
The handle is an integer that refers to a particular data structure in the collection of data structures that Windows maintains. How it refers to which structure is not important. In a similar way, if you ever did DOS file manipulation, you will know that you identify a particular file with a file handle. What the file handle (an integer as well) means is not important. What matters is that a file handle will refer back to a particular file.
Other APIs such as the BDE's IDAPI take the same approach. At the API level, you need a database handle or a cursor handle or some other handle in order to uniquely identify what you want to talk to.
Since Delphi applications are Windows applications, behind the scenes many APIs are called to get the thing working. Since Delphi applications have windows and icons and bitmaps and the all the other regular Windows facilities, it follows that there are handles floating around somewhere that can be used to identify them, if necessary. If you wish to use the API in some sense to manipulate something in your Delphi application you will need to use its handle. How do you get the handle of something in Delphi? All Delphi objects that represent something in Windows that has a handle, have a Handle property. This returns the handle.
So a form's Handle property and indeed the Handle property of all visual controls return a window handle, a TIcon's handle property returns an icon handle, a TBitmap's Handle returns a bitmap handle and so on. A Delphi TCanvas returns a handle for something called a device context - a drawing surface. The Delphi objects that represent BDE things also follow the same idea. A TTable and a TQuery both have a Handle property that return a cursor handle. A TDatabase's Handle property returns a database handle and a TSession's Handle property returns a session handle.
All handles that Windows bandies about are integers (some of the BDE handles are more involved, but we will ignore those). The generic handle type is a THandle, defined in Delphi 1 as a Word and in Delphi 2 and 3 as an Integer. In order to help distinguish between one type of handle and another, there are a whole host of other handle types as well. A HWnd is a window handle, a HFont is a font handle, a HBitmap is a bitmap handle, a HMenu is a menu handle, a HCursor is a cursor handle and so forth.
Calling The API
When you want to do something in Delphi that none of the components seem to cater for, then it is time to call the API. The principal problem here is that you need to know which, out of the colossal number available, are appropriate. Unfortunately the naming scheme employed by Microsoft for their APIs is not consistent, nor in many instances helpful. To give a few simple examples, if you want to launch another application you might think of searching for "Run," "Exec," "Execute" and so on. It is unlikely you would think of prefixing your search term with "Win" or "Shell." So it is equally unlikely that you would find WinExec or ShellExecute, some of a selection of APIs that are suitable for the job. Under Windows 3.1 there was an API officially called PrestoChangoSelector. It doesn't really conform to any particular naming convention.
So the point I am getting round to is to say that you may find difficulty in identifying the APIs that are right for the job in hand. This is where external help is required. You don't have any choice. You will need to consult either:
- a Windows API guru
- the Microsoft Developer Network (MSDN) Library CD
- appropriately helpful API-based programming books, such as the Windows API Programming Bible or the Windows 95 Win32 API Programming Bible, both published by Waite Group.
Once you find the API or APIs you need to call, you can then consult your Delphi help file. Delphi 1 users have a bit of an advantage here as the Windows API help has all the API declarations translated into Pascal syntax (albeit with a handful of mistakes). From Delphi 2 onwards the task of doing this each time Microsoft issued a new version of the API help file was found to be too onerous. So in 32-bit Delphi the declarations are written in C syntax.
Let's try a simple example. Consider the scenario where you have a project with two forms. Both are visible and your user is working in the first form. Under some circumstance - in our case a button being pressed - the other form needs to be brought to the foreground. Certainly you could call Form2.Show, but that is VCL stuff and the point is to examine the Windows API. There is a Windows API called BringWindowToTop which can be employed to do this. In Delphi 1's help it is declared (incorrectly) as:
procedure BringWindowToTop(Wnd: HWnd);
The description of the help proceeds to describe a return value of 0 or 1, but the routine is declared as a procedure. Well, in this case the person who translated the help declaration into Pascal got it wrong. It should be declared as:
function BringWindowToTop(Wnd: HWnd): Bool;
So the Delphi 1 user's advantage is already beginning to diminish. The immediately obvious question to ask ourselves is how do we find the correct declaration? The answer is to look in the API import unit that contains the declaration. Neither Delphi 1 Desktop nor Delphi 2 Desktop have the source code of the Run-Time Library or Visual Component Library supplied, but all other Delphi versions do. Either way it doesn't matter. We can find the information we need.
The most common APIs are declared in Delphi 1's WinProcs unit and all the Windows data types are in Delphi 1's WinTypes unit. In Delphi 2 and 3, these are merged into the rather large Windows unit. If you do not have the source code, you can open up the WINPROCS.INT or WINDOWS.INT file from Delphi's DOC subdirectory. Otherwise open up WINPROCS.PAS or WINDOWS.PAS from Delphi's SOURCE\RTL\WIN subdirectory. Search for BringWindowToTop and you will see the correct declaration as shown above. In Delphi 2 and 3 there are one or two minor differences, most notably the appearance of the word stdcall at the end of the declaration. All Win32 APIs feature this and it can be safely ignored.
The Delphi 2 help file declares the routine as:
BOOL BringWindowToTop(HWND hWnd);
This is C syntax for a function that returns a BOOL and takes a HWND type parameter called hWnd. The Delphi 2 declaration for the function in the Windows unit is:
function BringWindowToTop(hWnd: HWND): BOOL;
It is much the same, but shifted around into Delphi syntax.
One or two things should be explained here. The Delphi 1 help describes the return values as being 1 or 0 for success and failure respectively. The Delphi 2 help says the function returns True or False. In C, 0 means False and 1 means True. Boolean expressions resolve down to numbers. Booleans are not a separate type as they are in Pascal. The Pascal declaration of the routine declares the return type to be a Bool, a C-compatible Boolean type (equivalent to WordBool in Delphi 1 and LongBool in Delphi 2). It could equally have declared it to be Integer as the two types are the same size. However Bool makes more sense, so Bool it is.
The parameter taken by the API is a HWND, previously described as a window handle. So we can call the routine, passing in an appropriate window handle. Providing the handle is valid, the window represented by it will become the foreground window. Bear in mind the details about Delphi objects and their Handle properties an appropriate Delphi statement would be:
BringWindowToTop(Form2.Handle);
or perhaps
if not BringWindowToTop(Form2.Handle) then raise Exception.Create('Failed to bring to top');
The key point to remember here is the import unit gives the correct declaration of the APIs in Delphi syntax. It is an excellent reference to use when trying to cobble some API code together.
And now a 16-bit API
Now we will have a look at a more convoluted API, one that can be used to launch another application. ShellExecute is available in 16-bit Windows and is also available in the Win32 API, but you are advised against using it in a 32-bit application. It is only there for compatibility with old code - instead you should use the new ShellExecuteEx API that we will come up against after dealing with the 16-bit version.
The Delphi 1 help file says that ShellExecute comes not from the normal WinProcs unit, but from the ShellAPI unit. It gives a declaration of:
function ShellExecute(Wnd: HWnd; Operation, FileName, Parameters, Directory: PChar; ShowCmd: Integer): THandle;
which matches what is in the unit. Delphi 2 and 3's help lists this API in a more troublesome way:
HINSTANCE ShellExecute(HWND hwnd, // handle to parent window LPCTSTR lpOperation, // pointer to string that specifies operation to perform LPCTSTR lpFile, // pointer to filename string LPTSTR lpParameters, // pointer to string that specifies executable-file parameters LPCTSTR lpDirectory, // pointer to string that specifies default directory INT nShowCmd // whether file is shown when opened );
To identify what import unit to find the routine in, Delphi 2 and 3 users should check the QuickInfo popup on the API help page. It says the header file is called ShellAPI.H - read this as saying that the import unit is ShellAPI.PAS.
When faced with this declaration we immediately stop and wonder what a LPCTSTR or a LPTSTR is. Well, we should rush straight for the Pascal declaration in the ShellAPI import unit and we find much the same as with Delphi 1:
function ShellExecute(hWnd: HWND; Operation, FileName, Parameters, Directory: PChar; ShowCmd: Integer): HINST; stdcall;
So they were PChars all along. All string information required by Windows needs to be passed as a PChar, which is a C-compatible string format. Since a PChar is a pointer, it is probably easier to use a Char array whose elements start at zero. One of these can be used wherever a PChar is required. To change a Pascal string into a PChar, or zero-based Char array you can use StrPCopy. Delphi 2 users have it a bit easier here - it now allows you to typecast Pascal strings directly into PChars. One point to remember is that since a PChar is a pointer an empty PChar type string can be represented by nil.
Additionally you can just pass a literal string when a PChar is required - the compiler ensures that the string is stored in the right format when it sees the context in which it is used.
ShellExecute allows you to pass parameters that indicate which file you would like to launch, what parameters it takes, which directory it should start in and how the application should display its main window when it starts (the appropriate values for this can be found in the help for the ShowWindow API). It also takes a window handle to act as a parent window, whose usefulness is minor - passing any form window handle will do nicely. Additionally, ShellExecute understands file associations so you can ask it to run README.TXT and it will launch NotePad and get it to load the file. When you refer to some document with a registered file extension, you can ask it to open the document or print it (providing appropriate information has been registered for these purposes). This is what the operation parameter is for - if you pass "open" or nil, the file is opened in an appropriate program and if you pass "print" it will endeavour to print it.
The function returns an instance handle - a handle that identifies the launched instance of the application. In 16-bit Windows applications an instance handle uniquely identifies each instance as its value is the address of the instance's data segment. 16-bit apps all run in one address space but 32-bit apps run in separate address spaces. This means that all instance's instance handles end up with the same value - they are less than useful which is why you are advised to use the new Win32 APIs in Win32 apps.
If ShellExecute fails to launch the specified file it returns an error value less than or equal to 32 (for which there is a constant defined called HInstance_Error). The help describes the more likely values and what they mean.
An suitable example of a call to ShellExecute would therefore be:
if ShellExecute(Form1.Handle, nil, 'NotePad.EXE', 'C:\AUTOEXEC.BAT', nil, sw_ShowNormal) < HInstance_Error then raise Exception.Create('Problem launching file')
Don't forget to add ShellAPI to your uses clause.
Error Status
One of the common features of many Win16 APIs is that they return an error value upon failure that you can examine. In Win32, APIs tend to return False upon failure. To get more information you must call the GetLastError API which returns an error value. There are more 32-bit APIs which can take an error value and produce a descriptive message (although whether it is helpful is debatable). Delphi 2 and 3 have a function SysErrorMessage defined in the SysUtils unit which simplify the necessary calls. Additionally, Delphi 3 has a SysUtils routine called Win32Check which you can pass an API result to. If it is False, it will raise an EWin32Error exception which is given the descriptive message.
And Now A 32-bit API
Having seen the expanse of parameters for the 16-bit ShellExecute, let's have a look at the 32-bit version, ShellExecuteEx. The help declares this as:
WINSHELLAPI BOOL WINAPI ShellExecuteEx( LPSHELLEXECUTEINFO lpExecInfo // pointer to SHELLEXECUTEINFO structure );
It goes onto describe the parameter as a pointer to a SHELLEXECUTEINFO structure, which the help proceeds to tell us is a record containing more fields than a baker's dozen.
When API calls take record pointers there are a few things to know. C programmers write routines that take pointers when they wish to pass a parameter by reference. Of course in Delphi that is typically done by way of var parameters. As a consequence, many APIs that are described as taking record pointers will in Delphi take a var parameter whose type is the appropriate record. The general exceptions to this rule are when the record is optional, in which case the Pascal declaration is indeed a pointer. This means that the Delphi programmer can pass nil if the parameter is of no interest. ShellExecuteEx is an exception to these rules in that the record is required but it is declared to be a pointer to a record nevertheless.
To pass a record pointer, just declare and set up the record variable and pass the record to the routine as normal, but with an @ sign in front. Alternatively, pass the record to the Addr function which has exactly the same effect.
The other important thing regarding records is that often the record has one field that needs to definitely be initialised. This field needs to be set to the size of the record and the API will check this value against the size it is expecting and fail if the two don't match - it is a sort of simple API version control. Usually, this field will be called cbSize, cb, cbBytes, dwSize, dwLength or uSize.
Again, we need to look in the appropriate import unit (ShellAPI) to find that in Delphi, the routine is declared as:
function ShellExecuteEx(lpExecInfo: PShellExecuteInfo):BOOL; stdcall;
Further examination of the file reveals that PShellExecuteInfo is the same as PShellExecuteInfoA which is defined as a pointer to a TShellExecuteInfoA record which is defined as:
TShellExecuteInfoA = record cbSize: DWORD; fMask: ULONG; Wnd: HWND; lpVerb: PAnsiChar; lpFile: PAnsiChar; lpParameters: PAnsiChar; lpDirectory: PAnsiChar; nShow: Integer; hInstApp: HINST; { Optional fields } lpIDList: Pointer; lpClass: PAnsiChar; hkeyClass: HKEY; dwHotKey: DWORD; hIcon: THandle; hProcess: THandle; end;
The help for ShellExecuteEx has a link to the description of what C programmers know as a SHELLEXECUTEINFO record so we don't need to go into the fields, although it is worth mentioning that the 16-bit operation parameter is now referred to as a lpVerb. Also, hProcess returns the process handle (the useful 32-bit value that is analogous in some ways to the 16-bit instance handle).
A suitable call to ShellExecuteEx might look like this - a bit more arduous that ShellExecute:
var SEI: TShellExecuteInfo; ... FillChar(SEI, SizeOf(SEI), 0); // Wipe the record to start with with SEI do begin cbSize := SizeOf(SEI); fMask := see_Mask_NoCloseProcess; Wnd := Application.MainForm.Handle; lpFile := 'NotePad.EXE'; lpParameters := 'C:\AUTOEXEC.BAT'; nShow := sw_ShowNormal; if not ShellExecuteEx(@SEI) then raise Exception.Create('Problem launching file'); end;
Doing something useful
To finish this paper off, I will leave you with a useful unit (OneInst.Pas) that calls upon several APIs. The idea of this unit is to restrict an application to only one instance and if a second instance is started, the original one is brought back in focus, being restored if minimised. The business of checking if a previous instance is running is a no-brainer in 16-bit. There is a variable in any Delphi 1 app called HPrevInst which gives the instance handle of the previously running instance of the application, or zero if there wasn't one. In 32-bit the instance handles are meaningless as was mentioned earlier so a different approach is required.
To cater for this issue the unit attempts to create a named 32-bit synchronisation object (which are shareable through multiple applications) with a unique name for the application. If the object already exists then an instance already exists.
The tricky part arises when you need to bring the other app to the surface. You can bring any window to the surface if you have its window handle, so most of the code is devoted to locating the application window of the other instance (that is the hidden window maintained by the Application object, not the main form window). The application window has a caption as specified by Application.Title. When you set a custom application title in the project options dialog, the net effect is that an assignment to Application.Title is placed in the project source file. To make sure the EnsureSingleInstance procedure from the unit works properly, you should set some unique application title in the project, add the unit (OneInst.Pas) to the project and then call EnsureSingleInstance from the project source file (View | Project Source) immediately after the Application.Title assignment.
The implementation of EnsureSingleInstance looks like this:
procedure EnsureSingleInstance; var Wnd: HWnd; WndClass, WndText: array[0..255] of char; begin {$ifdef Win32} { Try and create a semaphore. If we succeed, then check } { if the semaphore was already present. If it was } { then a previous instance is floating around. } { Note the OS will free the returned semaphore handle } { when the app shuts so we can forget about it } if (CreateSemaphore(nil, 0, 1, PChar(ExtractFileName(Application.ExeName))) <> 0) and (GetLastError = Error_Already_Exists) then {$else} if HPrevInst <> 0 then {$endif} begin Wnd := GetWindow(Application.Handle, gw_HWndFirst); while Wnd <> 0 do begin { Look for the other TApplication window out there } if Wnd <> Application.Handle then begin { Check it's definitely got the same class and caption } GetClassName(Wnd, WndClass, Pred(SizeOf(WndClass))); GetWindowText(Wnd, WndText, Succ(Length(Application.Title))); if (StrPas(WndClass) = Application.ClassName) and (StrPas(WndText) = Application.Title) then begin { This technique is used by the VCL: post } { a message then bring the window to the } { top, before the message gets processed } PostMessage(Wnd, wm_SysCommand, sc_Restore, 0); {$ifdef Win32} SetForegroundWindow(Wnd); {$else} BringWindowToTop(Wnd); {$endif} Halt end end; Wnd := GetWindow(Wnd, gw_HWndNext) end end end;
GetClassName retrieves the window class of a window - we are looking for TApplication which will be returned by Application.ClassName. GetWindowText retrieves a window's caption - we are looking for Application.Title. To loop through all the windows, GetWindow is called in a loop to cycle through all the top-level windows.
If the original application instance is minimised, a request to restore it is sent via PostMessage and the window is brought into focus using SetForegroundWindow or BringWindowToTop depending upon target platform. I'll leave it to you to explore the help file to find out what all these APIs do and how they work.
Summary
To conclude this brief introduction to calling Windows APIs, remember that you will need to research things before you dive in. But the most important thing is to UTSL (Use The Source, Luke) - it is supplied so take advantage of it. With the source files and some appropriate background reading you can tackle the scary, I mean daunting, Windows API.
About Brian Long
Brian Long used to work at Borland UK, performing a number of duties including Technical Support on all the programming tools. Since leaving in 1995, Brian has spent the intervening years as a trainer, trouble-shooter and mentor focusing on the use of the C#, Delphi and C++ languages, and of the Win32 and .NET platforms. In his spare time Brian actively researches and employs strategies for the convenient identification, isolation and removal of malware. If you need training in these areas or need solutions to problems you have with them, please get in touch or visit Brian's Web site.
Brian authored a Borland Pascal problem-solving book in 1994 and occasionally acts as a Technical Editor for Wiley (previously Sybex); he was the Technical Editor for Mastering Delphi 7 and Mastering Delphi 2005 and also contributed a chapter to Delphi for .NET Developer Guide. Brian is a regular columnist in The Delphi Magazine and has had numerous articles published in Developer's Review, Computing, Delphi Developer's Journal and EXE Magazine. He was nominated for the Spirit of Delphi award in 2000.