@@ -42,6 +42,10 @@ mod mmap {
4242 CloseHandle , DUPLICATE_SAME_ACCESS , DuplicateHandle , HANDLE , INVALID_HANDLE_VALUE ,
4343 } ,
4444 Storage :: FileSystem :: { FILE_BEGIN , GetFileSize , SetEndOfFile , SetFilePointerEx } ,
45+ System :: Memory :: {
46+ CreateFileMappingW , FILE_MAP_COPY , FILE_MAP_READ , FILE_MAP_WRITE , FlushViewOfFile ,
47+ MapViewOfFile , PAGE_READONLY , PAGE_READWRITE , PAGE_WRITECOPY , UnmapViewOfFile ,
48+ } ,
4549 System :: Threading :: GetCurrentProcess ,
4650 } ;
4751
@@ -193,17 +197,68 @@ mod mmap {
193197 vm. ctx . exceptions . os_error . to_owned ( )
194198 }
195199
200+ /// Named file mapping on Windows using raw Win32 APIs.
201+ /// Supports tagname parameter for inter-process shared memory.
202+ #[ cfg( windows) ]
203+ struct NamedMmap {
204+ map_handle : HANDLE ,
205+ view_ptr : * mut u8 ,
206+ len : usize ,
207+ }
208+
209+ #[ cfg( windows) ]
210+ // SAFETY: The memory mapping is managed by the OS and is safe to share
211+ // across threads. Access is synchronized by PyMutex in PyMmap.
212+ unsafe impl Send for NamedMmap { }
213+ #[ cfg( windows) ]
214+ unsafe impl Sync for NamedMmap { }
215+
216+ #[ cfg( windows) ]
217+ impl core:: fmt:: Debug for NamedMmap {
218+ fn fmt ( & self , f : & mut core:: fmt:: Formatter < ' _ > ) -> core:: fmt:: Result {
219+ f. debug_struct ( "NamedMmap" )
220+ . field ( "map_handle" , & self . map_handle )
221+ . field ( "view_ptr" , & self . view_ptr )
222+ . field ( "len" , & self . len )
223+ . finish ( )
224+ }
225+ }
226+
227+ #[ cfg( windows) ]
228+ impl Drop for NamedMmap {
229+ fn drop ( & mut self ) {
230+ unsafe {
231+ if !self . view_ptr . is_null ( ) {
232+ UnmapViewOfFile (
233+ windows_sys:: Win32 :: System :: Memory :: MEMORY_MAPPED_VIEW_ADDRESS {
234+ Value : self . view_ptr as * mut _ ,
235+ } ,
236+ ) ;
237+ }
238+ if !self . map_handle . is_null ( ) {
239+ CloseHandle ( self . map_handle ) ;
240+ }
241+ }
242+ }
243+ }
244+
196245 #[ derive( Debug ) ]
197246 enum MmapObj {
198247 Write ( MmapMut ) ,
199248 Read ( Mmap ) ,
249+ #[ cfg( windows) ]
250+ Named ( NamedMmap ) ,
200251 }
201252
202253 impl MmapObj {
203254 fn as_slice ( & self ) -> & [ u8 ] {
204255 match self {
205256 MmapObj :: Read ( mmap) => & mmap[ ..] ,
206257 MmapObj :: Write ( mmap) => & mmap[ ..] ,
258+ #[ cfg( windows) ]
259+ MmapObj :: Named ( named) => unsafe {
260+ core:: slice:: from_raw_parts ( named. view_ptr , named. len )
261+ } ,
207262 }
208263 }
209264 }
@@ -276,7 +331,6 @@ mod mmap {
276331 #[ pyarg( any) ]
277332 length : isize ,
278333 #[ pyarg( any, default ) ]
279- #[ allow( dead_code) ]
280334 tagname : Option < PyObjectRef > ,
281335 #[ pyarg( any, default = AccessMode :: Default ) ]
282336 access : AccessMode ,
@@ -494,11 +548,22 @@ mod mmap {
494548 let mut map_size = args. validate_new_args ( vm) ?;
495549 let MmapNewArgs {
496550 fileno,
551+ tagname,
497552 access,
498553 offset,
499554 ..
500555 } = args;
501556
557+ // Parse tagname: None or a string
558+ let tag_str: Option < String > = match tagname {
559+ Some ( ref obj) if !vm. is_none ( obj) => {
560+ Some ( obj. try_to_value :: < String > ( vm) . map_err ( |_| {
561+ vm. new_type_error ( "tagname must be a string or None" . to_owned ( ) )
562+ } ) ?)
563+ }
564+ _ => None ,
565+ } ;
566+
502567 // Get file handle from fileno
503568 // fileno -1 or 0 means anonymous mapping
504569 let fh: Option < HANDLE > = if fileno != -1 && fileno != 0 {
@@ -595,6 +660,75 @@ mod mmap {
595660 }
596661 }
597662
663+ // When tagname is provided, use raw Win32 APIs for named shared memory
664+ if let Some ( ref tag) = tag_str {
665+ let ( fl_protect, desired_access) = match access {
666+ AccessMode :: Default | AccessMode :: Write => ( PAGE_READWRITE , FILE_MAP_WRITE ) ,
667+ AccessMode :: Read => ( PAGE_READONLY , FILE_MAP_READ ) ,
668+ AccessMode :: Copy => ( PAGE_WRITECOPY , FILE_MAP_COPY ) ,
669+ } ;
670+
671+ let fh = if let Some ( fh) = fh {
672+ // Close the duplicated handle - we'll use the original
673+ // file handle for CreateFileMappingW
674+ if duplicated_handle != INVALID_HANDLE_VALUE {
675+ unsafe { CloseHandle ( duplicated_handle) } ;
676+ }
677+ fh
678+ } else {
679+ INVALID_HANDLE_VALUE
680+ } ;
681+
682+ let tag_wide: Vec < u16 > = tag. encode_utf16 ( ) . chain ( core:: iter:: once ( 0 ) ) . collect ( ) ;
683+
684+ let total_size = ( offset as u64 )
685+ . checked_add ( map_size as u64 )
686+ . ok_or_else ( || vm. new_overflow_error ( "mmap offset plus size would overflow" ) ) ?;
687+ let size_hi = ( total_size >> 32 ) as u32 ;
688+ let size_lo = total_size as u32 ;
689+
690+ let map_handle = unsafe {
691+ CreateFileMappingW (
692+ fh,
693+ core:: ptr:: null ( ) ,
694+ fl_protect,
695+ size_hi,
696+ size_lo,
697+ tag_wide. as_ptr ( ) ,
698+ )
699+ } ;
700+ if map_handle. is_null ( ) {
701+ return Err ( io:: Error :: last_os_error ( ) . to_pyexception ( vm) ) ;
702+ }
703+
704+ let off_hi = ( offset as u64 >> 32 ) as u32 ;
705+ let off_lo = offset as u32 ;
706+
707+ let view =
708+ unsafe { MapViewOfFile ( map_handle, desired_access, off_hi, off_lo, map_size) } ;
709+ if view. Value . is_null ( ) {
710+ unsafe { CloseHandle ( map_handle) } ;
711+ return Err ( io:: Error :: last_os_error ( ) . to_pyexception ( vm) ) ;
712+ }
713+
714+ let named = NamedMmap {
715+ map_handle,
716+ view_ptr : view. Value as * mut u8 ,
717+ len : map_size,
718+ } ;
719+
720+ return Ok ( Self {
721+ closed : AtomicCell :: new ( false ) ,
722+ mmap : PyMutex :: new ( Some ( MmapObj :: Named ( named) ) ) ,
723+ handle : AtomicCell :: new ( INVALID_HANDLE_VALUE as isize ) ,
724+ offset,
725+ size : AtomicCell :: new ( map_size) ,
726+ pos : AtomicCell :: new ( 0 ) ,
727+ exports : AtomicCell :: new ( 0 ) ,
728+ access,
729+ } ) ;
730+ }
731+
598732 let mut mmap_opt = MmapOptions :: new ( ) ;
599733 let mmap_opt = mmap_opt. offset ( offset as u64 ) . len ( map_size) ;
600734
@@ -719,6 +853,10 @@ mod mmap {
719853 match m. as_mut ( ) . expect ( "mmap closed or invalid" ) {
720854 MmapObj :: Read ( _) => panic ! ( "mmap can't modify a readonly memory map." ) ,
721855 MmapObj :: Write ( mmap) => & mut mmap[ ..] ,
856+ #[ cfg( windows) ]
857+ MmapObj :: Named ( named) => unsafe {
858+ core:: slice:: from_raw_parts_mut ( named. view_ptr , named. len )
859+ } ,
722860 }
723861 } )
724862 . into ( )
@@ -749,15 +887,19 @@ mod mmap {
749887 fn try_writable < R > (
750888 & self ,
751889 vm : & VirtualMachine ,
752- f : impl FnOnce ( & mut MmapMut ) -> R ,
890+ f : impl FnOnce ( & mut [ u8 ] ) -> R ,
753891 ) -> PyResult < R > {
754892 if matches ! ( self . access, AccessMode :: Read ) {
755893 return Err ( vm. new_type_error ( "mmap can't modify a readonly memory map." ) ) ;
756894 }
757895
758896 match self . check_valid ( vm) ?. deref_mut ( ) . as_mut ( ) . unwrap ( ) {
759- MmapObj :: Write ( mmap) => Ok ( f ( mmap) ) ,
760- _ => unreachable ! ( "already check" ) ,
897+ MmapObj :: Write ( mmap) => Ok ( f ( & mut mmap[ ..] ) ) ,
898+ #[ cfg( windows) ]
899+ MmapObj :: Named ( named) => Ok ( f ( unsafe {
900+ core:: slice:: from_raw_parts_mut ( named. view_ptr , named. len )
901+ } ) ) ,
902+ _ => unreachable ! ( "already checked" ) ,
761903 }
762904 }
763905
@@ -873,6 +1015,14 @@ mod mmap {
8731015 mmap. flush_range ( offset, size)
8741016 . map_err ( |e| e. to_pyexception ( vm) ) ?;
8751017 }
1018+ #[ cfg( windows) ]
1019+ MmapObj :: Named ( named) => {
1020+ let ptr = unsafe { named. view_ptr . add ( offset) } ;
1021+ let result = unsafe { FlushViewOfFile ( ptr as * const _ , size) } ;
1022+ if result == 0 {
1023+ return Err ( io:: Error :: last_os_error ( ) . to_pyexception ( vm) ) ;
1024+ }
1025+ }
8761026 }
8771027
8781028 Ok ( ( ) )
@@ -1044,11 +1194,17 @@ mod mmap {
10441194 }
10451195
10461196 let handle = self . handle . load ( ) ;
1047- let is_anonymous = handle == INVALID_HANDLE_VALUE as isize ;
10481197
10491198 // Get the lock on mmap
10501199 let mut mmap_guard = self . mmap . lock ( ) ;
10511200
1201+ // Check if this is a Named mmap - these cannot be resized
1202+ if let Some ( MmapObj :: Named ( _) ) = mmap_guard. as_ref ( ) {
1203+ return Err ( vm. new_system_error ( "mmap: cannot resize a named memory mapping" ) ) ;
1204+ }
1205+
1206+ let is_anonymous = handle == INVALID_HANDLE_VALUE as isize ;
1207+
10521208 if is_anonymous {
10531209 // For anonymous mmap, we need to:
10541210 // 1. Create a new anonymous mmap with the new size
@@ -1067,10 +1223,7 @@ mod mmap {
10671223
10681224 // Copy data from old mmap to new mmap
10691225 if let Some ( old_mmap) = mmap_guard. as_ref ( ) {
1070- let src = match old_mmap {
1071- MmapObj :: Write ( m) => & m[ ..copy_size] ,
1072- MmapObj :: Read ( m) => & m[ ..copy_size] ,
1073- } ;
1226+ let src = & old_mmap. as_slice ( ) [ ..copy_size] ;
10741227 new_mmap[ ..copy_size] . copy_from_slice ( src) ;
10751228 }
10761229
0 commit comments