Microsoft Windows API C++ applications
This article demonstrates the main aspects involved to building Windows applications with MVS using the Windows API.
Windows Data types
Both Windows API and MFC (Microsoft Foundation Classes) derived applications make use of Windows data types, which map to C++ types. Some (not all) are tabulated below:
| Windows data type | Description |
|---|---|
| BOOL or BOOLEAN | A boolean that can be either TRUE or FALSE (uppercase, contrast to true and false in C++) |
| BYTE | An 8-bit byte |
| CHAR | An 8-bit character |
| DWORD | A 32-bit unsigned integer, equivalent to unsigned long in C++ |
| HANDLE | A 32-bit integer value that records the location of an object |
| HBRUSH | A handle to a brush (a brush fills an area with colour) |
| HCURSOR | A handle to a cursor |
| HDC | A handle to a device context (an object that outputs data to a screen or printer) |
| HINSTANCE | A handle to an instance (running application) |
| LPARAM | A message parameter |
| LPCSTR | A pointer to a constant null-terminated string of 8-bit characters |
| LPHANDLE | A pointer to a handle |
| LRESULT | A signed value that results from processing a message (a message represents an application event) |
| WORD | A 16-bit unisgned integer, equivalent to an unsigned short in C++ |
All above types are contained the header file windows.h.
Hungarian notation
Before the days of strict type-checking at compile-time, programmers would attempt to resolve type misuse by prefixing variable names with letters to indicate their type. This Hungarian notation is largely not required anymore with modern C++ compilers that check for valid type handling. The notation is however still prevalent in Windows application code.
| Prefix | Variable type/intention |
|---|---|
| b | BOOL, equivalent to int |
| by | unsigned char; a byte |
| c | char |
| dw | DWORD; an unsigned long |
| fn | a function |
| h | a handle |
| i | int |
| l | long |
| lp | long pointer |
| n | int |
| p | a pointer |
| s | a string |
| sz | a zero terminated string |
| w | WORD; unsigned short |
Windows applications
Windows recognises and calls two functions in a C++ project:
WinMain()- where execution begins and endsWindowProc()- Windows message handling with the application
WinMain()
The function has the prototype (note the Hungarian notation applied):
int WINAPI WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow
)
The parameters:
hInstance- a unique handle (32-bit integer) for the instancehPrevInstance- for 16-bit applications, a handle to the previous instance of the application; this is always NULL for 32-bit applicationslpCmdLine- a pointer to a command line instruction that ran the applicationnCmdShow- the display mode of the application e.g. normal, minimised, maxmised.
The function returns an instance of WINAPI, which is required for all C++ applications on Windows.
Defining the sort of window to create: WNDCLASSEX
The C/C++ structure WNDCLASSEX defines what sort of window to create. The naming CLASS is not a C++ class, but an MFC class, a representation of a window. Historically, WNDCLASSEX succeeds (implements additional parameters) an older structure, WNDCLASS.
An instance of WNDCLASSEZ is constructed in WinMain(), and so have access to its parameters.
The fields to focus on for now are commented:
struct WNDCLASSEX {
UINT cbSize;
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
HICON hIconSm;
}
// construct an instance of WNDCLASSEX:
WNDCLASSEX windowClass;
//initialise specific fields
// size of the structure object
windowClass.cbSize = sizeof(WNDCLASSEX);
// determine behaviour e.g. when the window should
// be redrawn (in this case, when both the horizontal
// and vertical dimensions have changed);
// the bitwise OR is applied as both options (flags) are have null, true or
// false states (in this case as 32-bit words set to 1 for true)
// so here windowClass.style would be 1 when there was a change
// to the dimensions, and 0 at all other times
windowClass.style = CS_HREDRAW | CS_VREDRAW;
// set a pointer to a function that handles messages i.e. WindowProc
windowClass.lpfnWndProc = WindowProc;
// pass WinMain's hInstance as the current instance's value
windowClass.hInstance = hInstance;
// the following window elements (icon, cursor and brush)
// can be set to null, after which
// Windows will apply defaults (standard UI elements); the following
// explicit calls are equivalent to null passed
windowClass.hIcon = LoadIcon(0, IDI_APPLICATION);
windowClass.hCursor = LoadCursor(0, IDC_ARROW);
windowClass.hbrBackground = static_cast<HBRUSH>(GetStockObject(GREY_BRUSH));
// set the name that identifies this classification of window
static char szAppName[] = L"someName"; // the L prefix is not a typo
windowClass.lpszClassName = szAppname;
Registering WNDCLASSEX object with Windows
The Windows API function RegisterClassEx() can be used the register the WNDCLASSEX object (or registering the window classification) with Windows. Historically, the function RegisterClass() can be used to register objects of the older WNDCLASS strcuture.
Getting a reference to the window created
The Windows API function CreateWindow() can be called after window registration and return a reference to the window. The reference can be useful later. Note this does not mean the window is drawn or shown yet.
HWND hWndAlpha;
//...
hWndAlpha = CreateWindow(
szAppname, // allows Windows to find the registered window
L"The Window title goes here", // the L prefix is not a typo
WS_OVERLAPPEDWINDOW, // this defines what sort of window components to show
CW_USEDEFAULT, // the next four are about window size and position
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
0, // 0 = this is a parent window (no handle); pass the parent of the parent handle if required
0, // 0 = no menu required
hInstance, // this would come from WinMain for this application
0 // 0 = simple window layout (single document inteface SDI); multi-document interface (MDI) discussed later
);
Showing the window
The Windows API function ShowWindow() then draws the window on the screen.
// note aforementioned parameters, the reference to the created window and WinMain's nCmdShow
ShowWindow(hWndAlpha, nCmdShow);
At present, this will show the window but without application content. The code to draw the content (in an area known as the client area) is normally defined in WindowProc(). Then, call UpdateWindow(hWndAlpha) to get Windows to refer to that code and draw the content.
// invoke code (trigger an event/send a message) from WindowProc()
UpdateWindow(hWndAlpha);
We cover Windows application messaging in more detail first before returning to updating the application content.
Windows Messages
Messages are either
- Queued (e.g. user interaction; these are queued in
WinMain()) - Non-queued
Messages are handled as follows:
- Process queued messages (if they exist) in WinMain()
- Ask Windows to call WinProc() (this doesn’t happen automatically) to deal with the message. By this point, the message isn’t in a queue anymore.
The WinMain() message loop can take the form given below:
MSG msg;
while (GetMessage(&msg, 0, 0, 0) == TRUE){
// perform any (keyboard input) conversion of the message if required
TranslateMessage(&msg);
// get Windows to call WindowProc() to deal with the message
DispatchMessage(&msg);
}
The variable msg above is an example of a Windows message (C/C++) structure:
struct MSG {
HWND hwnd; // handle to the relevant window
UINT message; // message ID, based on standard actions e.g. WM_PAINT, WM_QUIT
WPARAM wParam; // note this is not a WORD, despite the "w" in "wParam"
LPARAM lParam;
DWORD time; // when messsage was queued
POINT pt; // mouse position
}
The function GetMessage() always returns true when a messsage to quit hasn’t been queued or there is no error involved.
GetMessage(
&msg, // stores the message content, via a reference, for a queued message found
0, // 0 = retrieve all message for an application, regardless of the no. of windows created
0, // these last two params indicate boundaries to message IDs, which allow Windows to focus on specific actions
0
);
Setting the second parameter to GetMesage() is preferred. If the application is composed of multiple windows and the second parameter to GetMessage is assigned to a particular window (i.e. listens to events from one window only), then its possible GetMessage won’t receive the action to quit, and therefore the application may never close.
Multitasking in Windows, old and new
For older 16-bit Windows operating systems, if there are no pending messages for a given application following evaluation of GetMessage(), then the operating system will allow execution to pass to another application to check its message queue. This mechanism of running multiple applications is known as cooperative multitasking.
For more modern operating systems, Windows can interrupt an application after a given period to transfer control to anther application, regardless of whether messages are pending. This approach is referred to as pre-emptive multitasking.
In either case, implementation of a messaging loop is required, since the application will need to prepare for the case when Windows interrupts execution.
Defining application behaviour: WindowProc()
As is hopefully becoming evident, most of the custom application logic is defined in WindowProc(). Below is the prototype:
LRESULT CALLBACK WindowProc(
HWND hWnd,
UINT message, // message ID
WPARAM wParam,
LPARAM lParam
);
Recall from Windows data types that LRESULT is the return to the message, equivalent to a long. The specifier CALLBACK is needed to indicate (for various reason) that WindowProc() is accessed through a pointer and how Windows should handle the four parameters.
In effect, WindowProc() processes the message IDs (known by the second parameter) via a switch block:
switch (message)
{
case WM_PAINT:
// code to handle drawing
break;
case WM_LBUTTONDOWN:
// code executed when the left mouse button is pressed
break;
case WM_LBUTTONUP:
// code executed when the left mouse button is released
break;
case WM_DESTROY:
// code executed when the window is destroyed (clean-up);
// this is where the application would call PostQuitMessage(0) to
// generate a WM_QUIT message
break;
default:
// default actions...
}
We focus on repainting a window and therefore examine the WM_PAINT message type.
Previously tabulated above, we use an HDC. In more detail, a HDC provides a link between device-independent Windows API functions that output data to a screen or printer, along with the device specific device drivers that support such operations. The HDC is issued to the application by Windows on request, granting the application permission to output data.
To get the HDC for drawing to the screen, the client area (from within WindowProc()), use BeginPaint():
HDC hDC; // the authority
PAINTSTRUCT PaintSt; // structure which defines the drawing region
// pass the winows handle and the PAINTSTRUCT variable.
hDC = BeginPaint(hWnd, &PaintSt);
The PAINTSTRUCT variable is updated by Windows with information about the clent area in response to a WM_PAINT message. One can obtain the coordinates (as upper left and lower right corners) within a RECT structure using GetClientRect():
RECT aRECT;
GetClientRect(hWnd, &aRECT);
The updated aRECT variable is updated by Windows.
We then update the background colour of the client area text (to be shown) as transparent, to allow the background of the client area to show through. (Without this, a default OPAQUE mode would apply to text background colours.)
SetBkMade(hDC, TRANSPARENT);
We then start drawing, in this setting text with Drawtext():
DrawText(
hDC,
L"Client area text", // the L prefix is not a typo
-1, // indicates that the second param is a null terminated string
&aRECT, // the recatangle structure
DT_SINGLELINE| // bitwise OR of text format flags; first: single line
DT_CENTER| // second: centred text
DT_VCENTER, // third: line vertically centered in aRECT
);
When finished drawing, we pair BeginPaint() with a call to EndPaint():
EndPaint(hWnd, &PaintSt);
Closing the application
As highlighted by the switch statement, we define code under WM_DESTROY to generate a WM_QUIT message, which ultimately finds its way into WinMain()’s GetMessage():
switch (message)
{
// ...
case WM_DESTROY:
// zero represents the exit code
PostQuitMessage(0);
break;
default:
// default actions...
}
The completed Windows API Win32 demo
Having selected a Win32 Project (instead of Windows Console application), the cpp file would look something like this:
#include <windows.h>
LRESULT WINAPI WindowProc(HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam);
// called by Windows at the start of execution
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow){
WNDCLASSEX WindowClass;
static LPCTSTR szAppName = L"winDemo";
HWND hWnd;
MSG msg;
WindowClass.cbSize = sizeof(WNDCLASSEX);
WindowClass.style = CS_HREDRAW | CS_VREDRAW;
WindowClass.lpfnWndProc = WindowProc;
WindowClass.cbClsExtra = 0;
WindowClass.cbWndExtra = 0;
WindowClass.hInstance = hInstance;
WindowClass.hIcon = LoadIcon(0, IDI_APPLICATION);
WindowClass.hCursor = LoadCursor(0, IDC_ARROW);
WindowClass.hbrBackground = static_cast<HBRUSH>(GetStockObject(GRAY_BRUSH));
WindowClass.lpszMenuName = 0;
WindowClass.lpszClassName = szAppName;
WindowClass.hIconSm = 0;
RegisterClassEx(&WindowClass);
hWnd = CreateWindow(
szAppName,
L"Example window title",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
0,
0,
hInstance,
0);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
while (GetMessage(&msg, 0, 0, 0) == TRUE){
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return static_cast<int>(msg.wParam);
}
// called by Windows whenever a message is passed to the application window
LRESULT WINAPI WindowProc(HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam){
HDC hDC;
PAINTSTRUCT PaintSt;
RECT aRECT;
switch(message){
case WM_PAINT:
hDC = BeginPaint(hWnd, &PaintSt);
GetClientRect(hWnd, &aRECT);
SetBkMode(hDC, TRANSPARENT);
DrawText(
hDC,
L"Some text that appears in the client area",
-1,
&aRECT,
DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hWnd, &PaintSt);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}