RSS
 
 
formats

IPC (part 3) – Shared Memory

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.

 

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">

© cpp-blog.com 2012
credit