use std::{ffi::OsStr, iter::once, marker::PhantomData, os::windows::ffi::OsStrExt};
use windows::{
Win32::{
Foundation::{
CloseHandle, ERROR_ALREADY_EXISTS, HANDLE, INVALID_HANDLE_VALUE, SetLastError,
WIN32_ERROR,
},
System::Memory::{
CreateFileMappingW, FILE_MAP_ALL_ACCESS, FILE_MAP_READ, MEMORY_MAPPED_VIEW_ADDRESS,
MapViewOfFile, OpenFileMappingW, PAGE_READWRITE, UnmapViewOfFile,
},
},
core::PCWSTR,
};
#[derive(Debug, thiserror::Error)]
pub enum SharedMemoryError {
#[error("failed to open shared memory file mapping: {0}")]
OpenMapping(#[source] windows::core::Error),
#[error("failed to create shared memory file mapping: {0}")]
CreateMapping(#[source] windows::core::Error),
#[error("failed to map view of file: {0}")]
MapView(#[source] windows::core::Error),
#[error("a shared memory region with the given name already exists")]
AlreadyExists,
#[error("type is too large to fit in a 32-bit file-mapping size")]
SizeTooLarge,
}
#[derive(Debug)]
pub struct ReadOnly;
#[derive(Debug)]
pub struct ReadWrite;
#[derive(Debug)]
pub struct SharedMemoryLink<T, Access = ReadOnly> {
handle: HANDLE,
view: MEMORY_MAPPED_VIEW_ADDRESS,
phantom: PhantomData<(T, Access)>,
}
unsafe impl<T: Send, A> Send for SharedMemoryLink<T, A> {}
unsafe impl<T: Sync, A> Sync for SharedMemoryLink<T, A> {}
fn to_null_terminated_wide(s: &str) -> Vec<u16> {
OsStr::new(s).encode_wide().chain(once(0)).collect()
}
unsafe fn map_view(
handle: HANDLE,
access: u32,
size: usize,
) -> Result<MEMORY_MAPPED_VIEW_ADDRESS, SharedMemoryError> {
let view = unsafe {
MapViewOfFile(
handle,
windows::Win32::System::Memory::FILE_MAP(access),
0,
0,
size,
)
};
if view.Value.is_null() {
let err = windows::core::Error::from_thread();
unsafe {
let _ = CloseHandle(handle);
}
return Err(SharedMemoryError::MapView(err));
}
Ok(view)
}
impl<T> SharedMemoryLink<T, ReadOnly> {
pub fn open(key: &str) -> Result<Self, SharedMemoryError> {
let wide = to_null_terminated_wide(key);
let handle = unsafe { OpenFileMappingW(FILE_MAP_READ.0, false, PCWSTR(wide.as_ptr())) }
.map_err(SharedMemoryError::OpenMapping)?;
let view = unsafe { map_view(handle, FILE_MAP_READ.0, size_of::<T>()) }?;
let output = Self {
handle,
view,
phantom: PhantomData,
};
Ok(output)
}
}
impl<T> SharedMemoryLink<T, ReadWrite> {
pub fn create(key: &str) -> Result<Self, SharedMemoryError> {
let wide = to_null_terminated_wide(key);
let size = u32::try_from(size_of::<T>()).map_err(|_| SharedMemoryError::SizeTooLarge)?;
unsafe { SetLastError(WIN32_ERROR(0)) };
let handle = unsafe {
CreateFileMappingW(
INVALID_HANDLE_VALUE,
None,
PAGE_READWRITE,
0,
size,
PCWSTR(wide.as_ptr()),
)
}
.map_err(SharedMemoryError::CreateMapping)?;
let last_err = windows::core::Error::from_thread();
if last_err.code() == ERROR_ALREADY_EXISTS.to_hresult() {
unsafe {
let _ = CloseHandle(handle);
}
return Err(SharedMemoryError::AlreadyExists);
}
let view = unsafe { map_view(handle, FILE_MAP_ALL_ACCESS.0, size_of::<T>()) }?;
let output = Self {
handle,
view,
phantom: PhantomData,
};
Ok(output)
}
pub fn get_or_create(key: &str) -> Result<Self, SharedMemoryError> {
let wide = to_null_terminated_wide(key);
let size = u32::try_from(size_of::<T>()).map_err(|_| SharedMemoryError::SizeTooLarge)?;
let handle = unsafe {
CreateFileMappingW(
INVALID_HANDLE_VALUE,
None,
PAGE_READWRITE,
0,
size,
PCWSTR(wide.as_ptr()),
)
}
.map_err(SharedMemoryError::CreateMapping)?;
let view = unsafe { map_view(handle, FILE_MAP_ALL_ACCESS.0, size_of::<T>()) }?;
let output = Self {
handle,
view,
phantom: PhantomData,
};
Ok(output)
}
pub fn open_existing(key: &str) -> Result<Self, SharedMemoryError> {
let wide = to_null_terminated_wide(key);
let handle =
unsafe { OpenFileMappingW(FILE_MAP_ALL_ACCESS.0, false, PCWSTR(wide.as_ptr())) }
.map_err(SharedMemoryError::OpenMapping)?;
let view = unsafe { map_view(handle, FILE_MAP_ALL_ACCESS.0, size_of::<T>()) }?;
let output = Self {
handle,
view,
phantom: PhantomData,
};
Ok(output)
}
pub unsafe fn get_mut(&mut self) -> &mut T {
unsafe { &mut *(self.view.Value as *mut T) }
}
}
impl<T, A> SharedMemoryLink<T, A> {
pub unsafe fn get(&self) -> &T {
unsafe { &*(self.view.Value as *const T) }
}
}
impl<T, A> Drop for SharedMemoryLink<T, A> {
fn drop(&mut self) {
unsafe {
let _ = UnmapViewOfFile(self.view);
let _ = CloseHandle(self.handle);
}
}
}