3b1f3301d6
Changes: - Added SharedMemory module for file-backed memory-mapped storage - Added MappingInfo, FileHeader, RecordState and TrackedRecord infrastructure - Replaced CSV-based serialization with binary struct serialization - Added DataStore initialization and shutdown lifecycle management - Added datastore mutex synchronization for multi-process access - Added shared-memory mapping configuration for all datastore entities - Added generic loadRecords and saveRecords template infrastructure - Added automatic datastore directory creation and .dat file storage - Updated configuration to use binary datastore files and initial capacities - Added enum underlying types for serialization compatibility - Added password masking support for login, registration and password change flows - Added Visual Studio project configuration for shared-memory components - Added datastore-owned record caches for runtime object management - Updated datastore APIs to return cached tracked-record collections by reference - Added generic cache refresh and cleanup infrastructure - Updated save operations to persist datastore caches directly - Added automatic cache cleanup during datastore destruction - Prepared datastore for multi-process shared-memory persistence
385 lines
9.8 KiB
C++
385 lines
9.8 KiB
C++
/*
|
|
File: SharedMemory.cpp
|
|
Description: Implements shared memory utilities for managing
|
|
Windows file mapping operations. Provides functions
|
|
to create, open, resize, and close mappings, as well
|
|
as access headers, records, and ensure synchronization
|
|
across processes.
|
|
Author: Trenser
|
|
Created: 11-June-2026
|
|
*/
|
|
|
|
#include "SharedMemory.h"
|
|
#include "Config.h"
|
|
|
|
/*
|
|
Function: invalidateMapping
|
|
Description: Releases all mapping resources and resets the mapping to an invalid state.
|
|
Parameters:
|
|
- mapping: MappingInfo&, mapping information and handles
|
|
Returns:
|
|
- None
|
|
*/
|
|
static void invalidateMapping(MappingInfo& mapping)
|
|
{
|
|
if (mapping.mappedView != nullptr)
|
|
{
|
|
UnmapViewOfFile(mapping.mappedView);
|
|
mapping.mappedView = nullptr;
|
|
}
|
|
if (mapping.mappingHandle != NULL)
|
|
{
|
|
CloseHandle(mapping.mappingHandle);
|
|
mapping.mappingHandle = NULL;
|
|
}
|
|
if (mapping.fileHandle != INVALID_HANDLE_VALUE)
|
|
{
|
|
CloseHandle(mapping.fileHandle);
|
|
mapping.fileHandle = INVALID_HANDLE_VALUE;
|
|
}
|
|
mapping.mappedCapacity = 0;
|
|
}
|
|
|
|
/*
|
|
Function: createOrOpenMapping
|
|
Description: Creates or opens a file mapping and maps it into the process address space.
|
|
Parameters:
|
|
- mapping: MappingInfo&, mapping information and handles
|
|
Returns:
|
|
- bool: True if the mapping was successfully created/opened, otherwise false
|
|
*/
|
|
bool SharedMemory::createOrOpenMapping(MappingInfo& mapping)
|
|
{
|
|
if (mapping.recordSize == 0)
|
|
{
|
|
return false;
|
|
}
|
|
mapping.fileHandle =
|
|
CreateFileA(
|
|
mapping.fileName.c_str(),
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
NULL,
|
|
OPEN_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL);
|
|
if (mapping.fileHandle == INVALID_HANDLE_VALUE)
|
|
{
|
|
return false;
|
|
}
|
|
LARGE_INTEGER fileSize;
|
|
if (!GetFileSizeEx(mapping.fileHandle, &fileSize))
|
|
{
|
|
invalidateMapping(mapping);
|
|
return false;
|
|
}
|
|
bool isNewFile = (fileSize.QuadPart == 0);
|
|
const size_t initialCapacity = config::file::INITIAL_CAPACITY;
|
|
if (isNewFile)
|
|
{
|
|
LARGE_INTEGER newSize;
|
|
newSize.QuadPart = sizeof(FileHeader) + initialCapacity * mapping.recordSize;
|
|
if (!SetFilePointerEx(mapping.fileHandle, newSize, NULL, FILE_BEGIN))
|
|
{
|
|
invalidateMapping(mapping);
|
|
return false;
|
|
}
|
|
if (!SetEndOfFile(mapping.fileHandle))
|
|
{
|
|
invalidateMapping(mapping);
|
|
return false;
|
|
}
|
|
}
|
|
mapping.mappingHandle =
|
|
CreateFileMappingA(
|
|
mapping.fileHandle,
|
|
NULL,
|
|
PAGE_READWRITE,
|
|
0,
|
|
0,
|
|
NULL);
|
|
if (mapping.mappingHandle == NULL)
|
|
{
|
|
invalidateMapping(mapping);
|
|
return false;
|
|
}
|
|
mapping.mappedView =
|
|
MapViewOfFile(
|
|
mapping.mappingHandle,
|
|
FILE_MAP_ALL_ACCESS,
|
|
0,
|
|
0,
|
|
0);
|
|
if (mapping.mappedView == nullptr)
|
|
{
|
|
invalidateMapping(mapping);
|
|
return false;
|
|
}
|
|
FileHeader* header = getHeader(mapping);
|
|
if (header == nullptr)
|
|
{
|
|
invalidateMapping(mapping);
|
|
return false;
|
|
}
|
|
if (isNewFile)
|
|
{
|
|
header->recordCount = 0;
|
|
header->capacity = initialCapacity;
|
|
}
|
|
mapping.mappedCapacity = header->capacity;
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
Function: closeMapping
|
|
Description: Unmaps and closes all resources associated with a file mapping.
|
|
Parameters:
|
|
- mapping: MappingInfo&, mapping to close
|
|
Returns:
|
|
- None
|
|
*/
|
|
void SharedMemory::closeMapping(MappingInfo& mapping)
|
|
{
|
|
invalidateMapping(mapping);
|
|
}
|
|
|
|
/*
|
|
Function: getHeader
|
|
Description: Returns the file header stored at the beginning of the mapped memory region.
|
|
Parameters:
|
|
- mapping: MappingInfo&, mapping information and handles
|
|
Returns:
|
|
- FileHeader*: Pointer to the file header, or nullptr if the mapping is not valid
|
|
*/
|
|
FileHeader* SharedMemory::getHeader(MappingInfo& mapping)
|
|
{
|
|
if (mapping.mappedView == nullptr)
|
|
{
|
|
return nullptr;
|
|
}
|
|
return reinterpret_cast<FileHeader*>(mapping.mappedView);
|
|
}
|
|
|
|
/*
|
|
Function: getRecordAddress
|
|
Description: Returns the address of a record at the specified index within the mapped memory region.
|
|
Parameters:
|
|
- mapping: MappingInfo&, mapping information and handles
|
|
- index: size_t, record index
|
|
Returns:
|
|
- void*: Pointer to the record, or nullptr if the mapping is not valid
|
|
*/
|
|
void* SharedMemory::getRecordAddress(MappingInfo& mapping, size_t index)
|
|
{
|
|
if (mapping.mappedView == nullptr)
|
|
{
|
|
return nullptr;
|
|
}
|
|
return reinterpret_cast<char*>(mapping.mappedView) + sizeof(FileHeader) + index * mapping.recordSize;
|
|
}
|
|
|
|
/*
|
|
Function: getRecordCount
|
|
Description: Returns the number of records currently stored in the mapping.
|
|
Parameters:
|
|
- mapping: MappingInfo&, mapping information and handles
|
|
Returns:
|
|
- size_t: Number of records in the mapping
|
|
*/
|
|
size_t SharedMemory::getRecordCount(MappingInfo& mapping)
|
|
{
|
|
FileHeader* header = getHeader(mapping);
|
|
if (header == nullptr)
|
|
{
|
|
return 0;
|
|
}
|
|
return header->recordCount;
|
|
}
|
|
|
|
/*
|
|
Function: setRecordCount
|
|
Description: Updates the number of records stored in the mapping.
|
|
Parameters:
|
|
- mapping: MappingInfo&, mapping information and handles
|
|
- count: size_t, new record count
|
|
Returns:
|
|
- None
|
|
*/
|
|
void SharedMemory::setRecordCount(MappingInfo& mapping, size_t count)
|
|
{
|
|
FileHeader* header = getHeader(mapping);
|
|
if (header == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
header->recordCount = count;
|
|
}
|
|
|
|
/*
|
|
Function: getCapacity
|
|
Description: Returns the current capacity of the mapping.
|
|
Parameters:
|
|
- mapping: MappingInfo&, mapping information and handles
|
|
Returns:
|
|
- size_t: Maximum number of records that can be stored in the mapping
|
|
*/
|
|
size_t SharedMemory::getCapacity(MappingInfo& mapping)
|
|
{
|
|
FileHeader* header = getHeader(mapping);
|
|
if (header == nullptr)
|
|
{
|
|
return 0;
|
|
}
|
|
return header->capacity;
|
|
}
|
|
|
|
/*
|
|
Function: resizeMapping
|
|
Description: Resizes the file mapping to the specified capacity.
|
|
Parameters:
|
|
- mapping: MappingInfo&, mapping information and handles
|
|
- newCapacity: size_t, new mapping capacity
|
|
Returns:
|
|
- bool: True if the resize succeeded, otherwise false
|
|
*/
|
|
bool SharedMemory::resizeMapping(MappingInfo& mapping, size_t newCapacity)
|
|
{
|
|
FileHeader* header = getHeader(mapping);
|
|
if (header == nullptr)
|
|
{
|
|
invalidateMapping(mapping);
|
|
return false;
|
|
}
|
|
header->capacity = newCapacity;
|
|
if (!FlushViewOfFile(mapping.mappedView, sizeof(FileHeader)))
|
|
{
|
|
return false;
|
|
}
|
|
if (!UnmapViewOfFile(mapping.mappedView))
|
|
{
|
|
return false;
|
|
}
|
|
mapping.mappedView = nullptr;
|
|
CloseHandle(mapping.mappingHandle);
|
|
mapping.mappingHandle = NULL;
|
|
LARGE_INTEGER newSize;
|
|
newSize.QuadPart = sizeof(FileHeader) + newCapacity * mapping.recordSize;
|
|
if (!SetFilePointerEx(mapping.fileHandle, newSize, NULL, FILE_BEGIN))
|
|
{
|
|
invalidateMapping(mapping);
|
|
return false;
|
|
}
|
|
if (!SetEndOfFile(mapping.fileHandle))
|
|
{
|
|
invalidateMapping(mapping);
|
|
return false;
|
|
}
|
|
mapping.mappingHandle =
|
|
CreateFileMappingA(
|
|
mapping.fileHandle,
|
|
NULL,
|
|
PAGE_READWRITE,
|
|
0,
|
|
0,
|
|
NULL);
|
|
if (mapping.mappingHandle == NULL)
|
|
{
|
|
invalidateMapping(mapping);
|
|
return false;
|
|
}
|
|
mapping.mappedView =
|
|
MapViewOfFile(
|
|
mapping.mappingHandle,
|
|
FILE_MAP_ALL_ACCESS,
|
|
0,
|
|
0,
|
|
0);
|
|
if (mapping.mappedView == nullptr)
|
|
{
|
|
invalidateMapping(mapping);
|
|
return false;
|
|
}
|
|
mapping.mappedCapacity = newCapacity;
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
Function: ensureCapacityForInsert
|
|
Description: Ensures that the mapping has space for at least one additional record, growing it if necessary.
|
|
Parameters:
|
|
- mapping: MappingInfo&, mapping information and handles
|
|
Returns:
|
|
- bool: True if capacity is available, otherwise false
|
|
*/
|
|
bool SharedMemory::ensureCapacityForInsert(MappingInfo& mapping)
|
|
{
|
|
size_t recordCount = getRecordCount(mapping);
|
|
size_t capacity = getCapacity(mapping);
|
|
if (recordCount < capacity)
|
|
{
|
|
return true;
|
|
}
|
|
return resizeMapping(mapping, capacity * 2);
|
|
}
|
|
|
|
/*
|
|
Function: ensureLatestMapping
|
|
Description: Remaps the file if another process has resized it.
|
|
Parameters:
|
|
- mapping: MappingInfo&, mapping information and handles
|
|
Returns:
|
|
- bool: True if the mapping is valid and up to date, otherwise false
|
|
*/
|
|
bool SharedMemory::ensureLatestMapping(MappingInfo& mapping)
|
|
{
|
|
FileHeader* header = getHeader(mapping);
|
|
if (header == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
if (header->capacity == mapping.mappedCapacity)
|
|
{
|
|
return true;
|
|
}
|
|
if (!UnmapViewOfFile(mapping.mappedView))
|
|
{
|
|
invalidateMapping(mapping);
|
|
return false;
|
|
}
|
|
mapping.mappedView = nullptr;
|
|
CloseHandle(mapping.mappingHandle);
|
|
mapping.mappingHandle = NULL;
|
|
mapping.mappingHandle =
|
|
CreateFileMappingA(
|
|
mapping.fileHandle,
|
|
NULL,
|
|
PAGE_READWRITE,
|
|
0,
|
|
0,
|
|
NULL);
|
|
if (mapping.mappingHandle == NULL)
|
|
{
|
|
invalidateMapping(mapping);
|
|
return false;
|
|
}
|
|
mapping.mappedView =
|
|
MapViewOfFile(
|
|
mapping.mappingHandle,
|
|
FILE_MAP_ALL_ACCESS,
|
|
0,
|
|
0,
|
|
0);
|
|
if (mapping.mappedView == nullptr)
|
|
{
|
|
invalidateMapping(mapping);
|
|
return false;
|
|
}
|
|
header = getHeader(mapping);
|
|
if (header == nullptr)
|
|
{
|
|
invalidateMapping(mapping);
|
|
return false;
|
|
}
|
|
mapping.mappedCapacity = header->capacity;
|
|
return true;
|
|
} |