3.2.6 Example: Multithreaded MDI Application

The following example shows how multiple threads might be used in a Multiple Document Interface (MDI) application. The application has a single main window, but can have any number of child windows. The primary thread of the process initializes the application and handles messages to the main window and to any child windows (in other words, MainWndProc and ThreadWndProc are both part of the primary thread). Each time a child window is created, a new thread is also created. In this example, the new thread simply checks continuously to see if it is time to terminate, but in a real application it could be used to draw the contents of the window or to carry out the functions that are associated with the window.

The HWND window handle of the child window is used for communications between each child window and its corresponding thread. The window handle is passed as a parameter to ThreadFunction when the thread is created; and it is also a parameter to ThreadWndProc each time a message is dispatched to a child window. The handle is used to access the four-byte space in each window structure that is reserved for application use. The four-byte space could be used to store a pointer to a block of data, but in this example it is used as a termination flag. When ThreadWndProc gets the WM_CLOSE message, it sets the flag; and ThreadFunction checks the flag each time through its loop.

This example illustrates the use of normal priority for the primary thread and below normal priority for the other threads. Since the primary thread handles all messages for both the main window and the child windows, its higher relative priority ensures responsiveness to user input. Even if the child threads were left at normal priority, the scheduler would automatically boost the priority of the input thread when necessary. However, in this example, their priority should still be reduced because their tasks are not time critical; and since these threads never block, the result if they were normal priority would be to starve all the lower priority threads of other applications.

When the user terminates the application by closing the main window, a global boolean is set to force the child threads to terminate. At this point, the primary thread waits for each to terminate before proceeding. This is necessary only if you want the threads to clean up before closing (for example, save changes to a file, detach from DLLs). Without waiting, the primary thread would terminate the process by returning before the lower priority threads were able to execute any additional code.

#include <windows.h>

#include <stdio.h>

#include <stdlib.h>

#define MM_NEWWIN 8001

typedef struct _PTHREADLIST {

HANDLE hThread;

void *pNext;

} THREADLIST, *PTHREADLIST;

/* globals */

HANDLE ghModule; /* handle to .EXE file for this process */

HWND ghwndMain = NULL; /* handle to main window */

BOOL bKillAll = FALSE; /* set TRUE to terminate all threads */

PTHREADLIST pHead = NULL; /* head of linked list of thread info */

/* Forward declarations */

BOOL InitializeApp (void);

LONG MainWndProc(HWND, UINT, DWORD, LONG);

LONG ThreadWndProc(HWND, UINT, DWORD, LONG);

LONG ThreadFunction(HWND);

VOID AddThreadToList(HANDLE);

VOID ErrorExit(LPSTR);

/* primary thread: initialize app and dispatch messages */

int WinMain(HANDLE hInst, HANDLE hPrevInst, LPSTR lpCmdLn, int nShowCmd)

{

MSG msg;

ghModule = GetModuleHandle(NULL);

if (!InitializeApp())

ErrorExit("InitializeApp failure!");

while (GetMessage(&msg, NULL, 0, 0)) {

TranslateMessage(&msg);

DispatchMessage(&msg);

}

return 1;

UNREFERENCED_PARAMETER(hInst);

UNREFERENCED_PARAMETER(hPrevInst);

UNREFERENCED_PARAMETER(lpCmdLn);

UNREFERENCED_PARAMETER(nShowCmd);

}

/* register window classes; create main window */

BOOL InitializeApp(void) {

HMENU hMenu, hPopup;

WNDCLASS wc;

/* register window class for main window */

wc.style = CS_OWNDC;

wc.lpfnWndProc = (WNDPROC)MainWndProc;

wc.cbClsExtra = 0;

wc.cbWndExtra = 0;

wc.hInstance = ghModule;

wc.hIcon = LoadIcon(NULL,IDI_APPLICATION);

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND+1);

wc.lpszMenuName = NULL;

wc.lpszClassName = "MainWindowClass";

if (!RegisterClass(&wc)) return FALSE;

/* register window class for sub windows */

wc.lpfnWndProc = (WNDPROC)ThreadWndProc;

wc.lpszClassName = "ThreadWindowClass";

if (!RegisterClass(&wc))

return FALSE;

/* create menu for main window */

hMenu = CreateMenu();

hPopup = CreateMenu();

if (!AppendMenu(hPopup, MF_STRING, MM_NEWWIN, "&New Window"))

return FALSE;

if (!AppendMenu(hMenu, MF_POPUP, (UINT) hPopup, "&Threads"))

return FALSE;

/* create main window */

ghwndMain = CreateWindow("MainWindowClass", "Primary Window",

WS_OVERLAPPED | WS_CAPTION | WS_BORDER | WS_THICKFRAME |

WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_CLIPCHILDREN |

WS_VISIBLE | WS_SYSMENU, CW_USEDEFAULT, CW_USEDEFAULT,

CW_USEDEFAULT, CW_USEDEFAULT, NULL, hMenu, ghModule,

NULL);

if (ghwndMain == NULL)

return FALSE;

SetFocus(ghwndMain); /* set initial focus */

return TRUE;

}

