Data Sharing [C++] [04]: Between Processes

7 minute read

Published:

Codes in this post can be found in folder DataSharingBetweenProcesses.

Inter Process Communication (IPC) refers to a mechanism, where the operating systems allow various processes to communicate with each other. This involves synchronizing their actions and managing shared data. There are many methods to realize this, such as Pipes, FIFO, Message Queues, Shared Memory, Semaphores, Signals. In our projects, shared memory has been used for sharing data between processes. We can either use File mapping for Windows or cross-platform library boost.interprocess from boost.


File Mapping

The procedures are very simple, firstly, process 1 creates a File Mapping Object in the physical memory using CreateFileMapping function. Then, process 2 can open the file mapping using OpenFileMapping function and and create a File View in virtual memory to access the file’s contents using MapViewOfFile function.

text

The file on disk can be any file that the users want to map into memory, or it can be the system page file. Processes use pointers to read from and write to the files, just as they would with dynamically allocated memory. Finally, When a process has finished with the file mapping object, it should destroy all file views in its address space by using the UnmapViewOfFile function for each file view. Below is a sample test code. The write process writes a timestamp to the file every 10 ms, and the read process reads the timestamp and print it out.

// Write Process

#include <Windows.h>
#include <iostream>
#include <string>
#include <cstring>
#include <chrono>
#include <ctime>
#include <thread>

using namespace std;
#pragma warning(disable:4996)
string timer();

int main(){
    string strMapName("ShareMemory");                
    string strComData("This is common data!");        
    LPVOID pBuffer;                                   
    LPVOID pBufferOld;                                   

    HANDLE hMap = ::OpenFileMapping(FILE_MAP_ALL_ACCESS, 0, (LPCWSTR)strMapName.c_str());
    if (NULL == hMap){    
        // Fail, create a file mapping
        hMap = ::CreateFileMapping(INVALID_HANDLE_VALUE,
                                   NULL,
                                   PAGE_READWRITE,
                                   0,
                                   strComData.length() + 1,
                                   (LPCWSTR)strMapName.c_str());

        // Create a file view and write data to the shared ameamory
        pBuffer = ::MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
        strcpy((char*)pBuffer, strComData.c_str());
        cout << "Write data to shared memory: " << (char*)pBuffer << endl;
    }
    else{    
        // Succeed, create a file view and read data from the shared memory
        pBuffer = ::MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
        cout << "Read data from shared memory: " << (char*)pBuffer << endl;
	}
    
    while (1) {
        // Initialize the file with "0"
        string dataInit = "0";
        pBuffer = ::MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);  // open
        strcpy((char*)pBuffer, dataInit.c_str());                       // write       
        std::this_thread::sleep_for(std::chrono::milliseconds(5));
        
        pBuffer = ::MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);  // open
        data = timer();
        strcpy((char*)pBuffer, data.c_str());                           // write
        std::this_thread::sleep_for(std::chrono::milliseconds(5));
	}         

    // Release
    ::UnmapViewOfFile(pBuffer);
    ::CloseHandle(hMap);

    return 0;
}

string timer() {
    // current date/time based on current system
    static stringstream timeS;
    static time_t now;
    static tm* ltm;

    now = time(0);
    ltm = localtime(&now);
    auto millisec_since_epoch = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
    timeS.str("");
    timeS << 1900 + ltm->tm_year << "." << 1 + ltm->tm_mon << "." << ltm->tm_mday << " " << ltm->tm_hour << ":" << ltm->tm_min << ":" << ltm->tm_sec << "." << millisec_since_epoch % 1000;

    return timeS.str();
}
// Read Process

#include <Windows.h>
#include <iostream>
#include <string>
#include <cstring>
#include <chrono>
#include <ctime>
#include <thread>

using namespace std;

#pragma warning(disable:4996)

int main(){
    string strMapName("ShareMemory");                
    string strComData("This is common data!");        
    LPVOID pBuffer;                                   
    LPVOID pBufferOld;                                   

    HANDLE hMap = ::OpenFileMapping(FILE_MAP_ALL_ACCESS, 0, (LPCWSTR)strMapName.c_str());
    if (NULL == hMap){    
        // Fail, create a file mapping
        hMap = ::CreateFileMapping(INVALID_HANDLE_VALUE,
                                   NULL,
                                   PAGE_READWRITE,
                                   0,
                                   strComData.length() + 1,
                                   (LPCWSTR)strMapName.c_str());

        // Create a file view and write data to the shared ameamory
        pBuffer = ::MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
        strcpy((char*)pBuffer, strComData.c_str());
        cout << "Write data to shared memory: " << (char*)pBuffer << endl;
    }
    else{    
        // Succeed, create a file view and read data from the shared memory
        pBuffer = ::MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
        cout << "Read data from shared memory: " << (char*)pBuffer << endl;
	}
    
    while (1) {
        pBuffer = ::MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0); 
        string dataS((char*)pBuffer);
        if (dataS[0] == '2') {
            cout << "Timestamp: " << dataS << endl;
        }
    }         

    // Release
    ::UnmapViewOfFile(pBuffer);
    ::CloseHandle(hMap);

    return 0;
}

