Introduction
In this tutorial I build a dialog-based application with an edit box. When the
number of instances of such an application are running, changes made by the user
in one edit box are reflected in all other instances. That means, all instances
always have the same text in their edit box. It looks like this:

Tutorial contains four steps:
- Step 1 - posting registered window message with
HWND_BROADCAST
parameter
to notify other instances when text is changed.
- Step 2 - using memory-mapped file to set the same text in all instances.
- Step 3 - synchronizing access to common memory block between all instances.
- Step 4 - advanced synchronization using inter-process
Single Writer/Multiple Reader Guard. (New)
The projects are built using MFC, this allows to concentrate on interprocess communication
features and not on boring implementation details.
Step 1. Notification.
On this step we build dialog-based application which notifies other instances
that text in edit box is changed by user. Dialog which receives such message
handles it by some way - for example, adds "*" to text in edit box.
- Create new MFC dialog-based application. Call in SameText. Remove Cancel button
from dialog template; set "Close" text to IDOK button. Add edit box to dialog
and set it's ID to
IDC_EDIT_BOX
. Arrange controls on dialog template.
- Using MFC Class Wizard, add member
CEdit m_edit_box
to
CSameTextDlg
class. Member is connected to IDC_EDIT_BOX
edit box.
- Add protected member
BOOL m_bNotify
to CSameTextDlg
class
and set it to TRUE in constructor. This member will be used in EN_CHANGE
message handler to prevent program reaction when text in edit box is changed by
SetWindowText
and not by user.
- Add next constant to the start of SameTextDlg.cpp file:
const UINT wm_Message =
RegisterWindowMessage(_T("CC667211-7CE9-40c5-809A-1DA48E4014C4"));
String constant is copied using GUIDGen utility which comes with
Microsoft Visual Studio.
- Using MFC Class Wizard, add
EN_CHANGE
message handler to
CSameTextDlg
class for IDC_EDIT_BOX
control. This
function is called when user changes text in edit box.
- Add manually handling of registered window message
wm_Message
to
CSameTextDlg
class. Add OnMessageTextChanged
function
definition to SameTextDlg.h:
afx_msg LRESULT OnMessageTextChanged(WPARAM wParam, LPARAM lParam);
DECLARE_MESSAGE_MAP()
Add entry to CSameTextDlg
message map in SameTextDlg.cpp:
ON_REGISTERED_MESSAGE(wm_Message, OnMessageTextChanged)
END_MESSAGE_MAP()
Add OnMessageTextChanged
function to SameTextDlg.cpp:
LRESULT CSameTextDlg::OnMessageTextChanged(WPARAM wParam, LPARAM lParam)
{
return 0;
}
- Write implementation of message handlers:
void CSameTextDlg::OnChangeEditBox()
{
if ( m_bNotify )
{
::PostMessage(HWND_BROADCAST,
wm_Message,
(WPARAM) m_hWnd,
0);
}
}
LRESULT CSameTextDlg::OnMessageTextChanged(WPARAM wParam, LPARAM lParam)
{
if ( wParam == (WPARAM) m_hWnd )
return 0;
CString s;
m_edit_box.GetWindowText(s);
s += _T("*");
m_bNotify = FALSE;
m_edit_box.SetWindowText(s);
m_bNotify = TRUE;
return 0;
}
When the program sets text in the edit box using SetWindowText
,
the EN_CHANGE
message handler is called. I don't want to handle
this case and use the m_bNotify
member for this. When the program
posts a message with the HWMD_BROADCAST
parameter, it is posted
to all top-level windows in the system, including the window
which posted the message. To prevent the handling this message in the same
window I wrote window handler in wParam
parameter.
- Build the project and run some number of instances. Change the text
in the edit box in any instance - symbol "*" is added to the edit box text
in all other instances.
Step 2. Using a memory-mapped file.
In this step I use the project built on step 1 and add to it a named
memory-mapped file which contains an edit box text and is available
from all running instances.
- Add the next constants to the start of SameTextDlg.cpp file:
const DWORD dwMemoryFileSize = 4 * 1024;
const LPCTSTR sMemoryFileName = _T("D9287E19-6F9E-45fa-897C-D392F73A0F2F");
The string constant is copied using the GUIDGen utility.
- Add protected members
HANDLE m_hFileMapping
and
LPVOID m_pViewOfFile
to the CSameTextDlg
class.
Set them to NULL in the class constructor.
- Using the MFC Class Wizard, add a
WM_DESTROY
message handler to
the CSameTextDlg
class. Add the next lines to this function:
if ( m_hFileMapping )
{
if ( m_pViewOfFile )
{
UnmapViewOfFile(m_pViewOfFile);
}
CloseHandle(m_hFileMapping);
}
- Add the protected function
Initialize
to the CSameTextDlg
class and call it from OnInitDialog
.
- Write the implementation of the
Initialize
function:
void CSameTextDlg::Initialize()
{
m_edit_box.SetLimitText(dwMemoryFileSize - 1);
m_hFileMapping = CreateFileMapping(
INVALID_HANDLE_VALUE,
NULL,
PAGE_READWRITE,
0,
dwMemoryFileSize*sizeof(TCHAR),
sMemoryFileName);
DWORD dwError = GetLastError();
if ( ! m_hFileMapping )
{
MessageBox(_T("Creating of file mapping failed"));
}
else
{
m_pViewOfFile = MapViewOfFile(
m_hFileMapping,
FILE_MAP_ALL_ACCESS,
0,
0,
0);
if ( ! m_pViewOfFile )
{
MessageBox(_T("MapViewOfFile failed"));
}
}
if ( dwError == ERROR_ALREADY_EXISTS )
{
GetTextFromMemoryMappedFile();
}
}
The
CreateFileMapping
function creates a file mapping
object. The INVALID_HANDLE_VALUE
parameter means that this object
is backed up in the system paging file and not in any other file.
MapViewOfFile
maps the file mapping object to the virtual memory of
the calling process. Since CreateFileMapping
is called with the
same name in all program instances, the memory block pointed by
m_pViewOfFile
is common for all instances and may be used
for interprocess communication.
If program tries to create a named file mapping
object which already exists, the call doesn't fail and GetLastError
returns ERROR_ALREADY_EXISTS
. I use this to fill the edit box when
the program starts - if this is not a first instance, it is a good idea to set
the same text as in all running instances.
If we allow the user to enter some number of characters to the edit box and want
to keep them in a memory-mapped file, it's size should be doubled in a UNICODE
configuration. Therefore I use sizeof(TCHAR)
in call to
CreateFileMapping
.
- Add the protected function
GetTextFromMemoryMappedFile
to the CSameTextDlg
class and implement it as:
void CSameTextDlg::GetTextFromMemoryMappedFile()
{
if ( m_pViewOfFile )
{
TCHAR s[dwMemoryFileSize];
lstrcpy(s, (LPCTSTR) m_pViewOfFile);
m_bNotify = FALSE;
m_edit_box.SetWindowText(s);
m_bNotify = TRUE;
}
}
This function reads text from the common memory region and sets it
to the edit box.
- Change the implementation of
OnMessageTextChanged
so that it looks like this:
LRESULT CSameTextDlg::OnMessageTextChanged(WPARAM wParam, LPARAM lParam)
{
if ( wParam == (WPARAM) m_hWnd )
return 0;
GetTextFromMemoryMappedFile();
return 0;
}
- Change the implementation of
OnChangeEditBox
so that it looks like this:
void CSameTextDlg::OnChangeEditBox()
{
if ( m_bNotify )
{
if ( m_pViewOfFile )
{
TCHAR s[dwMemoryFileSize];
m_edit_box.GetWindowText(s, dwMemoryFileSize);
lstrcpy( (LPTSTR) m_pViewOfFile, s);
::PostMessage(HWND_BROADCAST,
wm_Message,
(WPARAM) m_hWnd,
0);
}
}
}
This function writes text to the common memory region and notifies
all other running instances about this change.
- Build the project and run some number of instances. Change the text
in the edit box in any instance - all other instances get the same text.
Step 3. Synchronization.
When the number of processes read/write the same memory block, we need to synchronize
access to it. I think this particular program will work fine without
synchronization, but more complicated programs should use this feature.
In this step I use project build on step 2 and add to it named mutex object.
-
Add a protected member
HANDLE m_hMutex
to the CSameTextDlg
class and set it to NULL in the constructor. Add the next lines:
if ( m_hMutex )
{
CloseHandle(m_hMutex);
}
to CSameTextDlg::OnDestroy
.
- Add the constants:
const LPCTSTR sMutexFileName = _T("4BD625E8-C16F-4836-9F62-60E151CCE3EC");
const DWORD dwMaxWait = 200;
to the start of the SameTextDlg.cpp file. The
string constant is copied using the GUIDGen utility.
- Add the next lines to the function
CSameTextDlg::Initialize
,
before if ( dwError == ERROR_ALREADY_EXISTS )
:
m_hMutex = CreateMutex(
NULL,
FALSE,
sMutexFileName);
if ( ! m_hMutex )
{
MessageBox(_T("CreateMutex failed"));
}
All instances call CreateMutex
with the same name,
so they get a handle to the same mutex object which will be used
to synchronize access to the common memory block.
- Change the implementation of functions
OnChangeEditBox
and GetTextFromMemoryMappedFile
so that they look like this:
void CSameTextDlg::OnChangeEditBox()
{
if ( m_bNotify )
{
if ( m_pViewOfFile && m_hMutex )
{
TCHAR s[dwMemoryFileSize];
m_edit_box.GetWindowText(s, dwMemoryFileSize);
if ( WaitForSingleObject(m_hMutex, dwMaxWait) == WAIT_OBJECT_0 )
{
lstrcpy( (LPTSTR) m_pViewOfFile, s);
ReleaseMutex(m_hMutex);
::PostMessage(HWND_BROADCAST,
wm_Message,
(WPARAM) m_hWnd,
0);
}
}
}
}
void CSameTextDlg::GetTextFromMemoryMappedFile()
{
if ( m_pViewOfFile && m_hMutex )
{
TCHAR s[dwMemoryFileSize];
if ( WaitForSingleObject(m_hMutex, dwMaxWait) == WAIT_OBJECT_0 )
{
lstrcpy(s, (LPCTSTR) m_pViewOfFile);
ReleaseMutex(m_hMutex);
m_bNotify = FALSE;
m_edit_box.SetWindowText(s);
m_bNotify = TRUE;
}
}
}
Now before using the common memory block I get exclusive
access to it by waiting for the mutex. After using the block I immediately
release the mutex.
Step 4. Advanced synchronization using inter-process
Single Writer/Multiple Reader Guard
The synchronization used in step 3 project is not optimal. The best way
is to use a single-writer and multiple-reader synchronization.
The book Programming Applications for Microsoft Windows
published by Microsoft Press contains the
Single Writer/Multiple Reader Guard for in-process synchronization
CSWMRG
(Copyright (c) 2000 Jeffrey Richter).
My class CIPCReadWriteLock
(the Single Writer/Multiple
Reader Guard for inter-process communication) is based on
Jeffrey Richter's CSWMRG
class and may be used
for inter-process communication. The description of this class
is out of the scope of this tutorial. For details, see:
- Single Writer/Multiple Reader Guard algorithm -
the Jeffrey Richter's boook Programming Applications for Microsoft Windows
(Copyright (c) 2000 Jeffrey Richter).
- Inter-process communication features added to class -
CIPCReadWriteLock
class source code (included to demo project).
The class CIPCReadWriteLock
is initialized with some name
and maximum number of milliseconds to wait. It has functions:
WaitToRead
, WaitToWrite
and
Done
. Using these functions we can get read/write
access to common resources and release common resources.
In this step I use the project built on step 3 and replace the mutex with
the CIPCReadWriteLock
class.
- Add the files IPCReadWriteLock.h and IPCReadWriteLock.cpp to the project.
Add the line
class CIPCReadWriteLock;
to SameTextDlg.h. Replace the member HANDLE m_hMutex
to
CIPCReadWriteLock* m_pLock
.
- In the
CSameTextDlg
constructor replace the line
m_hMutex = NULL;
with the line m_pLock = NULL;
.
In the CSameTextDlg::OnDestroy
replace releasing of
mutex with the lines:
if ( m_pLock )
{
delete m_pLock;
}
- In the
CSameTextDlg::Initialize
replace the creating of the
mutex with the lines:
m_pLock = new CIPCReadWriteLock(
sReadWriteLockName,
dwMaxWait);
In the start of SameTextDlg.cpp replace the constant name sMutexFileName
with sReadWriteLockName
.
- Change the implementation of the functions
OnChangeEditBox
and GetTextFromMemoryMappedFile
so that they look like this:
void CSameTextDlg::OnChangeEditBox()
{
if ( m_bNotify )
{
if ( m_pViewOfFile && m_pLock )
{
TCHAR s[dwMemoryFileSize];
m_edit_box.GetWindowText(s, dwMemoryFileSize);
if ( m_pLock->WaitToWrite() )
{
lstrcpy( (LPTSTR) m_pViewOfFile, s);
m_pLock->Done();
::PostMessage(HWND_BROADCAST,
wm_Message,
(WPARAM) m_hWnd,
0);
}
}
}
}
void CSameTextDlg::GetTextFromMemoryMappedFile()
{
if ( m_pViewOfFile && m_pLock )
{
TCHAR s[dwMemoryFileSize];
if ( m_pLock->WaitToRead() )
{
lstrcpy(s, (LPCTSTR) m_pViewOfFile);
m_pLock->Done();
m_bNotify = FALSE;
m_edit_box.SetWindowText(s);
m_bNotify = TRUE;
}
}
}