/* handle messages for main window */

long MainWndProc(HWND hwnd, UINT message, DWORD wParam, LONG lParam)

{

static HWND hwndClient;

static int iCount=1;

CLIENTCREATESTRUCT clientcreate;

HWND hwndChildWnd;

DWORD ThreadId;

PTHREADLIST pNode;

switch (message) {

/* create client window to receive child window messages */

case WM_CREATE:

clientcreate.hWindowMenu = NULL;

clientcreate.idFirstChild = 1;

hwndClient = CreateWindow("MDICLIENT", NULL,

WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE, 0, 0, 0, 0,

hwnd, NULL, ghModule, (LPVOID)&clientcreate);

return 0L;

/*

* Close main window. First set bKillAll to TRUE to

* terminate all threads. Then wait for threads to exit

* before passing close message to default handler. If you

* don't wait for threads to terminate, process terminates

* with no chance for thread cleanup

*/

case WM_CLOSE:

bKillAll = TRUE;

pNode = pHead;

while (pNode != NULL) {

DWORD dwRes;

SetThreadPriority(pNode->hThread,

THREAD_PRIORITY_NORMAL);

dwRes = WaitForSingleObject(pNode->hThread,

0xffffffff);

pNode = (PTHREADLIST) pNode->pNext;

}

return DefFrameProc(hwnd, hwndClient, message,

wParam, lParam);

/* Terminate process */

case WM_DESTROY:

PostQuitMessage(0);

return 0L;

/* Handle menu commands */

case WM_COMMAND:

switch (LOWORD(wParam)) {

/* Create child window and start a thread for it */

case MM_NEWWIN: {

HANDLE hThrd;

MDICREATESTRUCT mdicreate;

char TitleBarText[32];

LONG lPrev;

DWORD dwSuspendCount;

sprintf(TitleBarText, "Thread Window %d", iCount);

mdicreate.szClass = "ThreadWindowClass";

mdicreate.szTitle = TitleBarText;

mdicreate.hOwner = ghModule;

mdicreate.x = mdicreate.y =

mdicreate.cx = mdicreate.cy = CW_USEDEFAULT;

mdicreate.style = mdicreate.lParam = 0L;

/* Send create child window message to client window */

hwndChildWnd = (HWND) SendMessage(hwndClient,

WM_MDICREATE, 0L, (LONG)&mdicreate);

if (hwndChildWnd == NULL)

ErrorExit("Failed in Creating Thread Window!");

/* window struct used to pass quit message to thread */

lPrev = SetWindowLong(hwndChildWnd, GWL_USERDATA, 0);

/* Create suspended, alter priority before resuming */

hThrd = CreateThread(NULL, /* no security descriptor */

0, /* use default stack size */

ThreadFunction, /* function thrd executes */

hwndChildWnd, /* parameter to thrd func */

CREATE_SUSPENDED, /* creation flag */

&ThreadId); /* returns thread id */

if (hThrd == NULL)

ErrorExit("CreateThread Failed!");

AddThreadToList(hThrd);

iCount++;

/*

* Set priority lower than primary (input) thread, so

* app is responsive to user input. Then resume

* thread.

*/

if (!SetThreadPriority(hThrd,

THREAD_PRIORITY_BELOW_NORMAL))

ErrorExit("SetThreadPriority failed!");

if ((dwSuspendCount = ResumeThread(hThrd))

== 0xFFFFFFFF)

ErrorExit("ResumeThread failed!");

return 0L;

}

default:

return DefFrameProc(hwnd, hwndClient, message,

wParam, lParam);

}

default:

return DefFrameProc(hwnd, hwndClient, message,

wParam, lParam);

}

}

/* handle messages for child windows */

long ThreadWndProc(HWND hwnd, UINT msg, DWORD wParam, LONG lParam)

{

LONG lPrevLong;

switch (msg) {

/* use window structure to pass close message to thread */

case WM_CLOSE:

lPrevLong = SetWindowLong(hwnd, GWL_USERDATA, 1);

return DefMDIChildProc(hwnd, msg, wParam, lParam);

case WM_DESTROY:

return 0L;

default:

return DefMDIChildProc(hwnd, msg, wParam, lParam);

}

}

/*

* Each child window has a thread that can be used to perform tasks

* associated with that window, e.g., drawing its contents

*/

long ThreadFunction(HWND hwnd)

{

LONG lKillMe = 0L;

while (TRUE) {

lKillMe = GetWindowLong(hwnd, GWL_USERDATA);

if (bKillAll || lKillMe) break;

/* perform task */

}

/* perform actions needed before thread termination */

ExitThread(0);

}

VOID AddThreadToList(HANDLE hThread)

{

PTHREADLIST pNode = (PTHREADLIST) malloc(sizeof(PTHREADLIST));

if (pNode == NULL)

ErrorExit("malloc failed!");

pNode->hThread = hThread;

pNode->pNext = (void *) pHead;

pHead = pNode;

}

VOID ErrorExit(LPSTR lpMessage)

{

int button = MessageBox(ghwndMain, lpMessage, "Error", MB_OK);

ExitProcess(0);

}