Разделяемая память делается просто - вызовами CreateFileMapping
и MapViewOfFile
. Допустим у нас есть структура которая будет жить в общей памяти
struct SharedData {
...
char text[1024];
};
Тогда мы создаем глобальный именованный объект общей памяти
mapping = ::CreateFileMappingW(
nullptr, // без файла на диске
nullptr, // не наследуется процессами-потомками
PAGE_READWRITE,
0, sizeof(SharedData), // размер
L"Local\\test-shared-memory");
и берем его память
data = (SharedData*)::MapViewOfFile(mapping, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
Хендл mapping
должен жить до конца работы с общей памятью. Если его закрыть, то память на которую указывает data
останется жить, но будет отвязана от общей памяти.
Когда общая память будет не нужна - надо вызвать ::UnmapViewOfFile(data);
и ::CloseHandle(mapping);
, в любом порядке. (Или ExitProcess
, при завершении процесса всё закрывается само).
Чтобы не вызывать их вручную, можно воспользоваться умными указателями:
std::unique_ptr<void, decltype(&::CloseHandle)> mapping(
::CreateFileMappingW(...),
::CloseHandle);
std::unique_ptr<SharedData, decltype(&::UnmapViewOfFile)> data(
(SharedData*)::MapViewOfFile(mapping.get(), FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0),
::UnmapViewOfFile);
Если запустить несколько процессов, то первый процесс вызвавший CreateFileMapping
создаст глобальный объект, а остальные будут получать его хендл. Изменения data
в одном процессе будут видны в остальных, но чтобы предотвратить гонки нужна какая-то синхронизация. В идеале нужен multiple readers/single-writer mutex и condition variable, но в Windows они не умеют работать с несколькими процессами, по этому все средства синхронизации надо писать самому. Реализация правильной межпроцессной синхронизации в Windows выходит за рамки этого вопроса, по этому в примере ниже будет использован обычный polling.
#include <iostream>
#include <memory>
#include <string>
#include <thread>
#include <Windows.h>
struct SharedData {
volatile unsigned long version;
char text[1024];
};
int main() {
std::unique_ptr<void, decltype(&::CloseHandle)> mapping(
::CreateFileMappingW(nullptr, nullptr, PAGE_READWRITE, 0, sizeof(SharedData), L"Local\\test-shared-memory"),
::CloseHandle);
if (!mapping)
return printf("CreateFileMapping error: %d\n", ::GetLastError());
std::unique_ptr<SharedData, decltype(&::UnmapViewOfFile)> data(
(SharedData*)::MapViewOfFile(mapping.get(), FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0),
::UnmapViewOfFile);
if (!data)
return printf("MapViewOfFile error: %d\n", ::GetLastError());
std::unique_ptr<void, decltype(&::CloseHandle)> mutex(
::CreateMutexW(nullptr, FALSE, L"Local\\test-shared-memory-mutex"),
::CloseHandle);
if (!mutex)
return printf("CreateMutex error: %d\n", ::GetLastError());
printf("Input 'new text' to change shared text, or 'exit' to exit\n\n");
std::thread user_input_thread([&]{
std::string new_text;
while (std::getline(std::cin, new_text) && new_text != "exit") {
::WaitForSingleObject(mutex.get(), INFINITE);
std::unique_ptr<void, decltype(&::ReleaseMutex)> lock(mutex.get(), ::ReleaseMutex);
memcpy(data->text, new_text.c_str(), new_text.size() + 1);
::InterlockedIncrement(&data->version);
}
});
auto current_version = data->version - 1;
while (::WaitForSingleObject(user_input_thread.native_handle(), 200) != WAIT_OBJECT_0) {
if (current_version == data->version)
continue;
current_version = data->version;
::WaitForSingleObject(mutex.get(), INFINITE);
std::unique_ptr<void, decltype(&::ReleaseMutex)> lock(mutex.get(), ::ReleaseMutex);
printf("Shared text: %s\n", data->text);
}
user_input_thread.join();
}