Разделяемая память

Рейтинг: 1Ответов: 2Опубликовано: 03.05.2015

Подскажите, как реализовать разделяемую память через CreateFileMapping и MapViewOfFile. Нужно, чтобы было две программы, т.е. 2 запущенные консоли и между ними было взаимодействие. Т.е. клиент и сервер. Например, один печатает у себя, и у другого то же самое должно напечататься и наоборот.

Ответы

▲ 1Принят

Для таких целей разделяемая память не очень подходит, т.к. не имеет механизма уведомлений. Это именно что разделяемый кусок памяти, в который можно писать с разных процессов. Для целей указанных в вовпросе лучше подходят пайпа(pipes). На MSDN есть пример построения клиент-серверного приложения, т.е. как раз то, что Вам нужно.

А если всё таки очень хочется делать через разделяемую память, то на том же MSDN есть замечательный пример, как пользоваться функциями из вопроса. Вам же остается с одной стороны писать в разделяемый кусок, а с другой - проверять, на наличие новых данных. Или ввести некий синхронизирующий примитив, типа именованного семафора, чтобы не заниматься постоянной проверкой, которая отъедает ресурсы ЦП впустую.

▲ 2

Разделяемая память делается просто - вызовами 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();
}