Microsoft Windows API C++ applications Part 1

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 ANSI characters
LPCWSTR A pointer to a constant null-terminated string of 16-bit Unicode 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, str a zero terminated string
w WORD; unsigned short
x, y short, usually used for coordinates
cx, cy short, c standing for count, used to denote lengths

Hungarian notation also sets out naming conventions for functions and classes.

Under the Hungarian notation, functions start with capitalised letters, however, underscores are not permitted. Variable names use camel-case. Types and constants are fully capitalised and can include an underscores. Classes always start with uppercase C.

Windows applications

Windows recognises and calls two functions in a C++ project:

  • WinMain() - where execution begins and ends (equivalent to command line main())
  • WindowProc() - 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 instance
  • hPrevInstance - for 16-bit applications, a handle to the previous instance of the application; this is always NULL for 32-bit applications
  • lpCmdLine - a pointer to a command line instruction that ran the application
  • nCmdShow - 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.

Win32 message boxes

Starting a new Win32 project (not console) with entry point WinMain():

// exclude MFC overhead, pure Win32
#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <windowsx.h> // additional macros

int WINAPI WinMain(HINSTANCE hInstance, 
				   HINSTANCE hPrevInstance, 
				   LPSTR lpCmdLine, 
				   int nCmdShow)
{
	MessageBox(
		NULL,  // handle of owner window (null if parent)

		// this project is configured to support Unicode
		// so the L macro which passes a Unicode wide char is required
		L"Message box title",
		L"Message box message", 

		// logical OR producing buttons for both
		MB_OK | MB_ICONEXCLAMATION);

	return 0;
}

On compilation, this produces a dialog box:

Sidenote: targetting Windows 9X or NT

The passing of string literals with wide Unicode chars (instead of narrower ANSI chars) is achieved with the L macro. The above MessageBox() expects LPCWSTR instead of LPCSTR. This will be commonplace for all examples in this section (compiled applications targetting Windows NT derived operating systems). See also here.

If applications need to run on Win 9X, which has very limited support for Unicode, then one will need to compile with Multi-byte character set found under the project settings in MVS 2005 (this normally defaults to Unicode set):

In such cases, it is not necessary to prefix strings with the L macro.

Window classes with 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 (obsolete) structure, WNDCLASS.

The procedure generally involves:

  1. Defining a Window class and attaching an event handler (callback)
  2. Registering the Window class with Windows
  3. Creating a new Window and getting a reference to it
  4. Showing the window
  5. Define the callback function that fires when an event occurs

1. Defining WNDCLASSEX and attaching a handler

An instance of WNDCLASSEX is constructed in WinMain() (the full cpp file is given at the end of this section), 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; // seldom used
	int cbWndExtra; // seldom used
	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 (useful for calling function
// to know ahead of time how much data is expected)
windowClass.cbSize = sizeof(WNDCLASSEX);

// determine behaviour; in this case 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) have null, true or
// false states (in this case as 32-bit words, with 1 for true)
// so here windowClass.style would be 1 when there was a change
// to either or both dimensions, and 0 at all other times
windowClass.style = CS_HREDRAW | CS_VREDRAW;

// set a pointer to a (callback) function (i.e. WindowProc)
//  that fires when an event occurs 
// WindowProc() is defined later
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 (Unicode)
windowClass.lpszClassName = szAppname;

The last field denotes the name Windows uses to refer to this Window class.

2. Registering WNDCLASSEX object with Windows

The Windows API function RegisterClassEx() can be used to 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.

RegisterClassEx($nameOfWindowClassInstance);

3. 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)
	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
);

There is another similar function CreateWindowEx() that is similar to CreateWindow(). The former includes an extra parameter dwExStyle for advanced window features, and is generally null. Hence, we use CreateWindow() here.

4. Showing the window

The Windows API function ShowWindow() can then be used to draw the window to the screen.

// note aforementioned parameters, the reference to 
// the created window and WinMain's nCmdShow
ShowWindow(hWndAlpha, nCmdShow);

This method is however not necessary if the third parameter to CreateWindow() is set with WS_VISIBLE:

hWndAlpha = CreateWindow(
	szAppname, 
	L"The Window title goes here",  
	WS_OVERLAPPEDWINDOW | WS_VISIBLE, // this shows that window on instantiation
	CW_USEDEFAULT, 
	CW_USEDEFAULT,
	CW_USEDEFAULT,
	CW_USEDEFAULT,
	0, 
	0, 
	hInstance, 
	0
);

