Programming with Threads

Threads illustrates how to program with threads, including synchronizing them using the thread's message queue.

The C main() function creates the thread and goes about its normal processing. Within its normal processing loop, it posts a thread message to be processed by the thread and then optionally calls SwitchToThread() to relinquish its timeslice to the thread. When main() is ready to terminate the thread, it posts a WM_QUIT to the thread to tell it to exit and then waits until the thread has exited. The main() function is then free to exit, knowing that the thread has properly terminated and freed its resources.

The thread routine waits in the GetMessage() loop. If the message is WM_QUIT, it exits the thread. Using the message queue this way synchronizes the processing and causes the thread to relinquish its time slice except when it has actual work to do. When a normal message is posted, the thread processes the request. During this message processing, the thread can optionally gobble up duplicated messages to suppress redundant processing.

Threads.dsw

Microsoft Developer Studio Workspace File, Format Version 6.00
# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!

###############################################################################

Project: "Threads"=.\Threads.dsp - Package Owner=<4>

Package=<5>
{{{
}}}

Package=<4>
{{{
}}}

###############################################################################

Global:

Package=<5>
{{{
}}}

Package=<3>
{{{
}}}

###############################################################################


Threads.dsp

# Microsoft Developer Studio Project File - Name="Threads" - Package Owner=<4>
# Microsoft Developer Studio Generated Build File, Format Version 6.00
# ** DO NOT EDIT **

# TARGTYPE "Win32 (x86) Console Application" 0x0103

CFG=Threads - Win32 Debug
!MESSAGE This is not a valid makefile. To build this project using NMAKE,
!MESSAGE use the Export Makefile command and run
!MESSAGE 
!MESSAGE NMAKE /f "Threads.mak".
!MESSAGE 
!MESSAGE You can specify a configuration when running NMAKE
!MESSAGE by defining the macro CFG on the command line. For example:
!MESSAGE 
!MESSAGE NMAKE /f "Threads.mak" CFG="Threads - Win32 Debug"
!MESSAGE 
!MESSAGE Possible choices for configuration are:
!MESSAGE 
!MESSAGE "Threads - Win32 Release" (based on "Win32 (x86) Console Application")
!MESSAGE "Threads - Win32 Debug" (based on "Win32 (x86) Console Application")
!MESSAGE 

# Begin Project
# PROP AllowPerConfigDependencies 0
# PROP Scc_ProjName ""
# PROP Scc_LocalPath ""
CPP=cl.exe
RSC=rc.exe

!IF  "$(CFG)" == "Threads - Win32 Release"

# PROP BASE Use_MFC 0
# PROP BASE Use_Debug_Libraries 0
# PROP BASE Output_Dir "Release"
# PROP BASE Intermediate_Dir "Release"
# PROP BASE Target_Dir ""
# PROP Use_MFC 0
# PROP Use_Debug_Libraries 0
# PROP Output_Dir ""
# PROP Intermediate_Dir "Release"
# PROP Ignore_Export_Lib 0
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
# ADD CPP /nologo /W4 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /Yu"StdAfx.h" /FD /c
# ADD BASE RSC /l 0x409 /d "NDEBUG"
# ADD RSC /l 0x409 /d "NDEBUG"
BSC32=bscmake.exe
# ADD BASE BSC32 /nologo
# ADD BSC32 /nologo
LINK32=link.exe
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386
# ADD LINK32 user32.lib /nologo /subsystem:console /machine:I386

!ELSEIF  "$(CFG)" == "Threads - Win32 Debug"

# PROP BASE Use_MFC 0
# PROP BASE Use_Debug_Libraries 1
# PROP BASE Output_Dir "Debug"
# PROP BASE Intermediate_Dir "Debug"
# PROP BASE Target_Dir ""
# PROP Use_MFC 0
# PROP Use_Debug_Libraries 1
# PROP Output_Dir ""
# PROP Intermediate_Dir "Debug"
# PROP Ignore_Export_Lib 0
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c
# ADD CPP /nologo /W4 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FR /Yu"StdAfx.h" /FD /GZ /c
# ADD BASE RSC /l 0x409 /d "_DEBUG"
# ADD RSC /l 0x409 /d "_DEBUG"
BSC32=bscmake.exe
# ADD BASE BSC32 /nologo
# ADD BSC32 /nologo
LINK32=link.exe
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
# ADD LINK32 user32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept

