FileMapping.h
/*****************************************************************
*
* @brief: Wrapper for file mapping on Windows, duplex process communication support
* @auth: 1058778041@qq.com
* @date: 2025/04/18
* @update:
*
******************************************************************/
#pragma once
#include <string>
#include <functional>
#include "Utils/ThreadObject.h"
using FuncDataCallback = std::function<void(const std::string& strMsg)>;
namespace Utils {
class FileMapping
{
const std::string kGlobalTag = "Global\\";
const std::string kDuplexTag = "Duplex";
const std::string kSemaphoreTag = "Sema";
const std::string kEventTag = "Evt";
public:
FileMapping();
/*
*
* @brief: Create mapping in server
* @auth: 1058778041@qq.com
* @create: 2025/04/18
* @para[in]: strMapping, name for file mapping
* @para[in]: bGlobal, global tag; set true if system process for server
* @para[in]: bDuplex, duplex communication; set true if client sends msg to server
* @return: bool, true: success, or failed
*
*/
bool CreateMapping(const std::string& strMapping, bool bGlobal = false, bool bDuplex = false);
/*
*
* @brief: Connect mapping in client
* @auth: 1058778041@qq.com
* @create: 2025/04/18
* @para[in]: strMapping, name for file mapping
* @para[in]: bGlobal, global tag; set true if system process for server
* @para[in]: bDuplex, duplex communication tag; set true if client sends msg to server
* @return: bool, true: success, or failed
*
*/
bool ConnectServer(const std::string& strMapping, bool bGlobal = false, bool bDuplex = false);
/*
*
* @brief: Write data to client
* @auth: 1058778041@qq.com
* @create: 2025/04/18
* @para[in]: strData, msg to write
* @return: bool, true: success, or failed
*
*/
bool WriteDataToClient(const std::string& strData);
/*
*
* @brief: Write data to server
* @auth: 1058778041@qq.com
* @create: 2025/04/18
* @para[in]: strData, msg to write
* @para[in]: nTimeout, timeout to wait write
* @return: bool, true: success, or failed
*
*/
bool WriteDataToServer(const std::string& strData, int nTimeout = 30000);
/*
*
* @brief: listen event for msg(client and server)
* @auth: 1058778041@qq.com
* @create: 2025/04/18
* @para[in]: cbData, callback while msg ready
* @para[in]: bServer, server tag; set true if duplex server (in server)
* @return: bool, true: success, or failed
*
*/
void ListenEvent(FuncDataCallback cbData, bool bServer = false);
/*
*
* @brief: close mapping
* @auth: 1058778041@qq.com
* @create: 2025/04/18
* @return: void
*
*/
void CloseMapping();
private:
std::string handle_global_name(const std::string& strInput);
void listen_event(bool bServer = false);
bool check_stop();
bool setup_mapping(const std::string& strName, bool bGlobal = false, bool bDuplex = false);
bool open_mapping(const std::string& strName, bool bGlobal = false, bool bDuplex = false);
bool setup_event(const std::string& strName, bool bGlobal = false, bool bDuplex = false);
bool open_event(const std::string& strName, bool bGlobal = false, bool bDuplex = false);
void notify_event(bool bReader = false);
bool create_semaphore(const std::string& strSemaphore, bool bGlobal = false);
bool open_semaphor(const std::string& strName, bool bGlobal = false);
void release_semaphor();
bool read_data(std::string& strData, bool bServer = false);
bool write_data(const std::string& strData, bool bToServer = false, int nTimeout = 30000);
private:
std::atomic<bool> m_bStop;
FuncDataCallback m_cbMsg;
// simple write(server) -> read(client)
void* m_pMappingFile;
void* m_pMappingData;
void* m_pEventHandle;
char* m_pFileData;
// duplex write(client) -> read(server)
bool m_bDuplex;
void* m_pDuplexMapping;
void* m_pDuplexReaderMapping;
void* m_pDuplexEvent;
char* m_pDuplexFileData;
// semaphore
void* m_pSemaphore;
// work thread
Utils::ThreadObject m_objWorker;
};
}
FileMapping.cpp
#include "FileMapping.h"
#include <Windows.h>
#include <sddl.h>
#include "Utils/Logger.h"
using namespace Utils;
FileMapping::FileMapping()
: m_pMappingFile(nullptr)
, m_pMappingData(nullptr)
, m_pFileData(nullptr)
, m_bStop(false)
, m_pDuplexMapping(nullptr)
, m_pDuplexReaderMapping(nullptr)
, m_pDuplexFileData(nullptr)
, m_pDuplexEvent(nullptr)
, m_pSemaphore(nullptr)
, m_bDuplex(false)
{
}
bool FileMapping::CreateMapping(const std::string& strMapping,
bool bGlobal/* = false */,
bool bDuplex/* = false */)
{
m_bDuplex = bDuplex;
bool bSucc = false;
// create/open mapping
if (!setup_mapping(strMapping, bGlobal)) return bSucc;
if (!open_mapping(strMapping, bGlobal)) return bSucc;
if (bDuplex) {
std::string strDuplex = strMapping + kDuplexTag;
if (!setup_mapping(strDuplex, bGlobal, bDuplex)) return bSucc;
if (!open_mapping(strDuplex, bGlobal, bDuplex)) return bSucc;
}
// event
std::string strEventName = strMapping + kEventTag;
if(!setup_event(strEventName, bGlobal)) return bSucc;
if (bDuplex) {
std::string strDuplex = strEventName + kDuplexTag;
if (!setup_event(strDuplex, bGlobal, bDuplex)) return bSucc;
}
// setup semaphore
if (bDuplex) {
std::string strSemaphor = strMapping + kSemaphoreTag;
create_semaphore(strSemaphor, bGlobal);
}
return true;
}
bool FileMapping::ConnectServer(const std::string& strMapping,
bool bGlobal/* = false */,
bool bDuplex/* = false */)
{
bool bSucc = false;
// mapping
if (!open_mapping(strMapping, bGlobal)) return bSucc;
if (bDuplex) {
std::string strDuplex = strMapping + kDuplexTag;
if (!open_mapping(strDuplex, bGlobal, bDuplex)) {
return bSucc;
}
}
// event
std::string strEventName = strMapping + kEventTag;
if (!open_event(strEventName, bGlobal)) return bSucc;
if (bDuplex) {
std::string strDuplex = strEventName + kDuplexTag;
if (!open_event(strDuplex, bGlobal, bDuplex)) {
return bSucc;
}
}
// semaphore
if (bDuplex) {
std::string strSemaphore = strMapping + kSemaphoreTag;
if (!open_semaphor(strSemaphore, bGlobal)) return bSucc;
}
return true;
}
bool FileMapping::WriteDataToClient(const std::string& strData)
{
return write_data(strData);
}
bool FileMapping::WriteDataToServer(const std::string& strData, int nTimeout/* = 30000*/)
{
return write_data(strData, true, nTimeout);
}
void FileMapping::ListenEvent(FuncDataCallback cbData, bool bServer/* = false */)
{
if (m_pEventHandle) {
m_cbMsg = cbData;
m_objWorker.Start(std::bind(&FileMapping::listen_event, this, bServer), 10);
}
}
void FileMapping::CloseMapping()
{
m_bStop.store(true);
// it may trigger error signal to client or server, so ignore it
//m_objWorker.Stop();
if (m_pFileData) {
UnmapViewOfFile(m_pFileData);
m_pFileData = NULL;
}
if (m_pMappingData) {
CloseHandle(m_pMappingData);
m_pMappingData = NULL;
}
if (m_pMappingFile) {
CloseHandle(m_pMappingFile);
m_pMappingFile = NULL;
}
if (m_pEventHandle) {
CloseHandle(m_pEventHandle);
m_pEventHandle = NULL;
}
// duplex
if (m_pDuplexFileData) {
UnmapViewOfFile(m_pDuplexFileData);
m_pDuplexFileData = NULL;
}
if (m_pDuplexReaderMapping) {
CloseHandle(m_pDuplexReaderMapping);
m_pDuplexReaderMapping = NULL;
}
if (m_pDuplexMapping) {
CloseHandle(m_pDuplexMapping);
m_pDuplexMapping = NULL;
}
if (m_pDuplexEvent) {
CloseHandle(m_pDuplexEvent);
m_pDuplexEvent = NULL;
}
if (m_pSemaphore) {
CloseHandle(m_pSemaphore);
m_pSemaphore = NULL;
}
}
std::string FileMapping::handle_global_name(const std::string& strInput)
{
std::string strResult(strInput);
if (strResult.find(kGlobalTag) == std::string::npos) {
strResult = kGlobalTag + strResult;
}
return strResult;
}
void FileMapping::listen_event(bool bServer/* = false */)
{
auto pEvent = bServer ? m_pDuplexEvent : m_pEventHandle;
DWORD dwWaitResult = WaitForSingleObject(pEvent, INFINITE);
if (dwWaitResult == WAIT_OBJECT_0) {
if (check_stop()) {
LOG(INFO) << "Receive exit signal";
return;
}
if (m_cbMsg) {
std::string strMsg;
read_data(strMsg, bServer);
m_cbMsg(strMsg);
}
}
}
bool FileMapping::check_stop()
{
return m_bStop.load();
}
bool FileMapping::setup_mapping(const std::string& strName, bool bGlobal/* = false */, bool bReader/* = false*/)
{
std::string strMappingName(strName);
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
// common user support
PSECURITY_DESCRIPTOR pSD = NULL;
if (bGlobal) {
strMappingName = handle_global_name(strMappingName);
if (!ConvertStringSecurityDescriptorToSecurityDescriptor(
L"D:(A;;GA;;;AU)(A;;GA;;;SY)",
1,//SDDL_REVISION_1,
&pSD,
NULL)) {
}
sa.lpSecurityDescriptor = pSD;
}
HANDLE hFileMapping = CreateFileMappingA(INVALID_HANDLE_VALUE, &sa, PAGE_READWRITE, 0, 0x100, strMappingName.c_str());
if (NULL == hFileMapping) {
LOG(LERROR) << "Create file mapping error: " << GetLastError();
return false;
}
if (bReader) {
m_pDuplexMapping = (void*)hFileMapping;
}
else {
m_pMappingFile = (void*)hFileMapping;
}
LOG(INFO) << "Create file mapping succ.";
return true;
}
bool FileMapping::open_mapping(const std::string& strName, bool bGlobal/* = false */, bool bReader/* = false*/)
{
std::string strMappingName(strName);
if (bGlobal)
strMappingName = handle_global_name(strMappingName);
HANDLE hMapping = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, strMappingName.c_str());
if (NULL == hMapping) {
LOG(LERROR) << "Open file mapping failed: " << GetLastError();
return false;
}
char* pFileData = (char*)MapViewOfFile(hMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if (NULL == pFileData) {
LOG(LERROR) << "Map view file failed: " << GetLastError();
return false;
}
if (bReader) {
m_pDuplexReaderMapping = (void*)hMapping;
m_pDuplexFileData = pFileData;
}
else {
m_pMappingData = (void*)hMapping;
m_pFileData = pFileData;
}
return true;
}
bool FileMapping::setup_event(const std::string& strName, bool bGlobal/* = false */, bool bDuplex/* = false*/)
{
std::string strEventName(strName);
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
// common user support
PSECURITY_DESCRIPTOR pSD = NULL;
if (bGlobal) {
strEventName = handle_global_name(strEventName);
if (!ConvertStringSecurityDescriptorToSecurityDescriptor(
L"D:(A;;GA;;;AU)",
1,//SDDL_REVISION_1,
&pSD,
NULL)) {
}
}
sa.lpSecurityDescriptor = pSD;
HANDLE hEvent = CreateEventA(&sa, TRUE, FALSE, strEventName.c_str());
if (NULL == hEvent) {
LOG(LERROR) << "Create event failed: " << GetLastError();
return false;
}
if (bDuplex) {
m_pDuplexEvent = (void*)hEvent;
}
else {
m_pEventHandle = (void*)hEvent;
}
return true;
}
bool FileMapping::open_event(const std::string& strName, bool bGlobal/* = false*/, bool bDuplex/* = false*/)
{
std::string strEventName(strName);
if (bGlobal)
strEventName = handle_global_name(strEventName);
HANDLE hEvent = OpenEventA(EVENT_ALL_ACCESS, FALSE, strEventName.c_str());
if (INVALID_HANDLE_VALUE == hEvent) {
LOG(LERROR) << "Open event failed: " << GetLastError();
return false;
}
if (bDuplex) {
m_pDuplexEvent = (void*)hEvent;
}
else {
m_pEventHandle = (void*)hEvent;
}
return true;
}
void FileMapping::notify_event(bool bToServer/* = false*/)
{
if (bToServer) {
if (m_pDuplexEvent != NULL) {
SetEvent(m_pDuplexEvent);
}
}
else {
if (m_pEventHandle != NULL) {
SetEvent(m_pEventHandle);
ResetEvent(m_pEventHandle);
}
}
}
bool FileMapping::create_semaphore(const std::string& strName, bool bGlobal/* = false*/)
{
std::string strSemphorName(strName);
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
// common user support
PSECURITY_DESCRIPTOR pSD = NULL;
if (bGlobal) {
strSemphorName = handle_global_name(strSemphorName);
if (!ConvertStringSecurityDescriptorToSecurityDescriptor(
L"D:(A;;GA;;;AU)",
1,//SDDL_REVISION_1,
&pSD,
NULL)) {
}
}
sa.lpSecurityDescriptor = pSD;
HANDLE hSemaphore = CreateSemaphoreA(&sa, 1, 1, strSemphorName.c_str());
if (NULL == hSemaphore) {
LOG(LERROR) << "Create semaphor error: " << GetLastError();
return false;
}
m_pSemaphore = hSemaphore;
LOG(INFO) << "Create semaphore succ";
return true;
}
bool FileMapping::open_semaphor(const std::string& strName, bool bGlobal/* = false*/)
{
std::string strSemaphorName(strName);
if (bGlobal)
strSemaphorName = handle_global_name(strSemaphorName);
HANDLE hSemaphore = OpenSemaphoreA(SEMAPHORE_ALL_ACCESS, FALSE, strSemaphorName.c_str());
if (NULL == hSemaphore) {
LOG(LERROR) << "Open semaphore mapping failed: " << GetLastError();
return false;
}
m_pSemaphore = hSemaphore;
return true;
}
void FileMapping::release_semaphor()
{
if(m_pSemaphore)
ReleaseSemaphore(m_pSemaphore, 1, NULL);
}
bool FileMapping::read_data(std::string& strData, bool bServer/* = false */)
{
bool bSucc = false;
if (bServer) {
if (NULL == m_pDuplexReaderMapping || NULL == m_pSemaphore) return bSucc;
if (m_pDuplexFileData && strlen(m_pDuplexFileData)) {
strData = m_pDuplexFileData;
ResetEvent(m_pDuplexEvent);
release_semaphor();
return true;
}
}
else {
if (NULL == m_pMappingData) return bSucc;
if (m_pFileData && strlen(m_pFileData)) {
strData = m_pFileData;
return true;
}
}
return bSucc;
}
bool FileMapping::write_data(const std::string& strData, bool bToServer/* = false*/, int nTimeout/* = 30000*/)
{
bool bSucc = false;
if (bToServer) {
if (NULL == m_pDuplexReaderMapping || NULL == m_pSemaphore) return bSucc;
if (m_pDuplexFileData) {
auto nCode = WaitForSingleObject(m_pSemaphore, nTimeout);
if (WAIT_OBJECT_0 == nCode) {
strcpy_s(m_pDuplexFileData, strData.length() + 1, strData.c_str());
#if 0
{
LOG(INFO) << "Write data succ: " << strData;
}
#endif
notify_event(true);
return true;
}
LOG(LERROR) << "Wait error to write: " << nCode;
}
}
else {
if (NULL == m_pMappingData) return bSucc;
if (m_pFileData) {
strcpy_s(m_pFileData, strData.length() + 1, strData.c_str());
notify_event();
return true;
}
}
return true;
}
main.cpp
#include <iostream>
#include "Utils/FileMapping.h"
#include <thread>
#include <chrono>
std::string strMappingName("MappingTest");
bool bStop = false;
void testServer()
{
Utils::FileMapping objMapping;
bool bGlobal = false;
bool bDuplex = true;
objMapping.CreateMapping(strMappingName, bGlobal, bDuplex);
#if 0
if (bDuplex) {
objMapping.ListenEvent([=](const std::string& strMsg) {
std::cout << "====Duplex====" << strMsg << "\n";
}, true);
}
#endif
int nIndex = 0;
while (!bStop) {
std::this_thread::sleep_for(std::chrono::milliseconds(3000));
std::string strTestData = "Hello World " + std::to_string(++nIndex);
objMapping.WriteDataToClient(strTestData);
}
}
void testClient(const std::string strTag, bool bDuplex = false)
{
Utils::FileMapping objMapping;
bool bGlobal = false;
objMapping.ConnectServer(strMappingName, bGlobal, bDuplex);
#if 1
objMapping.ListenEvent([=](const std::string& strMsg) {
std::cout << "[" << strTag << "]: " << strMsg << "\n";
});
#endif
#if 0
if (bDuplex) {
int nIndex = 0;
while (!bStop) {
std::string strTestData = "[" + strTag + "] Msg: " + std::to_string(++nIndex);
objMapping.WriteDataToServer(strTestData);
std::this_thread::sleep_for(std::chrono::milliseconds(3000));
}
}
#endif
getchar();
}
int main()
{
std::thread objServer(testServer);
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
std::thread objClientx(testClient, "A", false);
std::thread objClienta(testClient, "B", false);
std::thread objClientC(testClient, "C", true);
std::thread objClientD(testClient, "D", true);
#if 0
std::thread objClientE(testClient, "E", false);
std::thread objClientF(testClient, "F", false);
std::thread objClientH(testClient, "G", false);
std::thread objClientI(testClient, "H", false);
std::thread objClientJ(testClient, "I", false);
std::thread objClientK(testClient, "J", false);
#endif
getchar();
bStop = true;
if (objServer.joinable()) objServer.join();
//if (objClientx.joinable()) objClientx.join();
//if (objClienta.joinable()) objClienta.join();
return 0;
}
PS
1、支持服务->客户端(广播)
2、支持双端(客户端->服务器)