Fallible Points

One thing needs to mention is that on different windows platforms, CreateFileMapping and OpenFileMapping are defined a bit differently. They are difined in header files memoryapi.h and winbase.h respectively.

In memoryapi.h, CreateFileMapping and OpenFileMapping are defined as follows.

CreateFileMappingW(
    _In_ HANDLE hFile,
    _In_opt_ LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
    _In_ DWORD flProtect,
    _In_ DWORD dwMaximumSizeHigh,
    _In_ DWORD dwMaximumSizeLow,
    _In_opt_ LPCWSTR lpName
    );

#ifdef UNICODE
#define CreateFileMapping  CreateFileMappingW
#endif

OpenFileMappingW(
    _In_ DWORD dwDesiredAccess,
    _In_ BOOL bInheritHandle,
    _In_ LPCWSTR lpName
    );

#ifdef UNICODE
#define OpenFileMapping  OpenFileMappingW
#endif

In winbase.h, CreateFileMapping and OpenFileMapping are defined as follows.

CreateFileMappingA(
    _In_     HANDLE hFile,
    _In_opt_ LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
    _In_     DWORD flProtect,
    _In_     DWORD dwMaximumSizeHigh,
    _In_     DWORD dwMaximumSizeLow,
    _In_opt_ LPCSTR lpName
    );
    
#ifndef UNICODE
#define CreateFileMapping  CreateFileMappingA
#endif

OpenFileMappingA(
    _In_ DWORD dwDesiredAccess,
    _In_ BOOL bInheritHandle,
    _In_ LPCSTR lpName
    );
    
#ifndef UNICODE
#define OpenFileMapping  OpenFileMappingA
#endif

The main difference is that in memoryapi.h, the data type of the name of the shared memory is LPCWSTR; in winbase.h, the data type of the name of the shared memory is LPCSTR. When different functions are used in two processes, the memory cannot be shared. Therefore, a good practice is to use the name OpenFileMappingW/A and CreateFileMappingW/A directly, so as to avoid bugs that are difficult to find.


Boost Library

Realizing shared memory using boost library is also very simple.

Fisrtly, process 1 requests to the operating system a memory segment that can be shared between processes using the shared_memory_object class. After setting the size of the memory using shared_memory_object.truncate(SIZE_OF_MEMORY). The process can map the whole shared memory or just part of it. The mapping process is done using the mapped_region class.

Then, process 2 can access the shared memory according to the name of the memory, just like that with the File Mapping.

Finally, the memory should be removed using method shared_memory_object::remove("NAME").

Below is a sample test code. The write process writes ‘1’s to the shared memory, and the read process prints it out.

// Write Process

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <boost/interprocess/shared_memory_object.hpp>
#include <boost/interprocess/mapped_region.hpp>
#include <cstring>
#include <cstdlib>
#include <string>
#include <thread>
#include <chrono>

using namespace boost::interprocess;
using namespace std;

int main(){
    cout << "P1 started" << endl;
	
    this_thread::sleep_for(chrono::milliseconds(10u));
	
    // Create a shared memory object
    shared_memory_object shm(create_only, "MySharedMemory", read_write);
	
    // Set size
    shm.truncate(100);
	
    // Map the whole shared memory in this process
    mapped_region region(shm, read_write);
	
    // Write all the memory to 1
    std::memset(region.get_address(), 1, region.get_size());
	
    cout << "P1 ended" << endl;
	
    return 0;
}
// Read Process

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <boost/interprocess/shared_memory_object.hpp>
#include <boost/interprocess/mapped_region.hpp>
#include <cstring>
#include <cstdlib>
#include <string>
#include <thread>
#include <chrono>

using namespace boost::interprocess;
using namespace std;

int main(){
    cout << "P2 started" << endl;
	
    // Create a shared memory object
    shared_memory_object shm(open_only, "MySharedMemory", read_only);
	
    //Map the whole shared memory in this process
    mapped_region region(shm, read_only);
	
    const char* mem = static_cast<char*>(region.get_address());

    // check the memory
    bool isError = false;
    for(auto i = 0u; i < region.get_size(); ++i){
        const int data = *(mem + i);
        printf("data %i is %d\n", i, data);
        if (data != 1) {
            isError = true;
            break;
        }
    }
    // Realease
    shared_memory_object::remove("MySharedMemory");
	
    cout << "P2 ended" << endl;
	
    return 0;
}

Table of Contents

Comments