Win32 API Introduction
by: DelaxPublished: May 2004
Copyright 2009 by Friends of FPC
Preface
This is an introduction to the Windows 32 API. You should know how to code in Pascal as I will not cover the basics like "what is a pointer" and stuff. But you don't need to know anything about the Windows internals. We start off with looking at Windows classes, event handling and so on. Then we will cover controls, output and stuff like that.
NOTE: This is a complete rewrite from the last version of the Win32/FPC tutorial. So if you know the old one you still might want to look at this as some major mistakes were fixed and the tutorial has now completely different topics.
Introduction
This tutorial is for all Pascal programmers that have some experience with coding for DOS and want to start programming for Windows. Therefore, I won't go into topics like "how does if/then work?" or "what is an integer?" but focus on Windows instead. If you run into trouble, please refer to any Pascal tutorial.
As English is not my native language I apologize in advance for any kind of mistakes.
For this tutorial you will need the FPC (Free Pascal Compiler). If you don't have it installed view my Free Pascal Getting Started. You should also read some FPC Docs first and get comfortable with the compiler.
Also a good thing to have is the "Win32 SDK Reference Help" from Microsoft. It is a Windows Help file and contains almost every API call. All the important ones anyway. Plus you can implement the help file in your favorite Windows Editor/FPC IDE so that you have a handy online help for coding Windows applications. A small drawback is the size of 20 MB (13 MB zip), but it's worth it.
By writing Windows 32 I mean every kind of Windows 32 system: Windows 95, 98, ME as well as Windows NT4, 2000, XP, 2003, Vista, 2008 and 7. As long as you stick close to the API you should have no problems to get your programs work with new Windows versions. Sometimes there are some differences between the Win9x (95, 98, ME) and the NT series (NT4, 2000, XP, Vista, etc) systems. If this is the case I will show both versions.
All sources of this tutorial are tested with Windows 98 SE, Windows NT4 SP6, Windows 2000 SP2 and Windows XP SP1. Well, so much for now, let's get going!
Andre LaMothe once said: "Windows programming is like going to the dentist: You know it's good for you, but no one likes doing it." I avoided Windows programming for a long time but as the time came, I spent 3 weeks in hell and now I'm actually liking it. And let me tell you this right from the start: it is different than I thought.
If we say "Windows", we mean some kind of Win32 system: Windows 95, 98, ME, NT4, 2000, ... These are Operating Systems, are able to do multitasking and multithreading. Multitasking means that the OS can execute multiple applications at the same time. Oh, and while we are at it, all Windows programs are applications. Multithreading means that every application can be composed of several smaller threads, that will also be executed at the same time. Right. Let's get real now...
The core of a Windows system is the so called "Scheduler". The Scheduler just waits until a new application starts. This new application gets a number and is added to the "Process List". The Scheduler is then going through the Process List and gives every application a small time slice to run. This is happening so fast that everything seems to run at the same time. If you are curious: you can take a look at the process list in an NT-based Windows by pressing CTRL, ALT, DEL and choosing "Task Manager" and "Process List".
To be honest: in reality it is a bit more complicated (as we will see later in this tutorial), but for now it has everything you need to know.
Windows is also "event driven". It means that, for Windows, everything that happens is an event which is processed as a message. Let's try it with an example: A user presses a key on the keyboard. Windows is aware that an event (key pressed) is happening and sends a message to your application "Hey, a key is pressed". Your application has to deal with this information and you have to make the choice which messages will be processed and which are ignored.
For Windows to send messages to your application, you will need some part of your application to handle these events. But more on this subject later on. For now let's take a look at a simple Windows program.
{$APPTYPE GUI} {$MODE DELPHI} program WinHello; uses Windows; begin MessageBox(0, 'Hello World', 'Hi there!', mb_Ok); end.
This application creates a small Message Box with the message "Hello World" (as usual). Compile it and take a look. Oh, and another thing. To compile it properly you have to tell the compiler that you want to build a Windows program. To do so, just add the parameter "-TWin32" to your fpc call. Your command line should now look something like this: "fpc source01.pp -TWin32". If you use EditPlus or some other Editor, you only need the "-TWin32" in the argument line.
MessageBox Hello World

