Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Interprocess communication tutorial

0.00/5 (No votes)
7 Oct 2001 1  
Using memory mapped files, mutexes and HWND_BROADCAST messages for interprocess communication

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:

Sample Image

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:
    // 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.
  • 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
    
        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;
    }
    
  • Write implementation of message handlers:
    // 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.

  • 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:
    // 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.

  • 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:
    // 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.

  • Add the protected function 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.

  • Change the implementation of 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;
    }
    
  • Change the implementation of 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.

  • 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:
    // 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.

  • Add the next lines to the function 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.

  • Change the implementation of functions 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.

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:
        // 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.

  • Change the implementation of the functions 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;
            }
        }
    }
    

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here