From d55d9f549fdd91f6c0171484c43b01a86f8c4721 Mon Sep 17 00:00:00 2001 From: mutouyun Date: Sun, 30 Oct 2022 16:09:02 +0800 Subject: [PATCH] add: [ipc] shared_memory object --- include/libipc/shm.h | 33 +++++++++++++- src/libipc/platform/posix/shm_impl.h | 10 ++--- src/libipc/platform/win/get_sa.h | 4 +- src/libipc/platform/win/mmap_impl.h | 49 ++++++++++++++------- src/libipc/shm.cpp | 65 +++++++++++++++++++++++++++- test/test_ipc_shm.cpp | 49 ++++++++++++++++++++- 6 files changed, 185 insertions(+), 25 deletions(-) diff --git a/include/libipc/shm.h b/include/libipc/shm.h index c7e697d..666ca18 100644 --- a/include/libipc/shm.h +++ b/include/libipc/shm.h @@ -37,6 +37,37 @@ LIBIMP_EXPORT std::size_t shm_size(shm_t) noexcept; /// @brief Gets the name of the shared memory file based on the shared memory handle. /// @return empty string on failure. -LIBIMP_EXPORT std::string shm_file(shm_t) noexcept; +LIBIMP_EXPORT std::string shm_name(shm_t) noexcept; + +/** + * @brief The shared memory object. + */ +class LIBIMP_EXPORT shared_memory { + shm_t shm_; + +public: + explicit shared_memory() noexcept; + ~shared_memory() noexcept; + + shared_memory(shared_memory &&other) noexcept; + shared_memory &operator=(shared_memory &&rhs) & noexcept; + void swap(shared_memory &other) noexcept; + + explicit shared_memory(std::string name, + std::size_t size = 0, + mode::type = mode::create | mode::open) noexcept; + + bool open(std::string name, std::size_t size, mode::type) noexcept; + void close() noexcept; + bool valid() const noexcept; + + void *get() const noexcept; + void *operator*() const noexcept; + template + T *as() { return static_cast(get()); } + + std::size_t size() const noexcept; + std::string name() const noexcept; +}; LIBIPC_NAMESPACE_END_ diff --git a/src/libipc/platform/posix/shm_impl.h b/src/libipc/platform/posix/shm_impl.h index d7bb2ea..13a320d 100644 --- a/src/libipc/platform/posix/shm_impl.h +++ b/src/libipc/platform/posix/shm_impl.h @@ -67,7 +67,7 @@ result shm_open(std::string name, std::size_t size, mode::type type) noex S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); if (fd == posix::failed) { - log.error("shm_open fails. error = {}", sys::error_msg(sys::error_code())); + log.error("shm_open fails. error = {}", sys::error()); return {}; } @@ -75,14 +75,14 @@ result shm_open(std::string name, std::size_t size, mode::type type) noex /// @brief Try to get the size of this fd struct stat st; if (::fstat(fd, &st) == posix::failed) { - log.error("fstat fails. error = {}", sys::error_msg(sys::error_code())); + log.error("fstat fails. error = {}", sys::error()); ::shm_unlink(name.c_str()); return {}; } size = static_cast(st.st_size); /// @brief Truncate this fd to a specified length } else if (::ftruncate(fd, size) != 0) { - log.error("ftruncate fails. error = {}", sys::error_msg(sys::error_code())); + log.error("ftruncate fails. error = {}", sys::error()); ::shm_unlink(name.c_str()); return {}; } @@ -90,7 +90,7 @@ result shm_open(std::string name, std::size_t size, mode::type type) noex /// @brief Creates a new mapping in the virtual address space of the calling process. void *mem = ::mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (mem == MAP_FAILED) { - log.error("mmap fails. error = {}", sys::error_msg(sys::error_code())); + log.error("mmap fails. error = {}", sys::error()); ::shm_unlink(name.c_str()); return {}; } @@ -114,7 +114,7 @@ result_code shm_close(shm_t h) noexcept { } if (::munmap(shm->memp, shm->f_sz) == posix::failed) { auto ec = sys::error_code(); - log.error("munmap fails. error = {}", sys::error_msg(ec)); + log.error("munmap fails. error = {}", sys::error(ec)); return ec; } /// @brief no unlink the file. diff --git a/src/libipc/platform/win/get_sa.h b/src/libipc/platform/win/get_sa.h index faea281..78bae67 100644 --- a/src/libipc/platform/win/get_sa.h +++ b/src/libipc/platform/win/get_sa.h @@ -30,11 +30,11 @@ inline LPSECURITY_ATTRIBUTES get_sa() { using namespace ::LIBIMP_; log::gripper log {"get_sa"}; if (!::InitializeSecurityDescriptor(&sd_, SECURITY_DESCRIPTOR_REVISION)) { - log.error("InitializeSecurityDescriptor fails. error = {}", sys::error_msg(sys::error_code())); + log.error("InitializeSecurityDescriptor fails. error = {}", sys::error()); return; } if (!::SetSecurityDescriptorDacl(&sd_, TRUE, NULL, FALSE)) { - log.error("SetSecurityDescriptorDacl fails. error = {}", sys::error_msg(sys::error_code())); + log.error("SetSecurityDescriptorDacl fails. error = {}", sys::error()); return; } sa_.nLength = sizeof(SECURITY_ATTRIBUTES); diff --git a/src/libipc/platform/win/mmap_impl.h b/src/libipc/platform/win/mmap_impl.h index 494532c..3b19952 100644 --- a/src/libipc/platform/win/mmap_impl.h +++ b/src/libipc/platform/win/mmap_impl.h @@ -40,18 +40,21 @@ void mmap_close(HANDLE h) { return; } if (!::CloseHandle(h)) { - log.error("CloseHandle fails. error = {}", sys::error_msg(sys::error_code())); + log.error("CloseHandle fails. error = {}", sys::error()); } } /** * @brief Creates or opens a file mapping object for a specified file. + * * @see https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-openfilemappinga * https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createfilemappinga + * * @param file Specifies the name of the file mapping object * @param size Specifies the size required to create a file mapping object. * This size is ignored when opening an existing file mapping object * @param type Combinable open modes, create | open + * * @return File mapping object HANDLE, NULL on error */ HANDLE mmap_open(std::string const &file, std::size_t size, mode::type type) noexcept { @@ -65,26 +68,42 @@ HANDLE mmap_open(std::string const &file, std::size_t size, mode::type type) noe log.error("file name is empty. (TCHAR conversion failed)"); return NULL; } + /// @brief Opens a named file mapping object. - if (type == mode::open) { + auto try_open = [&]() -> HANDLE { HANDLE h = ::OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, t_name.c_str()); if (h == NULL) { - log.error("OpenFileMapping fails. error = {}", sys::error_msg(sys::error_code())); + log.error("OpenFileMapping fails. error = {}", sys::error()); } return h; + }; + + /// @brief Creates or opens a named or unnamed file mapping object for a specified file. + auto try_create = [&]() -> HANDLE { + HANDLE h = ::CreateFileMapping(INVALID_HANDLE_VALUE, detail::get_sa(), PAGE_READWRITE | SEC_COMMIT, + /// @remark dwMaximumSizeHigh always 0 here. + 0, static_cast(size), t_name.c_str()); + if (h == NULL) { + log.error("CreateFileMapping fails. error = {}", sys::error()); + return NULL; + } + return h; + }; + + if (type == mode::open) { + return try_open(); + } else if ((type == (mode::create | mode::open)) && (size == 0)) { + /// @remark CreateFileMapping may returns ERROR_INVALID_PARAMETER when dwMaximumSizeLow is zero. + /// @see CreateFileMapping (Windows CE 5.0) + /// https://learn.microsoft.com/en-us/previous-versions/windows/embedded/aa517331(v=msdn.10) + return try_open(); } else if (!(type & mode::create)) { log.error("mode type is invalid. type = {}", type); return NULL; } - /// @brief Creates or opens a named or unnamed file mapping object for a specified file. - HANDLE h = ::CreateFileMapping(INVALID_HANDLE_VALUE, detail::get_sa(), PAGE_READWRITE | SEC_COMMIT, - 0, static_cast(size), t_name.c_str()); - if (h == NULL) { - log.error("CreateFileMapping fails. error = {}", sys::error_msg(sys::error_code())); - return NULL; - } - // If the object exists before the function call, the function returns a handle to the existing object - // (with its current size, not the specified size), and GetLastError returns ERROR_ALREADY_EXISTS. + HANDLE h = try_create(); + /// @remark If the object exists before the function call, the function returns a handle to the existing object + /// (with its current size, not the specified size), and GetLastError returns ERROR_ALREADY_EXISTS. if ((type == mode::create) && (::GetLastError() == ERROR_ALREADY_EXISTS)) { mmap_close(h); log.info("the file being created already exists. file = {}, type = {}", file, type); @@ -105,7 +124,7 @@ LPVOID mmap_memof(HANDLE h) { } LPVOID mem = ::MapViewOfFile(h, FILE_MAP_ALL_ACCESS, 0, 0, 0); if (h == NULL) { - log.error("MapViewOfFile fails. error = {}", sys::error_msg(sys::error_code())); + log.error("MapViewOfFile fails. error = {}", sys::error()); return NULL; } return mem; @@ -123,7 +142,7 @@ SIZE_T mmap_sizeof(LPCVOID mem) { } MEMORY_BASIC_INFORMATION mem_info {}; if (::VirtualQuery(mem, &mem_info, sizeof(mem_info)) == 0) { - log.error("VirtualQuery fails. error = {}", sys::error_msg(sys::error_code())); + log.error("VirtualQuery fails. error = {}", sys::error()); return 0; } return mem_info.RegionSize; @@ -144,7 +163,7 @@ void mmap_release(HANDLE h, LPCVOID mem) { return; } if (!::UnmapViewOfFile(mem)) { - log.warning("UnmapViewOfFile fails. error = {}", sys::error_msg(sys::error_code())); + log.warning("UnmapViewOfFile fails. error = {}", sys::error()); } mmap_close(h); } diff --git a/src/libipc/shm.cpp b/src/libipc/shm.cpp index da15fb5..5804193 100644 --- a/src/libipc/shm.cpp +++ b/src/libipc/shm.cpp @@ -1,4 +1,6 @@ +#include // std::exchange, std::swap (since C++11) + #include "libimp/log.h" #include "libimp/detect_plat.h" @@ -10,6 +12,8 @@ LIBIPC_NAMESPACE_BEG_ +/// @brief C style shared memory access interface implementation. + void *shm_get(shm_t h) noexcept { LIBIMP_LOG_(); if (h == nullptr) { @@ -30,7 +34,7 @@ std::size_t shm_size(shm_t h) noexcept { return shm->f_sz; } -std::string shm_file(shm_t h) noexcept { +std::string shm_name(shm_t h) noexcept { LIBIMP_LOG_(); if (h == nullptr) { log.error("shm handle is null."); @@ -40,4 +44,63 @@ std::string shm_file(shm_t h) noexcept { return shm->file; } +/// @brief The shared memory object. + +shared_memory::shared_memory() noexcept + : shm_(nullptr) {} + +shared_memory::~shared_memory() noexcept { + this->close(); +} + +shared_memory::shared_memory(shared_memory &&other) noexcept + : shm_(std::exchange(other.shm_, nullptr)) {} + +shared_memory &shared_memory::operator=(shared_memory &&rhs) & noexcept { + this->shm_ = std::exchange(rhs.shm_, nullptr); + return *this; +} + +void shared_memory::swap(shared_memory &other) noexcept { + std::swap(this->shm_, other.shm_); +} + +shared_memory::shared_memory(std::string name, std::size_t size, mode::type type) noexcept + : shared_memory() { + open(std::move(name), size, type); +} + +bool shared_memory::open(std::string name, std::size_t size, mode::type type) noexcept { + this->close(); + auto ret = shm_open(std::move(name), size, type); + if (!ret) return false; + shm_ = *ret; + return true; +} + +void shared_memory::close() noexcept { + if (!valid()) return; + shm_close(std::exchange(shm_, nullptr)); +} + +bool shared_memory::valid() const noexcept { + return this->shm_ != nullptr; +} + +void *shared_memory::get() const noexcept { + return shm_get(shm_); +} + +void *shared_memory::operator*() const noexcept { + return this->get(); +} + +std::size_t shared_memory::size() const noexcept { + return shm_size(shm_); +} + +std::string shared_memory::name() const noexcept { + return shm_name(shm_); +} + LIBIPC_NAMESPACE_END_ diff --git a/test/test_ipc_shm.cpp b/test/test_ipc_shm.cpp index 830b10c..99f7aac 100644 --- a/test/test_ipc_shm.cpp +++ b/test/test_ipc_shm.cpp @@ -15,16 +15,63 @@ TEST(shm, create_close) { EXPECT_NE(pt1, nullptr); *(int *)pt1 = 0; - auto shm2 = ipc::shm_open(ipc::shm_file(*shm1), 0, ipc::mode::open); + auto shm2 = ipc::shm_open(ipc::shm_name(*shm1), 0, ipc::mode::create | ipc::mode::open); EXPECT_TRUE(shm2); + auto shm3 = ipc::shm_open(ipc::shm_name(*shm1), 128, ipc::mode::open); + EXPECT_TRUE(shm3); + auto shm4 = ipc::shm_open(ipc::shm_name(*shm1), 256, ipc::mode::create | ipc::mode::open); + EXPECT_TRUE(shm4); EXPECT_EQ(ipc::shm_size(*shm1), ipc::shm_size(*shm2)); + EXPECT_EQ(ipc::shm_size(*shm1), ipc::shm_size(*shm3)); + EXPECT_EQ(ipc::shm_size(*shm1), ipc::shm_size(*shm4)); auto pt2 = ipc::shm_get(*shm1); EXPECT_NE(pt2, nullptr); EXPECT_EQ(*(int *)pt2, 0); *(int *)pt1 = 1234; EXPECT_EQ(*(int *)pt2, 1234); + EXPECT_TRUE(ipc::shm_close(*shm4)); + EXPECT_TRUE(ipc::shm_close(*shm3)); EXPECT_TRUE(ipc::shm_close(*shm2)); EXPECT_TRUE(ipc::shm_close(*shm1)); EXPECT_FALSE(ipc::shm_close(nullptr)); +} + +TEST(shm, shared_memory) { + ipc::shared_memory shm; + EXPECT_FALSE(shm.valid()); + EXPECT_EQ(shm.size(), 0); + EXPECT_EQ(shm.name(), ""); + EXPECT_EQ(shm.get() , nullptr); + EXPECT_EQ(*shm , nullptr); + EXPECT_EQ(shm.as(), nullptr); + shm.close(); + + EXPECT_TRUE(shm.open("hello-ipc-shared-memory", 2048, ipc::mode::create | ipc::mode::open)); + EXPECT_TRUE(shm.valid()); + EXPECT_TRUE(shm.size() >= 2048); + EXPECT_EQ(shm.name(), "hello-ipc-shared-memory"); + EXPECT_NE(shm.get() , nullptr); + EXPECT_NE(*shm , nullptr); + EXPECT_NE(shm.as(), nullptr); + *shm.as() = 4321; + + auto shm_r = ipc::shm_open(shm.name()); + ASSERT_TRUE(shm_r); + EXPECT_EQ(*static_cast(ipc::shm_get(*shm_r)), 4321); + + shm = ipc::shared_memory("hello-ipc-shared-memory-2", 512); + EXPECT_TRUE(shm.valid()); + EXPECT_TRUE(shm.size() >= 512); + EXPECT_EQ(shm.name(), "hello-ipc-shared-memory-2"); + EXPECT_NE(shm.get() , nullptr); + EXPECT_NE(*shm , nullptr); + EXPECT_NE(shm.as(), nullptr); + + *static_cast(ipc::shm_get(*shm_r)) = 1234; + *shm.as() = 4444; + EXPECT_EQ(*static_cast(ipc::shm_get(*shm_r)), 1234); + EXPECT_EQ(*shm.as(), 4444); + + EXPECT_TRUE(ipc::shm_close(*shm_r)); } \ No newline at end of file