$APPTYPE GUI
- APPTYPE shows what kind of application we want to create. (GUI shows we want an application on the Windows GUI, not a console application).$MODE DELPHI
- We then change fpc dialect to Delphi Mode.
MessageBox(0, 'Hello World', 'Hi there!', mb_Ok);
For this line we need a little more explanation. MessageBox is a call for a Windows Message Box, followed by the parameters. These parameters define the look and behavior of the box. The Win32 SDK Reference has the following to say about a standard Windows Message Box:
MessageBox(hwnd, lptext, lpcaption, utype);
Let's go through the parameters.
hwnd - In our case set to 0. It's the handle of the Box and defines which application the owner of the box is. By setting it to 0 we choose the top window: the Windows Desktop.
lptext - This is the text to put out in the Message Box.
lpcaption - This is the title of the box. It appears in the title bar.
utype - This is the parameter to have fun with. Here you can define what kind of buttons your Message Box has (OK, Cancel, Retry, ...), what kind of icon is shown (question marks, ...) and many more things to try out. I will not show them here as there are too many. For a complete list, take a look at the Win32 SDK Reference Help.
Well, that was a simple Windows application. Take a break, compile it, read the SDK Reference and fool around with the parameters.
Now we will take a closer look at a typical Windows application. First on the list: Windows Classes.
Every Window, Dialog Box, Message Box or Fullscreen Window has a Windows Class. This class defines the look and the behavior of a window. Of course there are standard classes for nearly every purpose, but in reality you want to use your own Window Class for your application.
I won't cover Window Classes in detail as it would take about 5 chapters and many lists of parameters to cover every aspect. Instead, I will only talk about the basics here and we'll go deeper into this matter as the tutorial is proceeding.
Previously we had a look at a simple "Hello World" application. FPC has it's own "Hello World" application. But the FPC Version is a little more complicated. Let's have a look.
{{ $Id: winhello.pp,v 1.4 2002/09/07 15:06:35 peter Exp $ Copyright (c) 1996 by Charlie Calvert Modifications by Florian Klaempfl Standard Windows API application written in Object Pascal. No VCL code included. This is all done on the Windows API level. } {$APPTYPE GUI} {$MODE DELPHI} program WinHello; uses Strings, Windows; const AppName = 'WinHello'; function WindowProc(Window: HWnd; AMessage: UINT; WParam : WPARAM; LParam: LPARAM): LRESULT; stdcall; export; var dc : hdc; ps : paintstruct; r : rect; begin WindowProc := 0; case AMessage of wm_paint: begin dc:=BeginPaint(Window,@ps); GetClientRect(Window,@r); DrawText(dc,'Hello world by Free Pascal',-1,@r, DT_SINGLELINE or DT_CENTER or DT_VCENTER); EndPaint(Window,ps); Exit; end; wm_Destroy: begin PostQuitMessage(0); Exit; end; end; WindowProc := DefWindowProc(Window, AMessage, WParam, LParam); end; {{ Register the Window Class } function WinRegister: Boolean; var WindowClass: WndClass; begin WindowClass.Style := cs_hRedraw or cs_vRedraw; WindowClass.lpfnWndProc := WndProc(@WindowProc); WindowClass.cbClsExtra := 0; WindowClass.cbWndExtra := 0; WindowClass.hInstance := system.MainInstance; WindowClass.hIcon := LoadIcon(0, idi_Application); WindowClass.hCursor := LoadCursor(0, idc_Arrow); WindowClass.hbrBackground := GetStockObject(WHITE_BRUSH); WindowClass.lpszMenuName := nil; WindowClass.lpszClassName := AppName; Result := RegisterClass(WindowClass) <> 0; end; {{ Create the Window Class } function WinCreate: HWnd; var hWindow: HWnd; begin hWindow := CreateWindow(AppName, 'Hello world program', ws_OverlappedWindow, cw_UseDefault, cw_UseDefault, cw_UseDefault, cw_UseDefault, 0, 0, system.MainInstance, nil); if hWindow >< 0 then begin ShowWindow(hWindow, CmdShow); ShowWindow(hWindow, SW_SHOW); UpdateWindow(hWindow); end; Result := hWindow; end; var AMessage: Msg; hWindow: HWnd; begin if not WinRegister then begin MessageBox(0, 'Register failed', nil, mb_Ok); Exit; end; hWindow := WinCreate; if longint(hWindow) = 0 then begin MessageBox(0, 'WinCreate failed', nil, mb_Ok); Exit; end; while GetMessage(@AMessage, 0, 0, 0) do begin TranslateMessage(AMessage); DispatchMessage(AMessage); end; Halt(AMessage.wParam); end. {{ $Log: winhello.pp,v $ Revision 1.4 2002/09/07 15:06:35 peter * old logs removed and tabs fixed Revision 1.3 2002/02/22 13:37:49 pierre * fix problem if started through cygwin bash }
OK. Take a deep breath and don't panic. Right now we are only interested in the part "Register the Windows Class".
{{ Register the Window Class } function WinRegister: Boolean; var WindowClass: WndClass; begin WindowClass.Style := cs_hRedraw or cs_vRedraw; WindowClass.lpfnWndProc := WndProc(@WindowProc); WindowClass.cbClsExtra := 0; WindowClass.cbWndExtra := 0; WindowClass.hInstance := system.MainInstance; WindowClass.hIcon := LoadIcon(0, idi_Application); WindowClass.hCursor := LoadCursor(0, idc_Arrow); WindowClass.hbrBackground := GetStockObject(WHITE_BRUSH); WindowClass.lpszMenuName := nil; WindowClass.lpszClassName := AppName; Result := RegisterClass(WindowClass) <> 0; end;
There you have a typical Windows Class stucture. A Windows Class is much like a structure or a record that has a couple of values that define the class itself. So we just store our values (how we would like the class to be) in the fields of the structure and that's it! Let's have a look at the fields.
style: This field has a ton of possible parameters. If you want them all take a look at the SDK Reference. Here are the "most common" ones:
CS_REDRAW {{redraws a Window if a user moved it};
CS_VREDRAW / CS_HREDRAW {{redraws a Window if the user changed it's size};
CS_OWNDC {{important - here we get an independent Device Context for our window. More on that later};
CS_NOCLOSE {{deactivates the "Close Window" icon};
CS_DBLCLKS {{sends a message if the user double clicks in our Window}.
lpfnWndProc: This is a pointer to our WinProc. What a WinProc is? Remember what I said previously about Windows being event driven and that our application would need to process the messages sent by the Operating System? Well, the WinProc is the part of your application that processes all received Windows messages. You need to define the name of your message handling routine, that's all. Again: more on this when we get to Windows messages.
cbClsExtra and cbWndExtra: not important right now. Set to 0.
hInstance: complicated. Let's just say the hInstance is a handle for Windows to identify your application. Remember the things about the Scheduler giving every application a number and putting them into a list? Here you go.
hIcon: Yaaaay! Something familiar. This is the icon of our application. We'll take the standard icon for now.
hCursor: The mouse cursor used in our Window. We'll take the standard cursor.
hbrBackground: This is the background color of our window. But it would be too easy just to enter some RGB values. The GDI (Graphical Device Interface) is a bit more complicated and so we have to enter a brush defined by the GDI interface. More on the GDI later.
lpszMenuName: This would be the name of the menu of our window - if we had one. We don't, so there is no need for a name.
lpszClassName: Now we need a name for our shiny new Window Class. Anything will do.
And that's it! Look at it again and try to understand it.
WindowClass.Style := cs_hRedraw or cs_vRedraw; WindowClass.lpfnWndProc := WndProc(@WindowProc); WindowClass.cbClsExtra := 0; WindowClass.cbWndExtra := 0; WindowClass.hInstance := system.MainInstance; WindowClass.hIcon := LoadIcon(0, idi_Application); WindowClass.hCursor := LoadCursor(0, idc_Arrow); WindowClass.hbrBackground := GetStockObject(WHITE_BRUSH); WindowClass.lpszMenuName := nil; WindowClass.lpszClassName := AppName;
Yeah, I know this is boring stuff, but it is important. No Windows application without these basics! So get yourself a Coke or whatever and try to understand what is happening here. Next, you will learn what you can do with your Window Class.
To use a Window Class you have to register it. This is actually quite easy and is done with one little line of code:
Result := RegisterClass(WindowClass) <> 0;
RegisterClass(WindowClass) registers a Window Class. As parameter we have to set a name of a valid Window Class. We store any messages in Result thus we are able to deal with any errors that may occur.
Using the FPC Hello World source, we are again only interested in a special part of the code. "Create the Window Class".
{{ Create the Window Class } function WinCreate: HWnd;var hWindow: HWnd; begin hWindow := CreateWindow(AppName, 'Hello world program', ws_OverlappedWindow, cw_UseDefault, cw_UseDefault, cw_UseDefault, cw_UseDefault, 0, 0, system.MainInstance, nil); if hWindow >< 0 then begin ShowWindow(hWindow, CmdShow); ShowWindow(hWindow, SW_SHOW); UpdateWindow(hWindow); end; Result := hWindow; end;
The title is quite confusing, but we are actually creating a Window here. Let's have a look what the SDK Reference has to say about creating windows.
CreateWindow( LPCTSTRlpClassName, LPCTSTRlpWindowName, DWORDdwStyle, intx, inty, intnWidth, intnHeight, HWNDhWndParent, HMENUhMenu, HINSTANCEhInstance, LPVOIDlpParam);
And, as usual, now the step-by-step explanation.
lpClassName: This is the name of the Window Class we want to use. As you (should!) remember, we gave our Window Class a name at the end.
lpWindowName: This is the title of your window.
dwStyle: Important. This defines the behavior of your window. You can define things like "Window is closed at startup", "Window is a Pop-Up", "Window has a scroll bar" and so on.
x and y: These are the X/Y coordinates of the top-left corner of your window in pixels. If it's not important where your window is located, take CW_USEDEFAULT and Windows will choose a place.
nWidth and nHeight: This is the height and width of your window in pixels. CW_USEDEFAULT is the alternative. Important: The height and width of your window is including the scroll bars, window title and stuff like that. A window defined as 640x480 is NOT 640x480 inside (canvas), but the size of the complete window is 640x480 from edge to edge.
hWndParent: This is a handle to the parent window (if there is one). Most of the applications will have the Windows Desktop as parent window. However, think about error messages of your own application. These will have your application as parent.
hMenu: Here you can define a menu for your window. Nope, we won't do that for now.
hInstance: Again the handle of our application.
lpParam: These are advanced parameters for creating the window or starting your application.
In theory this created a window. However, it is better to check than to be sorry.
if hWindow <> 0 then begin ShowWindow(hWindow, CmdShow); ShowWindow(hWindow, SW_SHOW); UpdateWindow(hWindow); end;
That's it. You now know how to create a window.
FPC's Hello World (scaled to fit)

Don't get completely excited as you cannot write Windows applications just yet. Only one little detail is missing: event handling. Receiving and processing Windows messages, remember?
Windows is event driven. We heard that line before, but I will cover it again. Windows is sending messages to every running application to inform them what's happening. "A key is pressed", "The screen saver is starting", ... I said that your application needs to process these messages. Now we get there.
The event handler of your application is written by the programmer (most likely you), but don't worry! You won't have to handle every single message your application receives. Your GFX Demo for example is not interested in incoming mails, is it? In real life you only need to look after a couple of messages and ignore the rest.
Let's look back at your Window Class. You defined some kind of event handler back there, right? With WindowClass.lpfnWndProc := WndProc(@WindowProc); you actually defined a pointer to the event handling procedure we want to write now. Your window will use the given WinProc as the default event handler.
Windows does not send any messages directly to your program by the way. It is storing the messages in a buffer and they stay there until they get processed.
To be crystal clear on this issue: Windows encounters an event. Windows sends a message notification. The message is put in buffer. Your application looks in the buffer. If the message is "interesting", process it. If not, let Windows deal with it.
Let's look at a simple WinProc.
function WindowProc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam )
hwnd: This is the window handle. Normally, it's only interesting if we open more than one window with one Window Class. hwnd then looks which window sent the message.
msg: This is the original message. Well...at least an ID of a message.
Wparam and Lparam: Message parameters that are given by the system.
LResult Callback is important. Don't forget it. Anyway, the whole thing is going like this: We get the message, look if the msg ID is interesting and if that we process it further. Now for a small selection of IDs.
WM_ACTIVATE {{The window is activated}
WM_CLOSE {{The window is closed}
WM_CREATE {{The window is created}
WM_DESTROY {{The windows is destroyed}
WM_MOUSEMOVE {{The mouse is moving}
Of course these are only the smallest tip of the iceberg. Take a look at the SDK Reference to get them all. Remember - there is a message for every possible event that can happen in a Windows operating system. And then some.
To make things as easy as possible we are only interested in only one message: WM_DESTROY. This message is sent if the user has closed our window. Remember: if the user closes the window our program is NOT terminated automatically! We have to catch the message and terminate our program our self! Here is an example WinProc:
function WindowProc(Window: HWnd; AMessage, WParam, LParam: Longint): Longint; stdcall; export; begin WindowProc := 0; case AMessage of wm_Destroy: begin PostQuitMessage(0); Exit; end; end; WindowProc := DefWindowProc(Window, AMessage, WParam, LParam); end;
In the "WinHello.pp", the WinProc is looking a bit different. Here we need to draw the "Hello World" in our Window, but we don't need that for this example.
And that's it? Hmm, yes this is a simple WinProc. But did you notice that we are not getting our messages from anywhere? We have to do that in the WinMain. The WinMain is our main loop. Remember our first program? It consisted only of a main loop.
while GetMessage(@AMessage, 0, 0, 0) do begin TranslateMessage(AMessage); DispatchMessage(AMessage); end;
So what are we doing there? GetMessage gets the message from the buffer and stores it in AMessage. It will then be translated and processed. Looks simple, eh?
Now, that is it. Finally! We have all the basics to start hacking a little application together. Relax, read the section again and then on with the show.
Now we will have some fun! We put all the basics together and create a working Windows application. All the different parts will be put in seperate procedures to avoid confusion and we'll add some error handling to track bugs.
Let's take a look at the variable section.
{$APPTYPE GUI} {$MODE DELPHI} program Win32_Source03; uses windows; const AppName = 'Win32/ FPC - Source 03'; VAR active : BOOLEAN; msg : MSG; hWindow : hwnd;
The unit Windows should be self explaining. active is a simple variable to check if the user has terminated our application. msg is a variable where the Windows messages will be stored in. hWindow will be the handle of our window.
The program starts with a little procedure to handle errors that might occur in our program. Whenever an error occurs, we call this procedure that puts out an error message and terminates the application.
// Try, Throw, Catch mechanism. Simple proc to display given errors. // procedure ThrowError(pcErrorMessage : pChar); begin MessageBox(0, pcErrorMessage, 'Error', MB_OK); Halt(0); end;
Now for the function to handle Windows messages - the WinProc. In C/C Math parser error: Expected expression. Position in string:5
// WinProc to handle Windows messages. // function WindowProc(hEventWindow : hwnd; msg : DWORD; wParam : WPARAM; lParam : LPARAM) : LRESULT; export; begin case (msg) of WM_ACTIVATE : begin active := true; end; WM_DESTROY : begin active := false; PostQuitMessage(0); Exit; end; WM_KEYDOWN : begin active := false; end; else WindowProc := DefWindowProc(hEventWindow, msg, wParam, lParam); end; end;
All right. We are checking for 3 events: WM_ACTIVATE, WM_DESTROY and WM_KEYDOWN. WM_ACTIVATE is sent if our window is opened. In this case we set the active variable to true. If WM_DESTROY is sent, we'll terminate the program. The same happens if the user presses any key.
Next, we need a Windows Class.
// Register the Window Class // procedure RegisterWindow(); var WinClass: WndClassEx; begin WinClass.cbSize := Sizeof(WndClassEx); WinClass.Style := cs_hRedraw OR cs_vRedraw; WinClass.lpfnWndProc := WndProc(@WindowProc); WinClass.cbClsExtra := 0; WinClass.cbWndExtra := 0; WinClass.hInstance := system.MainInstance; WinClass.hIcon := LoadIcon(0, idi_Application); WinClass.hCursor := LoadCursor(0, idc_Arrow); WinClass.hbrBackground := GetStockObject(LTGRAY_BRUSH); WinClass.lpszMenuName := nil; WinClass.lpszClassName := 'WindowClass'; WinClass.hIconSm := LoadIcon(0, IDI_APPLICATION); if RegisterClassEx(WinClass) = 0 then ThrowError('Registering the Windows Class failed!'); end;
Note, that we don't use WndClass, but WndClassEx. The "Ex" is for extended and was introduced with Windows 98. Windows 95 and NT4 do not know the Ex structure, on the other hand: it does not hurt either as older versions of Windows just use the normal Window Class structure instead. And it's the modern version, so we will use it from now on.
Also note, we are checking if the Window Class has been correctly registered. If not we throw an error.
Now we'll create the Window.
// Create the window. Throw error if this fails. // procedure CreateWindow(); begin hWindow := CreateWindowEx( WS_EX_TOPMOST, 'WindowClass', AppName, WS_CAPTION OR WS_POPUPWINDOW OR WS_VISIBLE, 0, 0, 640, 480, 0, 0, system.MainInstance, NIL); if hWindow <> 0 then begin ShowWindow(hWindow, CmdShow); ShowWindow(hWindow, SW_SHOW); UpdateWindow(hWindow); end else ThrowError('Window could not be created'); end;
We'll use CreateWindowEx because we also used the extended Window Class. We also check if our window has been created properly. If the Window could not be created we'll throw an error message.
// Destroy the window. // procedure KillWindow(); begin DestroyWindow(hWindow); end;
Here the window is killed and the window handle is freed again. This should be done for a really clean escape. Now for the main loop.
// Main // begin active := false; RegisterWindow(); CreateWindow(); // Main loop // repeat if PeekMessage(@msg,0,0,0,0) = TRUE then begin // if message waiting then get it GetMessage(@msg,0,0,0); TranslateMessage(msg); DispatchMessage(msg); end; // -------- enter main loop code here -------- until active = false; KillWindow(); end.
First, we set active to false. Why? Because if the window is created properly, active will become true anyway. If it is not created, the program will terminate itself automatically.
Now we call our procedures to create the Window Class and the window. Then we start the actual main loop. In this case it is a Repeat/Until loop.
As of right now we only do the message handling here, but in a real application we would do all our main loop work here. As we will see most of the code is done in the WinProc actually, depending on the type of application. In our little program we only wait if there is a quit or key pressed message so we can leave the Repeat/Until loop and therefore terminate our program.
Yaaaaay! We did it! We opened a Window!
These are popular related words:
none.