From 89f1c684d73ce275671f2cf5c312dd75af1f18d8 Mon Sep 17 00:00:00 2001 From: Egor Tensin Date: Wed, 21 Oct 2020 03:39:05 +0300 Subject: add Shared{Memory,Object} classes --- include/winapi/shmem.hpp | 125 ++++++++++++++++++++++++++++++++++++++++++++++ src/shmem.cpp | 88 ++++++++++++++++++++++++++++++++ test/unit_tests/shmem.cpp | 72 ++++++++++++++++++++++++++ 3 files changed, 285 insertions(+) create mode 100644 include/winapi/shmem.hpp create mode 100644 src/shmem.cpp create mode 100644 test/unit_tests/shmem.cpp diff --git a/include/winapi/shmem.hpp b/include/winapi/shmem.hpp new file mode 100644 index 0000000..0fb2b76 --- /dev/null +++ b/include/winapi/shmem.hpp @@ -0,0 +1,125 @@ +// Copyright (c) 2020 Egor Tensin +// This file is part of the "winapi-common" project. +// For details, see https://github.com/egor-tensin/winapi-common. +// Distributed under the MIT License. + +#pragma once + +#include "handle.hpp" + +#include + +#include +#include +#include +#include +#include + +namespace winapi { + +class SharedMemory { +public: + static SharedMemory create(const std::string& name, std::size_t nb); + static SharedMemory open(const std::string& name); + + // VS 2013 won't generate these automatically: + SharedMemory(SharedMemory&&) BOOST_NOEXCEPT_OR_NOTHROW; + SharedMemory& operator=(SharedMemory) BOOST_NOEXCEPT_OR_NOTHROW; + void swap(SharedMemory&) BOOST_NOEXCEPT_OR_NOTHROW; + SharedMemory(const SharedMemory&) = delete; + + void* get() const { return m_addr.get(); } + void* ptr() const { return get(); } + +private: + struct Unmap { + void operator()(void*) const; + }; + + SharedMemory() = default; + + SharedMemory(Handle&& handle, void* addr) : m_handle{std::move(handle)}, m_addr{addr} {} + + Handle m_handle; + std::unique_ptr m_addr; +}; + +inline void swap(SharedMemory& a, SharedMemory& b) BOOST_NOEXCEPT_OR_NOTHROW { + a.swap(b); +} + +template +class SharedObject { +public: + typedef typename std::aligned_storage::type AlignedType; + + template + static SharedObject create(const std::string& name, Args&&... args) { + SharedObject obj{SharedMemory::create(name, sizeof(AlignedType))}; + new (obj.ptr()) T(std::forward(args)...); + obj.m_destruct = true; + return obj; + } + + static SharedObject open(const std::string& name) { + SharedObject obj{SharedMemory::open(name)}; + return obj; + } + + SharedObject(SharedObject&& other) BOOST_NOEXCEPT_OR_NOTHROW + : m_shmem{std::move(other.m_shmem)}, + m_destruct{other.m_destruct} {} + + SharedObject& operator=(SharedObject other) BOOST_NOEXCEPT_OR_NOTHROW { + swap(other); + return *this; + } + + ~SharedObject() { + if (m_destruct && ptr()) { + ptr()->~T(); + } + } + + void swap(SharedObject& other) BOOST_NOEXCEPT_OR_NOTHROW { + using std::swap; + swap(m_shmem, other.m_shmem); + swap(m_destruct, other.m_destruct); + } + + T* ptr() const { return reinterpret_cast(m_shmem.ptr()); } + T& get() const { return *ptr(); } + + T* operator->() const { return ptr(); } + T& operator*() const { return get(); } + +private: + explicit SharedObject(SharedMemory&& shmem) : m_shmem{std::move(shmem)} {} + + SharedMemory m_shmem; + // Destruct only once, no matter the number of mappings. + bool m_destruct = false; + + SharedObject(const SharedObject&) = delete; +}; + +template +inline void swap(SharedObject& a, SharedObject& b) BOOST_NOEXCEPT_OR_NOTHROW { + a.swap(b); +} + +} // namespace winapi + +namespace std { + +template <> +inline void swap(winapi::SharedMemory& a, winapi::SharedMemory& b) BOOST_NOEXCEPT_OR_NOTHROW { + a.swap(b); +} + +template +inline void swap(winapi::SharedObject& a, winapi::SharedObject& b) BOOST_NOEXCEPT_OR_NOTHROW { + a.swap(b); +} + +} // namespace std diff --git a/src/shmem.cpp b/src/shmem.cpp new file mode 100644 index 0000000..552351d --- /dev/null +++ b/src/shmem.cpp @@ -0,0 +1,88 @@ +// Copyright (c) 2020 Egor Tensin +// This file is part of the "winapi-common" project. +// For details, see https://github.com/egor-tensin/winapi-common. +// Distributed under the MIT License. + +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include + +namespace winapi { +namespace { + +void* do_map(const Handle& mapping, std::size_t nb = 0) { + const auto addr = ::MapViewOfFile(static_cast(mapping), FILE_MAP_ALL_ACCESS, 0, 0, nb); + + if (addr == NULL) { + throw error::windows(GetLastError(), "MapViewOfFile"); + } + + return addr; +} + +} // namespace + +void SharedMemory::Unmap::operator()(void* ptr) const { + const auto ret = ::UnmapViewOfFile(ptr); + assert(ret); + WINAPI_UNUSED_PARAMETER(ret); +}; + +SharedMemory SharedMemory::create(const std::string& name, std::size_t nb) { + const auto nb64 = static_cast(nb); + static_assert(sizeof(nb64) == 2 * sizeof(DWORD), "sizeof(DWORD) != 32"); + + const auto nb_low = static_cast(nb64); + const auto nb_high = static_cast(nb64 >> 32); + + const auto h_mapping = ::CreateFileMappingW( + INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, nb_high, nb_low, widen(name).c_str()); + + if (h_mapping == NULL) { + throw error::windows(GetLastError(), "CreateFileMappingW"); + } + + Handle mapping{h_mapping}; + const auto addr = do_map(mapping); + return {std::move(mapping), addr}; +} + +SharedMemory SharedMemory::open(const std::string& name) { + const auto h_mapping = ::OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, widen(name).c_str()); + + if (h_mapping == NULL) { + throw error::windows(GetLastError(), "OpenFileMappingW"); + } + + Handle mapping{h_mapping}; + const auto addr = do_map(mapping); + return {std::move(mapping), addr}; +} + +SharedMemory::SharedMemory(SharedMemory&& other) BOOST_NOEXCEPT_OR_NOTHROW { + swap(other); +} + +SharedMemory& SharedMemory::operator=(SharedMemory other) BOOST_NOEXCEPT_OR_NOTHROW { + swap(other); + return *this; +} + +void SharedMemory::swap(SharedMemory& other) BOOST_NOEXCEPT_OR_NOTHROW { + using std::swap; + swap(m_handle, other.m_handle); + swap(m_addr, other.m_addr); +} + +} // namespace winapi diff --git a/test/unit_tests/shmem.cpp b/test/unit_tests/shmem.cpp new file mode 100644 index 0000000..26f1389 --- /dev/null +++ b/test/unit_tests/shmem.cpp @@ -0,0 +1,72 @@ +// Copyright (c) 2020 Egor Tensin +// This file is part of the "winapi-common" project. +// For details, see https://github.com/egor-tensin/winapi-common. +// Distributed under the MIT License. + +#include + +#include +#include + +#include +#include +#include + +using namespace winapi; + +namespace { + +BOOST_CONSTEXPR_OR_CONST auto shmem_name = "test-data-struct"; + +BOOST_CONSTEXPR_OR_CONST int main_data = -1; +BOOST_CONSTEXPR_OR_CONST int setter1_data = 69; +BOOST_CONSTEXPR_OR_CONST int setter2_data = 420; + +struct DataStruct { + std::mutex mtx; + std::condition_variable cv; + int data; +}; + +void setter1_main() { + const auto data_struct = SharedObject::open(shmem_name); + std::unique_lock lck{data_struct->mtx}; + BOOST_TEST_MESSAGE(data_struct.ptr()); + + data_struct->cv.wait(lck, [&]() { return data_struct->data == main_data; }); + data_struct->data = setter1_data; + data_struct->cv.notify_all(); +} + +void setter2_main() { + auto data_struct = SharedObject::open(shmem_name); + std::unique_lock lck{data_struct->mtx}; + BOOST_TEST_MESSAGE(data_struct.ptr()); + + data_struct->cv.wait(lck, [&]() { return data_struct->data == setter1_data; }); + data_struct->data = setter2_data; + data_struct->cv.notify_all(); +} + +} // namespace + +BOOST_AUTO_TEST_SUITE(shmem_tests) + +BOOST_AUTO_TEST_CASE(basic) { + auto data_struct = SharedObject::create(shmem_name); + + std::thread setter1{&setter1_main}; + std::thread setter2{&setter2_main}; + { + std::lock_guard lck{data_struct->mtx}; + data_struct->data = main_data; + } + data_struct->cv.notify_all(); + + setter1.join(); + setter2.join(); + + BOOST_TEST(data_struct->data == setter2_data); +} + +BOOST_AUTO_TEST_SUITE_END() -- cgit v1.2.3