!ENDIF 

# Begin Target

# Name "Threads - Win32 Release"
# Name "Threads - Win32 Debug"
# Begin Group "Source Files"

# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
# Begin Source File

SOURCE=.\Main.c
# End Source File
# Begin Source File

SOURCE=.\StdAfx.c
# ADD CPP /Yc"StdAfx.h"
# End Source File
# End Group
# Begin Group "Header Files"

# PROP Default_Filter "h;hpp;hxx;hm;inl"
# Begin Source File

SOURCE=.\Main.h
# End Source File
# Begin Source File

SOURCE=.\StdAfx.h
# End Source File
# End Group
# Begin Group "Resource Files"

# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
# End Group
# End Target
# End Project

StdAfx.h

// StdAfx.h : include file for standard system include files,
//  or project specific include files that are used frequently, but
//      are changed infrequently
//

#if !defined(AFX_STDAFX_H__6D968E9E_F6A5_4C6A_8DEF_8128F351189A__INCLUDED_)
#define AFX_STDAFX_H__6D968E9E_F6A5_4C6A_8DEF_8128F351189A__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#define VC_EXTRALEAN    // Exclude rarely-used stuff from Windows headers

#pragma warning ( disable : 4115 )
#include <windows.h>
#pragma warning ( default : 4115 )

// Global Defines

#ifdef __cplusplus
extern "C" {
#endif

extern WINBASEAPI BOOL WINAPI SwitchToThread ( VOID );   // Requires NT 4.0 or Later

#ifdef __cplusplus
}
#endif

// TODO: reference additional headers your program requires here

#endif // !defined(AFX_STDAFX_H__6D968E9E_F6A5_4C6A_8DEF_8128F351189A__INCLUDED_)

StdAfx.c

// StdAfx.cpp : source file that includes just the standard includes
// Threads.pch will be the pre-compiled header
// StdAfx.obj will contain the pre-compiled type information

#include "StdAfx.h"

// TODO: reference any additional headers you need in StdAfx.h
// and not in this file

Main.h

#ifndef _MAIN_H
#define _MAIN_H
/*
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#ifdef TRACE_HEADERS
#pragma message ( "NOTE:  Included header file:  \"" __FILE__ "\"" )
#endif
*/
typedef unsigned __int64 QWORD;

#endif // _MAIN_H

Main.c

// Main.c : Defines the entry point for the console application.
//

#include "StdAfx.h"                    // Windows Prototypes and Defines
#include <stdio.h>                     // Standard I/O Prototypes and Defines
#include "Main.h"                      // Globally Shared Routines and Data

// Comm Thread Messages are WM_APP + ECOMMSG:  WM_APP Messages ( 0x8000 - 0xBFFF ) are Available to Applications

typedef enum {

   WM_COM_MSG_TEST,                    // Strictly Test for Now

} ECOMMSG;

static BOOL fShutDownRequested = FALSE;

// Detect and Honor all of the Normal Shutdown Events, Including Ctrl+C and Ctrl+Break

static BOOL WINAPI ControlHandler ( DWORD dwCtrlType )

   {

   switch ( dwCtrlType )
      {
      case CTRL_C_EVENT:               // Ctrl+C from Keyboard
      case CTRL_BREAK_EVENT:           // Ctrl+Break from Keyboard
      case CTRL_CLOSE_EVENT:           // Close from System Menu or Button
      case CTRL_LOGOFF_EVENT:          // User Logoff Event
      case CTRL_SHUTDOWN_EVENT:        // System Shutdown Event

         MessageBeep ( 0xFFFFFFFF );
         MessageBeep ( 0xFFFFFFFF );
         MessageBeep ( 0xFFFFFFFF );

         fprintf ( stdout, "\rShutdown Requested ...\n" );

         fShutDownRequested = TRUE;    // Set Shutdown Requested

         return TRUE;

      default:  return FALSE;
      }

   }  // static BOOL WINAPI ControlHandler ( DWORD dwCtrlType )

