Skip to content

Instantly share code, notes, and snippets.

@yuyoyuppe
Last active July 12, 2020 10:35
Show Gist options
  • Save yuyoyuppe/3748b8504f16388245047c3ce3ad39a3 to your computer and use it in GitHub Desktop.
Save yuyoyuppe/3748b8504f16388245047c3ce3ad39a3 to your computer and use it in GitHub Desktop.
A simple wrapper class for working with a spin-lock protected shared memory on Windows.
#include "SerializedSharedMemory.h"
class HandleTransferChannel
{
std::optional<HANDLE> _handle;
const DWORD _destination_pid;
public:
HandleTransferChannel(const DWORD destination_pid) noexcept
:_destination_pid{destination_pid}
{
}
std::optional<wil::unique_handle> try_extract() noexcept
{
std::optional<HANDLE> extracted;
_handle.swap(extracted);
if(!extracted.has_value())
{
return std::nullopt;
}
return wil::unique_handle {*extracted};
}
bool try_send(const HANDLE handle) noexcept
{
HANDLE destination_process_handle = OpenProcess(PROCESS_DUP_HANDLE, false, _destination_pid);
if(!destination_process_handle)
{
return false;
}
HANDLE target_handle = INVALID_HANDLE_VALUE;
if(!DuplicateHandle(
GetCurrentProcess(),
handle,
&destination_process_handle,
&target_handle,
0,
true,
DUPLICATE_SAME_ACCESS))
{
return false;
}
_handle.emplace(target_handle);
return true;
}
};
#include "SerializedSharedMemory.h"
inline char * SerializedSharedMemory::lock_flag_addr() noexcept
{
return reinterpret_cast<char *>(_memory.data() + _memory.size());
}
inline void SerializedSharedMemory::lock() noexcept
{
if(_read_only)
{
return;
}
while(LOCKED == _InterlockedCompareExchange8(lock_flag_addr(), LOCKED, !LOCKED))
{
while(*lock_flag_addr() == LOCKED)
{
_mm_pause();
}
}
}
inline void SerializedSharedMemory::unlock() noexcept
{
if(_read_only)
{
return;
}
_InterlockedExchange8(lock_flag_addr(), !LOCKED);
}
SerializedSharedMemory::SerializedSharedMemory(std::array<wil::unique_handle, 2> handles,
memory_t memory,
const bool readonly) noexcept
: _handles{std::move(handles)}, _memory{std::move(memory)}, _read_only(readonly)
{
}
SerializedSharedMemory::~SerializedSharedMemory() noexcept
{
if(!_memory.empty())
{
UnmapViewOfFile(_memory.data());
}
}
SerializedSharedMemory::SerializedSharedMemory(SerializedSharedMemory && rhs) noexcept { *this = std::move(rhs); }
SerializedSharedMemory & SerializedSharedMemory::operator=(SerializedSharedMemory && rhs) noexcept
{
_handles = {};
_handles.swap(rhs._handles);
_memory = std::move(rhs._memory);
rhs._memory = {};
rhs._read_only = true;
return *this;
}
std::optional<SerializedSharedMemory> SerializedSharedMemory::create(const std::wstring_view object_name,
const size_t size,
const bool read_only,
SECURITY_ATTRIBUTES * maybe_attributes) noexcept
{
SECURITY_DESCRIPTOR sd;
SECURITY_ATTRIBUTES sa = {sizeof SECURITY_ATTRIBUTES};
if(!maybe_attributes)
{
sa.lpSecurityDescriptor = &sd;
sa.bInheritHandle = false;
if(!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION) ||
!SetSecurityDescriptorDacl(&sd, true, nullptr, false))
{
return std::nullopt;
}
}
// We need an extra byte for locking if it's not readonly
const ULARGE_INTEGER UISize{.QuadPart = size + !read_only};
wil::unique_handle hMapFile{CreateFileMappingW(INVALID_HANDLE_VALUE,
maybe_attributes ? maybe_attributes : &sa,
read_only ? PAGE_READONLY : PAGE_READWRITE,
UISize.HighPart,
UISize.LowPart,
object_name.data())};
if(!hMapFile)
{
return std::nullopt;
}
auto shmem = static_cast<uint8_t *>(
MapViewOfFile(hMapFile.get(), read_only ? FILE_MAP_READ : FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, UISize.QuadPart));
if(!shmem)
{
return std::nullopt;
}
std::array<wil::unique_handle, 2> handles = {std::move(hMapFile), {}};
return SerializedSharedMemory{std::move(handles), memory_t{shmem, size}, read_only};
}
std::optional<SerializedSharedMemory> SerializedSharedMemory::open(const std::wstring_view object_name,
const size_t size,
const bool read_only) noexcept
{
wil::unique_handle hMapFile{OpenFileMappingW(FILE_MAP_READ | FILE_MAP_WRITE, FALSE, object_name.data())};
if(!hMapFile)
{
return std::nullopt;
}
auto shmem = static_cast<uint8_t *>(
MapViewOfFile(hMapFile.get(), read_only ? FILE_MAP_READ : FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, size + !read_only));
if(!shmem)
{
return std::nullopt;
}
std::array<wil::unique_handle, 2> handles = {std::move(hMapFile), {}};
return SerializedSharedMemory{std::move(handles), memory_t{shmem, size}, read_only};
}
std::optional<SerializedSharedMemory> SerializedSharedMemory::create_readonly(
const std::wstring_view object_name,
const std::wstring_view file_path,
SECURITY_ATTRIBUTES * maybe_attributes) noexcept
{
SECURITY_DESCRIPTOR sd;
SECURITY_ATTRIBUTES sa = {sizeof SECURITY_ATTRIBUTES};
if(!maybe_attributes)
{
sa.lpSecurityDescriptor = &sd;
sa.bInheritHandle = false;
if(!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION) ||
!SetSecurityDescriptorDacl(&sd, true, nullptr, false))
{
return std::nullopt;
}
}
wil::unique_handle hFile{CreateFileW(file_path.data(),
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
maybe_attributes ? maybe_attributes : &sa,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
nullptr)};
if(!hFile)
{
return std::nullopt;
}
LARGE_INTEGER fileSize;
if(!GetFileSizeEx(hFile.get(), &fileSize))
{
return std::nullopt;
}
wil::unique_handle hMapFile{CreateFileMappingW(hFile.get(),
maybe_attributes ? maybe_attributes : &sa,
PAGE_READONLY,
fileSize.HighPart,
fileSize.LowPart,
object_name.data())};
if(!hMapFile)
{
return std::nullopt;
}
auto shmem = static_cast<uint8_t *>(MapViewOfFile(nullptr, FILE_MAP_READ, 0, 0, fileSize.QuadPart));
if(shmem)
{
return std::nullopt;
}
std::array<wil::unique_handle, 2> handles = {std::move(hMapFile), std::move(hFile)};
return SerializedSharedMemory{std::move(handles), memory_t{shmem, static_cast<size_t>(fileSize.QuadPart)}, true};
}
void SerializedSharedMemory::access(std::function<void(memory_t)> access_routine) noexcept
{
lock();
access_routine(_memory);
unlock();
}
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <string>
#include <optional>
#include <wil/resource.h>
#include <span>
#include <functional>
#include <array>
// Wrapper class allowing sharing readonly/writable memory with a serialized access via atomic locking.
// Note that it doesn't protect against a 3rd party concurrently modifying physical file contents.
class SerializedSharedMemory
{
public:
using memory_t = std::span<uint8_t>;
static std::optional<SerializedSharedMemory> create(const std::wstring_view object_name,
const size_t size,
const bool read_only,
SECURITY_ATTRIBUTES * maybe_attributes = nullptr) noexcept;
static std::optional<SerializedSharedMemory> create_readonly(
const std::wstring_view object_name,
const std::wstring_view file_path,
SECURITY_ATTRIBUTES * maybe_attributes = nullptr) noexcept;
static std::optional<SerializedSharedMemory> open(const std::wstring_view object_name,
const size_t size,
const bool read_only) noexcept;
void access(std::function<void(memory_t)> access_routine) noexcept;
inline size_t size() const noexcept { return _memory.size_bytes(); }
~SerializedSharedMemory() noexcept;
SerializedSharedMemory(SerializedSharedMemory &&) noexcept;
SerializedSharedMemory & operator=(SerializedSharedMemory &&) noexcept;
private:
std::array<wil::unique_handle, 2> _handles;
memory_t _memory;
bool _read_only = true;
constexpr static inline int64_t LOCKED = 1;
char * lock_flag_addr() noexcept;
void lock() noexcept;
void unlock() noexcept;
SerializedSharedMemory(std::array<wil::unique_handle, 2> handles, memory_t memory, const bool readonly) noexcept;
};
#include "SerializedSharedMemory.h"
#include <iostream>
#include <atomic>
std::atomic_bool ShouldExit = false;
BOOL WINAPI consoleHandler(DWORD signal)
{
if(signal == CTRL_C_EVENT)
ShouldExit = true;
return TRUE;
}
struct Settings
{
std::optional<size_t> _pending_overlay_image_size;
};
int main(int argc, char **)
{
const bool iAmServer = argc == 1;
SetConsoleCtrlHandler(consoleHandler, TRUE);
const wchar_t shMemSettingsName[] = L"SettingsSharedMemory";
const wchar_t shMemOverlayImageName[] = L"OverlayImageMemory";
if(iAmServer)
{
std::cout << "server mode!\n";
auto settings = SerializedSharedMemory::create(shMemSettingsName, sizeof(Settings), false);
if(!settings)
{
std::cout << "couldn't create shared memory!\n";
return 1;
}
settings->access([](auto memory) { new(memory.data()) Settings{}; });
std::optional<SerializedSharedMemory> image;
do
{
settings->access([&](auto memory) mutable {
auto settings = reinterpret_cast<Settings *>(memory.data());
if(!settings->_pending_overlay_image_size.has_value())
{
return;
}
image.reset();
image = SerializedSharedMemory::open(shMemOverlayImageName, *settings->_pending_overlay_image_size, true);
settings->_pending_overlay_image_size.reset();
if(!image.has_value())
{
std::cout << "couldn't open pending image: " << std::system_category().message(GetLastError()) << '\n';
}
});
if(!image.has_value())
{
Sleep(50);
continue;
}
std::cout << "reading file contents:\n";
image->access([](auto memory) {
for(const auto byte : memory)
{
std::cout << byte;
}
});
std::cout << std::endl;
image.reset();
Sleep(1000);
} while(!ShouldExit);
}
else
{
std::cout << "client mode!\n";
auto settings = SerializedSharedMemory::open(shMemSettingsName, sizeof(Settings), false);
if(!settings)
{
std::cout << "couldn't open shared memory!\n";
return 1;
}
std::optional<SerializedSharedMemory> image;
do
{
std::wstring filePath;
std::cout << "\nfilename: ";
std::wcin >> filePath;
image.reset();
image = SerializedSharedMemory::create_readonly(shMemOverlayImageName, filePath);
if(!image)
{
std::cout << "couldn't open the image filemapping!\n";
continue;
}
settings->access([&](auto memory) {
auto transfer = reinterpret_cast<Settings *>(memory.data());
transfer->_pending_overlay_image_size = image->size();
std::cout << "file size has been posted!\n";
});
} while(!ShouldExit);
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment