Windows 32 API Introduction
by: Delax
Published: May 2004
©opyright 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 mesage
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 concole 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 explaination. 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 independant 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(
LPCTSTR lpClassName,
LPCTSTR lpWindowName,
DWORD dwStyle,
int x,
int y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU hMenu,
HINSTANCE hInstance,
LPVOID lpParam);
And, as usual, now the step-by-step explaination.
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 scrollbar" 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 scrollbars, 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. Normaly, 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 orginal 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 thei 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 ourself! 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++ the WinProc has to be
the first procedure (but has to be below the WinMain, but that's a C/C++ problem.
// 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!