Continuing on following a call to ShowWindow(), 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);

More on Windows applications

We cover Windows application messaging in more detail first before returning to updating the application content following UpdateWindow().

Scheduling and application threads

Windows (in contrast to older operating systems like DOS) allows different applications to run in a round-robin fashion, where each application gets a small time slice to run in. The CPU is shared among the different applications. The time allotted to each application is managed by a scheduler.

Data is transferred between an application and the operating system along an execution line is known as a thread, with some applications having multiple threads of execution. Each thread is normally executed a short amount of the time and can be created logically through programming.

The CPU works on a given thread, strictly speaking* in turn and so in the true sense is not multitasking.

Event-driven applications

Windows supports “multitasking” (in truth, Windows 9X/NT onwards did; for example, Windows 3.1 required applications to yield to the next, related to the deprecated hPrevInstance param in WinMain()) and is also event-driven.

An event is an action, usually performed by the user. Windows receives notice of these events and then sends the application one or more messages that describe what the user did. The message is processed by the application by a handler, as defined within the application.

Windows Messages

Messages are either

  • Queued (e.g. user interaction; these are queued in WinMain())
  • Non-queued

Messages are handled as follows:

  1. Process queued messages (if they exist) in WinMain()
  2. Ask Windows to call WinProc() (short for Windows Procedure; this isn’t called automatically) to deal with the message. By this point, the message isn’t in a queue anymore.

The main event (message) loop

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
	0, // these last two params indicate boundaries to message IDs, allowing 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.

5. Defining application behaviour with 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 reasons) 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...
}

A complete implementation of WindowProc() is given at the end of this section. We focus on repainting a window and therefore examine the WM_PAINT message type.

Repainting a window

Previously tabulated above, we use an HDC. In more detail, a HDC (handle to a device context) 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 (defined within WindowProc()) use BeginPaint():

// the authority
HDC hDC;  

// a structure which defines the region
// that must be redrawn
PAINTSTRUCT PaintSt;

// pass the window's handle (unqiue to the window)
// and the PAINTSTRUCT variable.
hDC = BeginPaint(hWnd, &PaintSt);

The client area is essentially the area of the window minus the title bar. When a part of the client area is invalidated, it means another window or similar previously obscured that part of the client area and now it is no longer obscured, the affected area needs to be redrawn. The remainder of the client area need not be redraw.

In this demo, we are going to redraw the entire client area and set text against it.

The PAINTSTRUCT variable is updated by Windows with information about the client 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 demo setting text with Drawtext():

DrawText(
	hDC,
	L"Client area text",	// the L prefix is not a typo
	-1,					// says 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;

   // set the Window class to point to WindowProc
   // in preparation for message handling when called upon
   // by DispatchMessage() (see main event loop)
   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);

   // the main event loop
   while (GetMessage(&msg, 0, 0, 0) == TRUE){
	   TranslateMessage(&msg);

	   // this call WindowProc()
	   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);

		  // returning zero indicates to Windows that this message
		  // was handled
		  return 0;

	  case WM_DESTROY:
		  PostQuitMessage(0);

		  // returning zero indicates to Windows that this message
		  // was handled
		  return 0;

	  default:
	  	  // send any message that weren't handled to Windows for
		  // default (def) processing
		  return DefWindowProc(hWnd, message, wParam, lParam);
  }
}

Allowing for background tasks

The above main event loop only fires logic if a message is queued. Any other logic that should run the background that should not be event-driven (e.g. background tasks) will not be invoked.

For example, DoBackgroundStuff() won’t be invoked all the time:

   while (GetMessage(&msg, 0, 0, 0) == TRUE){
	   TranslateMessage(&msg);

	   // this call WindowProc()
	   DispatchMessage(&msg);

	   // this only fires if a message (may not be related)
	   // is queued and GetMessage() grants access
	   DoBackgroundStuff();
   }

An alternative approach is to utilise PeekMessage():

while(TRUE){
	if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){
		if (msg.message == WM_QUIT)
			break;

		TranslateMessage(&msg);

	   	// this call WindowProc()
	   	DispatchMessage(&msg);
	}

	// now we can repeatedly do stuff until the application closes 
	DoBackgroundStuff();
}

In the above case, the flag PM_REMOVE removes the message from the queue, assigning it to msg. In this case, there is no need to call GetMessage().