The XTalk Example Program
The following example program, XTalk, uses events, mutexes, and a shared memory-mapped block of memory to communicate among different copies of itself. The example demonstrates the rather common problem of one-to-many communication. In this case, the XTalk window has an edit box with a Send button next to it. When a user taps the Send button, the text in the edit box is communicated to every copy of XTalk running on the system. Each copy of XTalk receives the text from the sending copy and places it in a list box, also in the XTalk window. Figure 10-1 shows two XTalk programs communicating.
Figure 10-1: The desktop showing two XTalk windows
To perform this feat of communication, XTalk uses a named memory-mapped object as a transfer buffer, a mutex to coordinate access to the buffer, and two event objects to indicate the start and end of communication. A third event is used to tell the sender thread to read the text from the edit control and write the contents to the shared memory block. Listing 10-2 shows the source code for XTalk.
Listing 10-2: The source code for XTalk
XTalk.rc
//======================================================================
// Resource file
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================
#include "windows.h"
#include "xtalk.h" // Program-specific stuff
//----------------------------------------------------------------------
// Icons and bitmaps
//
ID_ICON ICON "xtalk.ico" // Program icon
//----------------------------------------------------------------------
xtalk DIALOG discardable 10, 10, 120, 60
STYLE WS_OVERLAPPED | WS_VISIBLE | WS_CAPTION | WS_SYSMENU |
DS_CENTER | DS_MODALFRAME
CAPTION "XTalk"
CLASS "xtalk"
BEGIN
LTEXT "&Text" -1, 2, 10, 20, 12
EDITTEXT IDD_OUTTEXT, 25, 10, 58, 12,
WS_TABSTOP | ES_AUTOHSCROLL
PUSHBUTTON "&Send", IDD_SENDTEXT, 88, 10, 30, 12, WS_TABSTOP
LISTBOX IDD_INTEXT, 2, 25, 116, 40,
WS_TABSTOP | WS_VSCROLL
END
XTalk.h
//======================================================================
// Header file
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================
// Returns number of elements
#define dim(x) (sizeof(x) / sizeof(x[0]))
//----------------------------------------------------------------------
// Generic defines and data types
//
struct decodeUINT { // Structure associates
UINT Code; // messages
// with a function.
LRESULT (*Fxn)(HWND, UINT, WPARAM, LPARAM);
};
struct decodeCMD { // Structure associates
UINT Code; // menu IDs with a
LRESULT (*Fxn)(HWND, WORD, HWND, WORD); // function.
};
//----------------------------------------------------------------------
// Generic defines used by application
#define ID_ICON 1
#define IDD_INTEXT 10 // Control IDs
#define IDD_SENDTEXT 11
#define IDD_OUTTEXT 12
#define MMBUFFSIZE 1024 // Size of shared buffer
#define TEXTSIZE 256
// Interprocess communication structure mapped in shared memory
typedef struct {
int nAppCnt;
int nReadCnt;
TCHAR szText[TEXTSIZE];
} SHAREBUFF;
typedef SHAREBUFF *PSHAREBUFF;
//----------------------------------------------------------------------
// Function prototypes
//
HWND InitInstance (HINSTANCE, LPWSTR, int);
int TermInstance (HINSTANCE, int);
// Window procedures
LRESULT CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM);
// Message handlers
LRESULT DoCreateMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoSetFocusMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoCommandMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoDestroyMain (HWND, UINT, WPARAM, LPARAM);
// Command functions
LPARAM DoMainCommandSend (HWND, WORD, HWND, WORD);
LPARAM DoMainCommandExit (HWND, WORD, HWND, WORD);
// Thread functions
DWORD WINAPI SenderThread (PVOID pArg);
DWORD WINAPI ReaderThread (PVOID pArg);
XTalk.cpp
//======================================================================
// XTalk - A simple application for Windows CE
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================
#include <windows.h> // For all that Windows stuff
#include <commctrl.h> // Command bar includes
#include "xtalk.h" // Program-specific stuff
// The include and lib files for the Pocket PC are conditionally
// included so that this example can share the same project file. This
// is necessary because this example must have a menu bar on the Pocket
// PC to have a SIP button.
#if defined(WIN32_PLATFORM_PSPC)
#include <aygshell.h> // Add Pocket PC includes
#pragma comment( lib, "aygshell" ) // Link Pocket PC lib for menu bar
#endif
//----------------------------------------------------------------------
// Global data
//
const TCHAR szAppName[] = TEXT ("xtalk");
HINSTANCE hInst; // Program instance handle
HANDLE g_hMMObj = 0; // Memory-mapped object
PSHAREBUFF g_pBuff = 0; // Pointer to mm object
HANDLE g_hmWriteOkay = 0; // Write mutex
HANDLE g_hSendEvent = 0; // Local send event
HANDLE g_hReadEvent = 0; // Shared read data event
HANDLE g_hReadDoneEvent = 0; // Shared data read event
// Message dispatch table for MainWindowProc
const struct decodeUINT MainMessages[] = {
WM_CREATE, DoCreateMain,
WM_SETFOCUS, DoSetFocusMain,
WM_COMMAND, DoCommandMain,
WM_DESTROY, DoDestroyMain,
};
// Command Message dispatch for MainWindowProc
const struct decodeCMD MainCommandItems[] = {
IDOK, DoMainCommandExit,
IDCANCEL, DoMainCommandExit,
IDD_SENDTEXT, DoMainCommandSend,
};
//======================================================================
// Program entry point
//
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPWSTR lpCmdLine, int nCmdShow) {
MSG msg;
int rc = 0;
HWND hwndMain;
// Initialize application.
hwndMain = InitInstance (hInstance, lpCmdLine, nCmdShow);
if (hwndMain == 0)
return TermInstance (hInstance, 0x10);
// Application message loop
while (GetMessage (&msg, NULL, 0, 0)) {
if ((hwndMain == 0) || !IsDialogMessage (hwndMain, &msg)) {
TranslateMessage (&msg);
DispatchMessage (&msg);
}
}
// Instance cleanup
return TermInstance (hInstance, msg.wParam);
}
//----------------------------------------------------------------------
// InitInstance - Instance initialization
//
HWND InitInstance (HINSTANCE hInstance, LPWSTR lpCmdLine, int nCmdShow){
HWND hWnd;
HANDLE hThread;
RECT rect;
int rc;
BOOL fFirstApp = TRUE;
WNDCLASS wc;
#if defined(WIN32_PLATFORM_PSPC)
// If Pocket PC, bring the other copy to the foreground so
// the user can see it.
HWND hWnd = FindWindow (szAppName, NULL);
if (hWnd) SetForegroundWindow ((HWND)(((DWORD)hWnd) | 0x01));
#endif
// Save program instance handle in global variable.
hInst = hInstance;
// Register application main window class.
wc.style = 0; // Window style
wc.lpfnWndProc = MainWndProc; // Callback function
wc.cbClsExtra = 0; // Extra class data
wc.cbWndExtra = DLGWINDOWEXTRA; // Extra window data
wc.hInstance = hInstance; // Owner handle
wc.hIcon = NULL, // Application icon
wc.hCursor = NULL; // Default cursor
wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
wc.lpszMenuName = NULL; // Menu name
wc.lpszClassName = szAppName; // Window class name
if (RegisterClass (&wc) == 0) return 0;
// Create mutex used to share memory-mapped structure.
g_hmWriteOkay = CreateMutex (NULL, TRUE, TEXT ("XTALKWRT"));
rc = GetLastError();
if (rc == ERROR_ALREADY_EXISTS)
fFirstApp = FALSE;
else if (rc) return 0;
// Wait here for ownership to ensure that the initialization is done.
// This is necessary since CreateMutex doesn't wait.
rc = WaitForSingleObject (g_hmWriteOkay, 2000);
if (rc != WAIT_OBJECT_0)
return 0;
// Create a file-mapping object.
g_hMMObj = CreateFileMapping ((HANDLE)-1, NULL, PAGE_READWRITE, 0,
MMBUFFSIZE, TEXT ("XTALKBLK"));
if (g_hMMObj == 0) return 0;
// Map into memory the file-mapping object.
g_pBuff = (PSHAREBUFF)MapViewOfFile (g_hMMObj, FILE_MAP_WRITE,
0, 0, 0);
if (!g_pBuff)
CloseHandle (g_hMMObj);
// Initialize structure if first application started.
if (fFirstApp)
memset (g_pBuff, 0, sizeof (SHAREBUFF));
// Increment app running count. Interlock not needed due to mutex.
g_pBuff->nAppCnt++;
// Release the mutex. We need to release the mutex twice
// if we owned it when we entered the wait above.
ReleaseMutex (g_hmWriteOkay);
if (fFirstApp)
ReleaseMutex (g_hmWriteOkay);
// Now create events for read, and send notification.
g_hSendEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
g_hReadEvent = CreateEvent (NULL, TRUE, FALSE, TEXT ("XTALKREAD"));
g_hReadDoneEvent = CreateEvent (NULL, FALSE, FALSE,
TEXT ("XTALKDONE"));
if (!g_hReadEvent || !g_hSendEvent || !g_hReadDoneEvent)
return 0;
// Create main window.
hWnd = CreateDialog (hInst, szAppName, NULL, NULL);
rc = GetLastError();
if (!fFirstApp) {
GetWindowRect (hWnd, &rect);
MoveWindow (hWnd, rect.left+10, rect.top+10,
rect.right-rect.left, rect.bottom-rect.top, FALSE);
}
// Create secondary threads for interprocess communication.
hThread = CreateThread (NULL, 0, SenderThread, hWnd, 0, (DWORD *)&rc);
if (hThread)
CloseHandle (hThread);
else {
DestroyWindow (hWnd);
return 0;
}
hThread = CreateThread (NULL, 0, ReaderThread, hWnd, 0, (DWORD *)&rc);
if (hThread)
CloseHandle (hThread);
else {
DestroyWindow (hWnd);
return 0;
}
// Return fail code if window not created.
if (!IsWindow (hWnd)) return 0;
// Standard show and update calls
ShowWindow (hWnd, nCmdShow);
UpdateWindow (hWnd);
return hWnd;
}
//----------------------------------------------------------------------
// TermInstance - Program cleanup
//
int TermInstance (HINSTANCE hInstance, int nDefRC) {
// Free memory-mapped object.
if (g_pBuff) {
// Decrement app running count.
InterlockedDecrement (&g_pBuff->nAppCnt);
UnmapViewOfFile (g_pBuff);
}
if (g_hMMObj)
CloseHandle (g_hMMObj);
// Free mutex.
if (g_hmWriteOkay)
CloseHandle (g_hmWriteOkay);
// Close event handles.
if (g_hReadEvent)
CloseHandle (g_hReadEvent);
if (g_hReadDoneEvent)
CloseHandle (g_hReadDoneEvent);
if (g_hSendEvent)
CloseHandle (g_hSendEvent);
return nDefRC;
}
//======================================================================
// Message handling procedures for main window
//----------------------------------------------------------------------
// MainWndProc - Callback function for application window
//
LRESULT CALLBACK MainWndProc (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
int i;
//
// Search message list to see if we need to handle this
// message. If in list, call procedure.
//
for (i = 0; i < dim(MainMessages); i++) {
if (wMsg == MainMessages[i].Code)
return (*MainMessages[i].Fxn)(hWnd, wMsg, wParam, lParam);
}
return DefWindowProc (hWnd, wMsg, wParam, lParam);
}
//----------------------------------------------------------------------
// DoCreateMain - Process WM_CREATE message for window.
//
LRESULT DoCreateMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
#if defined(WIN32_PLATFORM_PSPC) && (_WIN32_WCE >= 300)
SHMENUBARINFO mbi; // For Pocket PC, create
memset(&mbi, 0, sizeof(SHMENUBARINFO)); // menu bar so that we
mbi.cbSize = sizeof(SHMENUBARINFO); // have a sip button.
mbi.hwndParent = hWnd;
mbi.dwFlags = SHCMBF_EMPTYBAR; // No menu
SHCreateMenuBar(&mbi);
#endif
return 0;
}
//----------------------------------------------------------------------
// DoSetFocusMain - Process WM_SETFOCUS message for window.
//
LRESULT DoSetFocusMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
SetFocus (GetDlgItem (hWnd, IDD_OUTTEXT));
return 0;
}
//----------------------------------------------------------------------
// DoCommandMain - Process WM_COMMAND message for window.
//
LRESULT DoCommandMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
WORD idItem, wNotifyCode;
HWND hwndCtl;
int i;
// Parse the parameters.
idItem = (WORD) LOWORD (wParam);
wNotifyCode = (WORD) HIWORD (wParam);
hwndCtl = (HWND) lParam;
// Call routine to handle control message.
for(i = 0; i < dim(MainCommandItems); i++) {
if(idItem == MainCommandItems[i].Code)
return (*MainCommandItems[i].Fxn)(hWnd, idItem, hwndCtl,
wNotifyCode);
}
return 0;
}
//----------------------------------------------------------------------
// DoDestroyMain - Process WM_DESTROY message for window.
//
LRESULT DoDestroyMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
PostQuitMessage (0);
return 0;
}
//======================================================================
// Command handler routines
//----------------------------------------------------------------------
// DoMainCommandExit - Process Program Exit command.
//
LPARAM DoMainCommandExit (HWND hWnd, WORD idItem, HWND hwndCtl,
WORD wNotifyCode) {
SendMessage (hWnd, WM_CLOSE, 0, 0);
return 0;
}
//----------------------------------------------------------------------
// DoMainCommandSend - Process Program Send command.
//
LPARAM DoMainCommandSend (HWND hWnd, WORD idItem, HWND hwndCtl,
WORD wNotifyCode) {
SetEvent (g_hSendEvent);
return 0;
}
//======================================================================
// SenderThread - Performs the interprocess communication
//
DWORD WINAPI SenderThread (PVOID pArg) {
HWND hWnd;
int nGoCode, rc;
TCHAR szText[TEXTSIZE];
hWnd = (HWND)pArg;
while (1) {
nGoCode = WaitForSingleObject (g_hSendEvent, INFINITE);
if (nGoCode == WAIT_OBJECT_0) {
SendDlgItemMessage (hWnd, IDD_OUTTEXT, WM_GETTEXT,
sizeof (szText), (LPARAM)szText);
rc = WaitForSingleObject (g_hmWriteOkay, 2000);
if (rc == WAIT_OBJECT_0) {
lstrcpy (g_pBuff->szText, szText);
g_pBuff->nReadCnt = g_pBuff->nAppCnt;
PulseEvent (g_hReadEvent);
// Wait while reader threads get data.
while (g_pBuff->nReadCnt)
rc = WaitForSingleObject (g_hReadDoneEvent,
INFINITE);
ReleaseMutex (g_hmWriteOkay);
}
} else
return –1;
}
return 0;
}
//======================================================================
// ReaderThread - Performs the interprocess communication
//
DWORD WINAPI ReaderThread (PVOID pArg) {
HWND hWnd;
int nGoCode, rc, i;
TCHAR szText[TEXTSIZE];
hWnd = (HWND)pArg;
while (1) {
nGoCode = WaitForSingleObject (g_hReadEvent, INFINITE);
if (nGoCode == WAIT_OBJECT_0) {
i = SendDlgItemMessage (hWnd, IDD_INTEXT, LB_ADDSTRING, 0,
(LPARAM)g_pBuff->szText);
SendDlgItemMessage (hWnd, IDD_INTEXT, LB_SETTOPINDEX, i, 0);
InterlockedDecrement (&g_pBuff->nReadCnt);
SetEvent (g_hReadDoneEvent);
} else {
rc = GetLastError();
wsprintf (szText, TEXT ("rc:%d"), rc);
MessageBox (hWnd, szText, TEXT ("ReadThread Err"), MB_OK);
}
}
return 0;
}
The interesting routines in the XTalk example are the InitInstance procedure and the two thread procedures SenderThread and ReaderThread. The relevant part of InitInstance is shown below with the error checking code removed for brevity.
// Create mutex used to share memory-mapped structure.
g_hmWriteOkay = CreateMutex (NULL, TRUE, TEXT ("XTALKWRT"));
rc = GetLastError();
if (rc == ERROR_ALREADY_EXISTS)
fFirstApp = FALSE;
// Wait here for ownership to ensure that the initialization is done.
// This is necessary since CreateMutex doesn't wait.
rc = WaitForSingleObject (g_hmWriteOkay, 2000);
if (rc != WAIT_OBJECT_0)
return 0;
// Create a file-mapping object.
g_hMMObj = CreateFileMapping ((HANDLE)-1, NULL, PAGE_READWRITE, 0,
MMBUFFSIZE, TEXT ("XTALKBLK"));
// Map into memory the file-mapping object.
g_pBuff = (PSHAREBUFF)MapViewOfFile (g_hMMObj, FILE_MAP_WRITE,
0, 0, 0);
// Initialize structure if first application started.
if (fFirstApp)
memset (g_pBuff, 0, sizeof (SHAREBUFF));
// Increment app running count. Interlock not needed due to mutex.
g_pBuff->nAppCnt++;
// Release the mutex. We need to release the mutex twice
// if we owned it when we entered the wait above.
ReleaseMutex (g_hmWriteOkay);
if (fFirstApp)
ReleaseMutex (g_hmWriteOkay);
// Now create events for read and send notification.
g_hSendEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
g_hReadEvent = CreateEvent (NULL, TRUE, FALSE, TEXT ("XTALKREAD"));
g_hReadDoneEvent = CreateEvent (NULL, FALSE, FALSE,
TEXT ("XTALKDONE"));
This code is responsible for creating the necessary synchronization objects as well as creating and initializing the shared memory block. The mutex object is created first with the parameters set to request initial ownership of the mutex object. A call is then made to GetLastError to determine whether the mutex object has already been created. If not, the application assumes that the first instance of XTalk is running and later will initialize the shared memory block. Once the mutex is created, an additional call is made to WaitForSingleObject to wait until the mutex is released. This call is necessary to prevent a late-starting instance of XTalk from disturbing communication in progress. Once the mutex is owned, calls are made to CreateFileMapping and MapViewOfFile to create a named memory-mapped object. Since the object is named, each process that opens the object opens the same object and is returned a pointer to the same block of memory.
Once the shared memory block is created, the first instance of XTalk zeroes out the block. This procedure also forces the block of RAM to be committed because memory-mapped objects by default are autocommit blocks. Then nAppCnt, which keeps a count of the running instances of XTalk, is incremented. Finally the mutex protecting the shared memory is released. If this is the first instance of XTalk, ReleaseMutex must be called twice because it gains ownership of the mutex twice—once when the mutex is created and again when the call to WaitForSingleObject is made.Finally, three event objects are created. SendEvent is an unnamed event, local to each instance of XTalk. The primary thread uses this event to signal the sender thread that the user has pressed the Send button and wants the text in the edit box transmitted. ReadEvent is a named event that tells the other instances of XTalk that there's data to be read in the transfer buffer. ReadDoneEvent is a named event signaled by each of the receiving copies of XTalk to indicate that they have read the data.The two threads, ReaderThread and SenderThread, are created immediately after the main window of XTalk is created. The code for SenderThread is shown here:
DWORD WINAPI SenderThread (PVOID pArg) {
HWND hWnd;
int nGoCode, rc;
TCHAR szText[TEXTSIZE];
hWnd = (HWND)pArg;
while (1) {
nGoCode = WaitForSingleObject (g_hSendEvent, INFINITE);
if (nGoCode == WAIT_OBJECT_0) {
SendDlgItemMessage (hWnd, IDD_OUTTEXT, WM_GETTEXT,
sizeof (szText), (LPARAM)szText);
rc = WaitForSingleObject (g_hmWriteOkay, 2000);
if (rc == WAIT_OBJECT_0) {
lstrcpy (g_pBuff->szText, szText);
g_pBuff->nReadCnt = g_pBuff->nAppCnt;
PulseEvent (g_hReadEvent);
// Wait while reader threads get data.
while (g_pBuff->nReadCnt)
rc = WaitForSingleObject (g_hReadDoneEvent,
INFINITE);
ReleaseMutex (g_hmWriteOkay);
}
}
}
return 0;
}
The routine waits on the primary thread of XTalk to signal SendEvent. The primary thread of XTalk makes the signal in response to a WM_COMMAND message from the Send button. The thread is then unblocked, reads the text from the edit control, and waits to gain ownership of the WriteOkay mutex. This mutex protects two copies of XTalk from writing to the shared block at the same time. When the thread owns the mutex, it writes the string read from the edit control into the shared buffer. It then copies the number of active copies of XTalk into the nReadCnt variable in the same shared buffer and pulses ReadEvent to tell the other copies of XTalk to read the newly written data. A manual resetting event is used so that all threads waiting on the event will be unblocked when the event is signaled.The thread then waits for the nReadCnt variable to return to 0. Each time a reader thread reads the data, the nReadCnt variable is decremented and the ReadDone event signaled. Note that the thread doesn't spin on this variable but uses an event to tell it when to check the variable again. This would actually be a great place to use WaitForMultipleObjects and have all reader threads signal when they've read the data, but Windows CE doesn't support the WaitAll flag in WaitForMultipleObjects.Finally, when all the reader threads have read the data, the sender thread releases the mutex protecting the shared segment and the thread returns to wait for another send event.The ReaderThread routine is even simpler. Here it is:
DWORD WINAPI ReaderThread (PVOID pArg) {
HWND hWnd;
int nGoCode, rc, i;
TCHAR szText[TEXTSIZE];
hWnd = (HWND)pArg;
while (1) {
nGoCode = WaitForSingleObject (g_hReadEvent, INFINITE);
if (nGoCode == WAIT_OBJECT_0) {
i = SendDlgItemMessage (hWnd, IDD_INTEXT, LB_ADDSTRING, 0,
(LPARAM)g_pBuff->szText);
SendDlgItemMessage (hWnd, IDD_INTEXT, LB_SETTOPINDEX, i, 0);
InterlockedDecrement (&g_pBuff->nReadCnt);
SetEvent (g_hReadDoneEvent);
}
}
return 0;
}
The reader thread starts up and immediately blocks on ReadEvent. When it's unblocked, it adds the text from the shared buffer into the list box in its window. The list box is then scrolled to show the new line. After this is accomplished, the nReadCnt variable is decremented using InterlockedDecrement to be thread safe, and the ReadDone event is signaled to tell SenderThread to check the read count. After that's accomplished, the routine loops around and waits for another read event to occur.