![]() |
General Programming »
Threads, Processes & IPC »
General
Beginner
License: The Code Project Open License (CPOL)
Interprocess communication tutorialBy Alex FrUsing memory mapped files, mutexes and HWND_BROADCAST messages for interprocess communication |
VC6Win2K, Dev
|
|
Advanced Search |
|
|
|
||||||||||||||||
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:
HWND_BROADCAST parameter
to notify other instances when text is changed.The projects are built using MFC, this allows to concentrate on interprocess communication features and not on boring implementation details.
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.
IDC_EDIT_BOX. Arrange controls on dialog template.CEdit m_edit_box to
CSameTextDlg class. Member is connected to IDC_EDIT_BOX
edit box.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.// Registered window message posted when user changes text in edit box const UINT wm_Message = RegisterWindowMessage(_T("CC667211-7CE9-40c5-809A-1DA48E4014C4"));String constant is copied using GUIDGen utility which comes with Microsoft Visual Studio.
EN_CHANGE message handler to
CSameTextDlg class for IDC_EDIT_BOX control. This
function is called when user changes text in edit box.wm_Message to
CSameTextDlg class. Add OnMessageTextChanged function
definition to SameTextDlg.h:
//}}AFX_MSG
afx_msg LRESULT OnMessageTextChanged(WPARAM wParam, LPARAM lParam);
DECLARE_MESSAGE_MAP()
Add entry to CSameTextDlg message map in SameTextDlg.cpp:
//}}AFX_MSG_MAP
ON_REGISTERED_MESSAGE(wm_Message, OnMessageTextChanged)
END_MESSAGE_MAP()
Add OnMessageTextChanged function to SameTextDlg.cpp:
LRESULT CSameTextDlg::OnMessageTextChanged(WPARAM wParam, LPARAM lParam)
{
return 0;
}
// Function is called when text in edit box is changed // by user void CSameTextDlg::OnChangeEditBox() { if ( m_bNotify ) // change is not done by SetWindowText { // Notify all running instances that text was changed ::PostMessage(HWND_BROADCAST, wm_Message, (WPARAM) m_hWnd, 0); } } // Handler for registered window message wm_Message // which is posted to all top-level windows in the system // when user changes text in edit box in any running instance // of this program. // wParam contains dialog handler. LRESULT CSameTextDlg::OnMessageTextChanged(WPARAM wParam, LPARAM lParam) { // If message is posted from this instance do nothing if ( wParam == (WPARAM) m_hWnd ) return 0; CString s; m_edit_box.GetWindowText(s); s += _T("*"); m_bNotify = FALSE; // prevent handling EN_CHANGE // when text is changed from here and not by user 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.
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.
// number of characters in memory-mapped file const DWORD dwMemoryFileSize = 4 * 1024; // memory-mapped file name const LPCTSTR sMemoryFileName = _T("D9287E19-6F9E-45fa-897C-D392F73A0F2F");
The string constant is copied using the GUIDGen utility.
HANDLE m_hFileMapping and
LPVOID m_pViewOfFile to the CSameTextDlg class.
Set them to NULL in the class constructor.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);
}
Initialize to the CSameTextDlg
class and call it from OnInitDialog.Initialize function:
// initialization void CSameTextDlg::Initialize() { // Ensure that text in edit box will not be longer // than memory file. Keep one character for end of string m_edit_box.SetLimitText(dwMemoryFileSize - 1); // Create file mapping which can contain dwMemoryFileSize characters m_hFileMapping = CreateFileMapping( INVALID_HANDLE_VALUE, // system paging file NULL, // security attributes PAGE_READWRITE, // protection 0, // high-order DWORD of size dwMemoryFileSize*sizeof(TCHAR), // low-order DWORD of size sMemoryFileName); // name DWORD dwError = GetLastError(); // if ERROR_ALREADY_EXISTS // this instance is not first (other instance created file mapping) if ( ! m_hFileMapping ) { MessageBox(_T("Creating of file mapping failed")); } else { m_pViewOfFile = MapViewOfFile( m_hFileMapping, // handle to file-mapping object FILE_MAP_ALL_ACCESS, // desired access 0, 0, 0); // map all file if ( ! m_pViewOfFile ) { MessageBox(_T("MapViewOfFile failed")); } // Now we have m_pViewOfFile memory block which is common for // all instances of this program } if ( dwError == ERROR_ALREADY_EXISTS ) { // Some other instance is already running, // get text from existing file mapping 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.
GetTextFromMemoryMappedFile
to the CSameTextDlg class and implement it as:
// Get text from memory mapped file and set it to edit box. // Called from: // 1) OnMessageTextChanged, when user changed text in some // other instance of this program // 2) OnInitDialog, so that new instance gets text from // running instance void CSameTextDlg::GetTextFromMemoryMappedFile() { if ( m_pViewOfFile ) { // read text from memory-mapped file TCHAR s[dwMemoryFileSize]; lstrcpy(s, (LPCTSTR) m_pViewOfFile); // Write text to edit box. // SetWindowText raises EN_CHANGE event and // OnChangeEditBox is called. Ensure that OnChangeEditBox // does nothing by setting m_bNotify to FALSE 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.
OnMessageTextChanged
so that it looks like this:
LRESULT CSameTextDlg::OnMessageTextChanged(WPARAM wParam, LPARAM lParam)
{
// If message is posted from this instance do nothing
if ( wParam == (WPARAM) m_hWnd )
return 0;
// Get text from memory mapped file and set it to edit box
GetTextFromMemoryMappedFile();
return 0;
}
OnChangeEditBox
so that it looks like this:
void CSameTextDlg::OnChangeEditBox() { if ( m_bNotify ) // change is not done by SetWindowText { // write text to memory-mapped file if ( m_pViewOfFile ) { TCHAR s[dwMemoryFileSize]; m_edit_box.GetWindowText(s, dwMemoryFileSize); lstrcpy( (LPTSTR) m_pViewOfFile, s); // Notify all running instances that text was changed ::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.
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.
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.// mutex name const LPCTSTR sMutexFileName = _T("4BD625E8-C16F-4836-9F62-60E151CCE3EC"); // maxinum number of milliseconds to wait const DWORD dwMaxWait = 200;
to the start of the SameTextDlg.cpp file. The
string constant is copied using the GUIDGen utility.
CSameTextDlg::Initialize,
before if ( dwError == ERROR_ALREADY_EXISTS ):
// create mutex used to syncronize access to m_pViewOfFile
// between instances of this program
m_hMutex = CreateMutex(
NULL, // security attributes
FALSE, // initial owner
sMutexFileName); // name
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.
OnChangeEditBox
and GetTextFromMemoryMappedFile
so that they look like this:
// Function is called when text in edit box is changed // by user void CSameTextDlg::OnChangeEditBox() { if ( m_bNotify ) // change is not done by SetWindowText { // write text to memory-mapped file if ( m_pViewOfFile && m_hMutex ) { TCHAR s[dwMemoryFileSize]; m_edit_box.GetWindowText(s, dwMemoryFileSize); // get exclusive access to common memory block if ( WaitForSingleObject(m_hMutex, dwMaxWait) == WAIT_OBJECT_0 ) { lstrcpy( (LPTSTR) m_pViewOfFile, s); ReleaseMutex(m_hMutex); // Notify all running instances that text was changed ::PostMessage(HWND_BROADCAST, wm_Message, (WPARAM) m_hWnd, 0); } } } } // Get text from memory mapped file and set it to edit box. // Called from: // 1) OnMessageTextChanged, when user changed text in some // other instance of this program // 2) OnInitDialog, so that new instance gets text from // running instance void CSameTextDlg::GetTextFromMemoryMappedFile() { if ( m_pViewOfFile && m_hMutex ) { // read text from memory-mapped file TCHAR s[dwMemoryFileSize]; // get exclusive access to common memory region if ( WaitForSingleObject(m_hMutex, dwMaxWait) == WAIT_OBJECT_0 ) { lstrcpy(s, (LPCTSTR) m_pViewOfFile); ReleaseMutex(m_hMutex); // Write text to edit box. // SetWindowText raises EN_CHANGE event and // OnChangeEditBox is called. Ensure that OnChangeEditBox // does nothing by setting m_bNotify to FALSE 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.
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:
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.
class CIPCReadWriteLock;
to SameTextDlg.h. Replace the member HANDLE m_hMutex to
CIPCReadWriteLock* m_pLock
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;
}
CSameTextDlg::Initialize replace the creating of the
mutex with the lines:
// create the Single Writer/Multiple Reader Guard
m_pLock = new CIPCReadWriteLock(
sReadWriteLockName,
dwMaxWait);
In the start of SameTextDlg.cpp replace the constant name sMutexFileName
with sReadWriteLockName.
OnChangeEditBox
and GetTextFromMemoryMappedFile
so that they look like this:
// Function is called when text in edit box is changed // by user void CSameTextDlg::OnChangeEditBox() { if ( m_bNotify ) // change is not done by SetWindowText { // write text to memory-mapped file if ( m_pViewOfFile && m_pLock ) { TCHAR s[dwMemoryFileSize]; m_edit_box.GetWindowText(s, dwMemoryFileSize); // get write access to common memory block if ( m_pLock->WaitToWrite() ) { lstrcpy( (LPTSTR) m_pViewOfFile, s); m_pLock->Done(); // Notify all running instances that text was changed ::PostMessage(HWND_BROADCAST, wm_Message, (WPARAM) m_hWnd, 0); } } } } // Get text from memory mapped file and set it to edit box. // Called from: // 1) OnMessageTextChanged, when user changed text in some // other instance of this program // 2) OnInitDialog, so that new instance gets text from // running instance void CSameTextDlg::GetTextFromMemoryMappedFile() { if ( m_pViewOfFile && m_pLock ) { // read text from memory-mapped file TCHAR s[dwMemoryFileSize]; // get read access to common memory region if ( m_pLock->WaitToRead() ) { lstrcpy(s, (LPCTSTR) m_pViewOfFile); m_pLock->Done(); // Write text to edit box. // SetWindowText raises EN_CHANGE event and // OnChangeEditBox is called. Ensure that OnChangeEditBox // does nothing by setting m_bNotify to FALSE m_bNotify = FALSE; m_edit_box.SetWindowText(s); m_bNotify = TRUE; } } }
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 7 Oct 2001 Editor: Chris Maunder |
Copyright 2001 by Alex Fr Everything else Copyright © CodeProject, 1999-2009 Web11 | Advertise on the Code Project |