DWORD WINAPI ComThread ( LPVOID lpThreadParameter )

   {
   MSG msg;

   UNREFERENCED_PARAMETER ( lpThreadParameter );

   // Process Pending Thread Messages

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

      switch ( ( ECOMMSG ) ( msg.message - WM_APP ) )
         {

         case WM_COM_MSG_TEST:

            fprintf ( stdout, "\rWM_COM_MSG_TEST Message ( %lu, %ld ) detected\n", msg.wParam, msg.lParam );

            // Gobble Up Additional WM_COM_MSG_TEST Messages

            while ( PeekMessage ( &msg, NULL, WM_APP + WM_COM_MSG_TEST, WM_APP + WM_COM_MSG_TEST, PM_REMOVE ) );

            break;

         default:  fprintf ( stderr, "\rInvalid Message type ( %u ) detected!\n\a", msg.message );
         }
      }

   // WM_QUIT Called - Exit Thread

   fprintf ( stdout, "\rThread Exiting!\n" );

   return ERROR_SUCCESS;

   }  // DWORD WINAPI ComThread ( LPVOID lpThreadParameter )

int main ( int argc, char * argv [], char * envp [] )
   {
   unsigned long  ulPass         = 0;
   char           szShow[]       = "|/-\\";

   HANDLE         hComThread     = INVALID_HANDLE_VALUE;
   DWORD          dwComThreadID  = 0LU;

   UNREFERENCED_PARAMETER ( argc );
   UNREFERENCED_PARAMETER ( argv );
   UNREFERENCED_PARAMETER ( envp );

   // Set the Console Handler

   SetConsoleCtrlHandler ( ControlHandler, TRUE );

   // Create the Processing Thread

   hComThread = CreateThread ( NULL, 0, ComThread, NULL, 0, &dwComThreadID );

   // Do Normal Processing Here

   while ( ! fShutDownRequested )
      {
      fprintf ( stdout, "\r%c", szShow [ ( ulPass++ ) % ( sizeof szShow - 1 ) ] );

      if ( ulPass % 10 == 0LU )
         {
         PostThreadMessage ( dwComThreadID, WM_APP + WM_COM_MSG_TEST, ulPass / 10, ulPass );
         SwitchToThread();
         }

      Sleep ( 100LU );
      }

   // Tell the Thread to Exit

   PostThreadMessage ( dwComThreadID, WM_QUIT, 0U, 0L );

   // Wait for the Worker Thread to Exit

   WaitForSingleObject ( hComThread, INFINITE );

   // Close the Thread

   CloseHandle ( hComThread );

   // Return Success!

   fprintf ( stdout, "Bye!\n" );

   return 0;

   }  // int int _tmain ( int argc, TCHAR * argv [], TCHAR * /* envp */ [] )

Following is a sample of the program's output:

F:\Projects\Threads>Threads
WM_COM_MSG_TEST Message ( 1, 10 ) detected
WM_COM_MSG_TEST Message ( 2, 20 ) detected
WM_COM_MSG_TEST Message ( 3, 30 ) detected
WM_COM_MSG_TEST Message ( 4, 40 ) detected
WM_COM_MSG_TEST Message ( 5, 50 ) detected
WM_COM_MSG_TEST Message ( 6, 60 ) detected
WM_COM_MSG_TEST Message ( 7, 70 ) detected
WM_COM_MSG_TEST Message ( 8, 80 ) detected
WM_COM_MSG_TEST Message ( 9, 90 ) detected
WM_COM_MSG_TEST Message ( 10, 100 ) detected
Shutdown Requested ...
Thread Exiting!
Bye!

F:\Projects\Threads>