Shared Memory is the fastest way to share some data between two processes. Concept is very simple – one process will create an area in the Memory which other process can access it.
Creating shared memory is not so difficult as it sounds – In general, it is kind of virtual file that one process creates, and second process read “content” of it.
Code for first process:
// buffer size for 1024 Unicode chars (wchar_t) #define BUF_SIZE 2048 int _tmain(int argc, _TCHAR* argv[]) { HANDLE hFile = ::CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, BUF_SIZE, L"Global\\www.cpp-blog.com"); if (NULL == hFile) { wprintf(L"CreateFileMapping Failed!"); return 1; } wchar_t* pBuf = (wchar_t*)::MapViewOfFile(hFile, FILE_MAP_ALL_ACCESS, 0, 0, BUF_SIZE); if (pBuf == NULL) { wprintf(L"MapViewOfFile Failed!\n"); ::CloseHandle(hFile); return 1; } // here pBuf is a buffer where we could store everything we need // NOTE: be carefully - this buffer can contain maximum BUF_SIZE/2 chars // let's write something in the buffer now const wchar_t* data = L"welcome to IPC introduction from www.cpp-blog.com"; ::CopyMemory((PVOID)pBuf, data, (wcslen(data) * sizeof(wchar_t))); _getwch(); // just "pause" ::UnmapViewOfFile(pBuf); ::CloseHandle(hFile); return 0; }
Code for second process:
#define BUF_SIZE 2048 int _tmain(int argc, _TCHAR* argv[]) { // here we have to use the same name HANDLE hFile = ::OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, L"Global\\www.cpp-blog.com"); if (NULL == hFile) { wprintf(L"OpenFileMapping Failed!"); return 1; } // now comes the same code like in on the server side wchar_t* pBuf = (wchar_t*)::MapViewOfFile(hFile, FILE_MAP_ALL_ACCESS, 0, 0, BUF_SIZE); if (pBuf == NULL) { wprintf(L"MapViewOfFile Failed!\n"); ::CloseHandle(hFile); return 1; } wprintf(L"Server told us : %s\n", pBuf); ::UnmapViewOfFile(pBuf); ::CloseHandle(hFile); return 0; }
Here, very important is the name of the file (Global\www.cpp-blog.com) and Buffer size (2048). Server and Client must know those values to be able to share some data. “Global\” prefix/namespace is required for sharing memory between two processes. In this case windows will “register” name in global mapping table accessible for all programs running on the system.
Synchronization
The most difficult thing using Shared Memory IPC is a synchronization. Server doesn’t know when Client is finished with reading data, but must keep memory valid all the time. Client could write also something in the memory, but it must inform server when reading is finished.
You can use additional windows messages (check previous blog post) to inform other side when something new is in the buffer, but I prefer other way – using Windows Synchronization Objects.
Main synchronization objects are Event, Mutex and Semaphore.
Event – notifies when something happens – one side can wait for event, and other side can trigger it
Mutex – some kind of a locking flag – only one process/thread could own it at the same time
Semaphore – kind of a counter - e.g. only 3 threads could access some resource at the same time
For our purposes we need two events (ServerNewData event and ClientReadFinish event) and one Mutex to prevent accessing to the shared memory at the same time.
ServerNewData event will be triggered from the Server when some new data are stored in a buffer. After that, server can continue, but will not write new data in the shared memory before ClientReadFinish event is triggered to avoid that old data are not overridden before client read it. Basically, mutex is not really required here because client will never read before ServerNewData is triggered and server will never write before ClientReadFinish, but I like to have a mutex for any kind of “shared” data I use.
We are going to build real example, where client is going to start server, and server will search for all files on the C drive and over shared memory give the data back to the client. Instead of plain wchar_t buffer we are going to use WIN32_FIND_DATA struct.
Client
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | #include "stdafx.h" #include int _tmain(int argc, _TCHAR* argv[]) { HANDLE hServerNewDataEvent = ::CreateEvent(NULL, FALSE, FALSE, L"Global\\IPC_Test_ServerNewData"); HANDLE hClientReadFinish = ::CreateEvent(NULL, FALSE, TRUE, L"Global\\IPC_Test_ClientReadFinish"); HANDLE hLockMutex = ::CreateMutex(NULL, TRUE, L"Global\\IPC_Test_LockMutex"); if (!hServerNewDataEvent || !hClientReadFinish || !hLockMutex) { wprintf(L"Can not get sync handles!\n"); return 1; } const size_t bufferSize = sizeof(WIN32_FIND_DATA); HANDLE hFile = ::CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, bufferSize, L"Global\\www.cpp-blog.com"); if (NULL == hFile) { wprintf(L"CreateFileMapping Failed!"); ::CloseHandle(hClientReadFinish); ::CloseHandle(hServerNewDataEvent); ::CloseHandle(hLockMutex); return 1; } WIN32_FIND_DATA* buff = (WIN32_FIND_DATA*)::MapViewOfFile(hFile, FILE_MAP_ALL_ACCESS, 0, 0, bufferSize); if (buff == NULL) { wprintf(L"MapViewOfFile Failed!\n"); ::CloseHandle(hFile); ::CloseHandle(hClientReadFinish); ::CloseHandle(hServerNewDataEvent); ::CloseHandle(hLockMutex); return 1; } // start another process SHELLEXECUTEINFO sii; ::ZeroMemory(&sii, sizeof(sii)); sii.cbSize = sizeof(sii); sii.lpVerb = L"open"; sii.nShow = SW_SHOW; sii.lpFile = L"C:\\Sources\\IPC_SharedMemory\\Debug\\IPC_SharedMemory_Server.exe"; // allow server to write data to the memory ::SetEvent(hClientReadFinish); ::ReleaseMutex(hLockMutex); BOOL bExecuted = ::ShellExecuteEx(&sii); if (bExecuted) { bool bRun = true; while(WAIT_OBJECT_0 == ::WaitForSingleObject(hServerNewDataEvent, INFINITE)) { DWORD dwRes = ::WaitForSingleObject(hLockMutex, INFINITE); if (dwRes != WAIT_OBJECT_0) break; if (0 == wcscmp(buff->cFileName, L"")) bRun = false; else wprintf(L"File found : %s\n", buff->cFileName); ::ReleaseMutex(hLockMutex); ::SetEvent(hClientReadFinish); if (!bRun) break; } wprintf(L"Done.\n"); } else wprintf(L"Can't start Server\n"); ::UnmapViewOfFile(buff); ::CloseHandle(hFile); ::CloseHandle(hClientReadFinish); ::CloseHandle(hServerNewDataEvent); ::CloseHandle(hLockMutex); return 0; } |
Server
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | #include "stdafx.h" #include HANDLE g_hServerNewDataEvent = NULL; HANDLE g_hClientReadFinish = NULL; HANDLE g_hLockMutex = NULL; WIN32_FIND_DATA* g_buff = NULL; typedef bool (*CallbackFnct)(const WIN32_FIND_DATA&); bool FileFoundCallback(const WIN32_FIND_DATA &data) { DWORD dwRes = ::WaitForSingleObject(g_hClientReadFinish, 5000); if (dwRes != WAIT_OBJECT_0) return false; // break operation DWORD dwRes2 = ::WaitForSingleObject(g_hLockMutex, INFINITE); if (dwRes2 != WAIT_OBJECT_0) return false; // here we own the mutex, and we can copy our data to the shared memory ::CopyMemory(g_buff, &data, sizeof(WIN32_FIND_DATA)); ::ReleaseMutex(g_hLockMutex); ::SetEvent(g_hServerNewDataEvent); return true; } bool FindFile(const wchar_t* strSearch, CallbackFnct fnct) { WIN32_FIND_DATA findData; wchar_t strFullSearch[MAX_PATH]; wcscpy(strFullSearch, strSearch); wcscat(strFullSearch, L"*.*"); bool bRun = true; HANDLE hFind = ::FindFirstFile(strFullSearch, &findData); if (INVALID_HANDLE_VALUE != hFind) { do { if (0 != wcscmp(findData.cFileName, L"..") && 0 != wcscmp(findData.cFileName, L".")) { if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { // recursion wchar_t strFullSearch[MAX_PATH]; wcscpy(strFullSearch, strSearch); wcscat(strFullSearch, findData.cFileName); wcscat(strFullSearch, L"\\"); bRun = FindFile(strFullSearch, fnct); } else bRun = fnct(findData); } } while (bRun && ::FindNextFile(hFind, &findData)); ::FindClose(hFind); } return bRun; } int _tmain(int argc, _TCHAR* argv[]) { wprintf(L"hallo from server\n"); g_hServerNewDataEvent = ::OpenEvent(EVENT_MODIFY_STATE, FALSE, L"Global\\IPC_Test_ServerNewData"); g_hClientReadFinish = ::OpenEvent(SYNCHRONIZE, FALSE, L"Global\\IPC_Test_ClientReadFinish"); g_hLockMutex = ::OpenMutex(WRITE_OWNER|SYNCHRONIZE, FALSE, L"Global\\IPC_Test_LockMutex"); if (!g_hServerNewDataEvent || !g_hClientReadFinish || !g_hLockMutex) { wprintf(L"Can not get sync handles!\n"); return 1; } const size_t bufferSize = sizeof(WIN32_FIND_DATA); HANDLE hFile = ::OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, L"Global\\www.cpp-blog.com"); if (NULL == hFile) { wprintf(L"OpenFileMapping Failed!"); ::CloseHandle(g_hServerNewDataEvent); ::CloseHandle(g_hClientReadFinish); ::CloseHandle(g_hLockMutex); return 1; } g_buff = (WIN32_FIND_DATA*)::MapViewOfFile(hFile, FILE_MAP_ALL_ACCESS, 0, 0, bufferSize); if (g_buff == NULL) { wprintf(L"MapViewOfFile Failed!\n"); ::CloseHandle(hFile); ::CloseHandle(g_hServerNewDataEvent); ::CloseHandle(g_hClientReadFinish); ::CloseHandle(g_hLockMutex); return 1; } bool bOk = FindFile(L"C:\\", FileFoundCallback); if (bOk) { // send one empty "data" to inform that we are done WIN32_FIND_DATA empty_wfd; ZeroMemory(&empty_wfd, sizeof(empty_wfd)); FileFoundCallback(empty_wfd); // wait for the last client read ::WaitForSingleObject(g_hClientReadFinish, 5000); } ::UnmapViewOfFile(g_buff); ::CloseHandle(hFile); ::CloseHandle(g_hServerNewDataEvent); ::CloseHandle(g_hClientReadFinish); ::CloseHandle(g_hLockMutex); wprintf(L"server done\n"); return 0; } |
Conclusion
Using Shared Memory for Inter Process communication is not the simplest way for IPC, but it’s definitely the fastest. Here, there is more to do with synchronization then with sharing memory itself. In my exemple we can get some “unexpected” problems in case when server side is not running as the same user. In this case correct access permissions should be used in CreateMutex and CreateEvent methods.
Pros
- it’s fast
- no special windows are needed or window message loops
- allocation and sharing memory is relative simple (few methods only)
- all methods are native from WinAPI, and it can be used from almost every language
Cons
- Synchronization is very complex
- Transfer of huge data is not recommended (no stream support)
- difficult to configure security issues
- transfer different kind (dynamic) of data is not so simple
- not so many good examples available
Next time we are going to check what COM can provide for us.
See you.

No Comments » 