win-shared-memory 0.1.0

A library to easily access windows shared memory.
//! Low-level Windows shared-memory primitives.
//!
//! This module provides [`SharedMemoryLink<T, Access>`], a typed, RAII handle to a named
//! Windows file-mapping object. Two access-mode markers control what operations are available:
//!
//! | Marker | Constructors | Access |
//! |--------|--------------|--------|
//! | [`ReadOnly`]  | [`SharedMemoryLink::open`] | [`get`][SharedMemoryLink::get] |
//! | [`ReadWrite`] | [`SharedMemoryLink::create`], [`SharedMemoryLink::get_or_create`], [`SharedMemoryLink::open_existing`] | [`get`][SharedMemoryLink::get] + [`get_mut`][SharedMemoryLink::get_mut] |
//!
//! # Example
//!
//! ```no_run
//! use win_shared_memory::{SharedMemoryLink, ReadOnly};
//!
//! #[repr(C)]
//! struct GameData { value: f32 }
//!
//! let link: SharedMemoryLink<GameData, ReadOnly> =
//!     SharedMemoryLink::open("Local\\my_game_data").expect("game not running");
//!
//! let data = unsafe { link.get() };
//! println!("value = {}", data.value);
//! ```

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,
};

/// Errors that can occur when opening or creating a shared memory region.
#[derive(Debug, thiserror::Error)]
pub enum SharedMemoryError {
    /// `OpenFileMappingW` failed — the named mapping does not exist or access was denied.
    #[error("failed to open shared memory file mapping: {0}")]
    OpenMapping(#[source] windows::core::Error),

    /// `CreateFileMappingW` failed.
    #[error("failed to create shared memory file mapping: {0}")]
    CreateMapping(#[source] windows::core::Error),

    /// `MapViewOfFile` returned a null address.
    #[error("failed to map view of file: {0}")]
    MapView(#[source] windows::core::Error),

    /// A named mapping with the given key already exists (returned by [`SharedMemoryLink::create`]).
    #[error("a shared memory region with the given name already exists")]
    AlreadyExists,

    /// `size_of::<T>()` exceeds `u32::MAX` and cannot be passed to `CreateFileMappingW`.
    #[error("type is too large to fit in a 32-bit file-mapping size")]
    SizeTooLarge,
}

/// Access-mode marker for a read-only [`SharedMemoryLink`].
#[derive(Debug)]
pub struct ReadOnly;

/// Access-mode marker for a read-write [`SharedMemoryLink`].
#[derive(Debug)]
pub struct ReadWrite;

/// A handle to a named Windows shared-memory region typed over the layout `T` it contains.
///
/// The `Access` type parameter is either [`ReadOnly`] or [`ReadWrite`] and controls which
/// operations are available:
///
/// - [`SharedMemoryLink<T, ReadOnly>`] — open with [`open`][SharedMemoryLink::open]; provides
///   read access via [`get`][SharedMemoryLink::get].
/// - [`SharedMemoryLink<T, ReadWrite>`] — create with [`create`][SharedMemoryLink::create] or
///   [`get_or_create`][SharedMemoryLink::get_or_create], or open an existing mapping with
///   [`open_existing`][SharedMemoryLink::open_existing]; provides both
///   [`get`][SharedMemoryLink::get] and [`get_mut`][SharedMemoryLink::get_mut].
///
/// The view is unmapped and the handle is closed automatically on drop.
#[derive(Debug)]
pub struct SharedMemoryLink<T, Access = ReadOnly> {
    handle: HANDLE,
    view: MEMORY_MAPPED_VIEW_ADDRESS,
    phantom: PhantomData<(T, Access)>,
}

// SAFETY: transferring ownership to another thread is safe when T: Send.
unsafe impl<T: Send, A> Send for SharedMemoryLink<T, A> {}
// SAFETY: sharing a reference across threads is safe when T: Sync.
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()
}

/// Map a view of `handle`, closing it and returning an error on failure.
///
/// # Safety
/// `handle` must be a valid file-mapping handle.
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> {
    /// Open an existing named shared-memory region for reading.
    ///
    /// `key` is the Win32 mapping name (e.g. `"Local\\file"`).
    ///
    /// # Errors
    ///
    /// - [`SharedMemoryError::OpenMapping`] — the mapping does not exist or access is denied.
    /// - [`SharedMemoryError::MapView`] — the view could not be mapped into the process.
    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> {
    /// Create a new named shared-memory region backed by the system paging file.
    ///
    /// The region is zero-initialised by the OS. Fails if a mapping with `key` already exists.
    ///
    /// `key` is the Win32 mapping name (e.g. `"Local\\file"`).
    ///
    /// # Errors
    ///
    /// - [`SharedMemoryError::AlreadyExists`] — a mapping with the given name already exists.
    /// - [`SharedMemoryError::CreateMapping`] — the mapping could not be created.
    /// - [`SharedMemoryError::MapView`] — the view could not be mapped into the process.
    /// - [`SharedMemoryError::SizeTooLarge`] — `size_of::<T>()` exceeds `u32::MAX`.
    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)?;

        // Clear the last error before the call so that a stale ERROR_ALREADY_EXISTS left by an
        // unrelated prior Win32 call cannot produce a false positive below.
        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)
    }

    /// Open or create a named shared-memory region backed by the system paging file.
    ///
    /// If the mapping does not exist it is created and zero-initialised by the OS. If it already
    /// exists the handle to the existing region is returned — the contents are whatever the
    /// owning process has written.
    ///
    /// `key` is the Win32 mapping name (e.g. `"Local\\file"`).
    ///
    /// # Errors
    ///
    /// - [`SharedMemoryError::CreateMapping`] — the mapping could not be created or opened.
    /// - [`SharedMemoryError::MapView`] — the view could not be mapped into the process.
    /// - [`SharedMemoryError::SizeTooLarge`] — `size_of::<T>()` exceeds `u32::MAX`.
    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)
    }

    /// Open an existing named shared-memory region for reading and writing.
    ///
    /// Unlike [`create`][Self::create] and [`get_or_create`][Self::get_or_create] this does not
    /// allocate a new mapping — it fails if no mapping with `key` exists yet.
    ///
    /// `key` is the Win32 mapping name (e.g. `"Local\\file"`).
    ///
    /// # Errors
    ///
    /// - [`SharedMemoryError::OpenMapping`] — the mapping does not exist or access is denied.
    /// - [`SharedMemoryError::MapView`] — the view could not be mapped into the process.
    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)
    }

    /// Return a mutable reference to the mapped region interpreted as `T`.
    ///
    /// # Safety
    ///
    /// The caller must ensure no other reference (in any process) aliases this memory for the
    /// duration of the returned borrow. In practice this means coordinating access with a named
    /// mutex or similar cross-process synchronisation primitive.
    pub unsafe fn get_mut(&mut self) -> &mut T {
        unsafe { &mut *(self.view.Value as *mut T) }
    }
}

impl<T, A> SharedMemoryLink<T, A> {
    /// Return a shared reference to the mapped region interpreted as `T`.
    ///
    /// # Safety
    ///
    /// The caller must ensure no other reference (in any process) mutably aliases this memory for
    /// the duration of the returned borrow. In practice this means coordinating access with a
    /// named mutex or similar cross-process synchronisation primitive.
    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);
        }
    }
}