@@ -39,9 +39,14 @@ mod mmap {
3939 #[ cfg( windows) ]
4040 use windows_sys:: Win32 :: {
4141 Foundation :: {
42- CloseHandle , DUPLICATE_SAME_ACCESS , DuplicateHandle , HANDLE , INVALID_HANDLE_VALUE ,
42+ CloseHandle , DUPLICATE_SAME_ACCESS , DuplicateHandle , HANDLE ,
43+ INVALID_HANDLE_VALUE ,
4344 } ,
4445 Storage :: FileSystem :: { FILE_BEGIN , GetFileSize , SetEndOfFile , SetFilePointerEx } ,
46+ System :: Memory :: {
47+ CreateFileMappingW , FILE_MAP_COPY , FILE_MAP_READ , FILE_MAP_WRITE , MapViewOfFile ,
48+ PAGE_READONLY , PAGE_READWRITE , PAGE_WRITECOPY , UnmapViewOfFile ,
49+ } ,
4550 System :: Threading :: GetCurrentProcess ,
4651 } ;
4752
@@ -193,17 +198,66 @@ mod mmap {
193198 vm. ctx . exceptions . os_error . to_owned ( )
194199 }
195200
201+ /// Named file mapping on Windows using raw Win32 APIs.
202+ /// Supports tagname parameter for inter-process shared memory.
203+ #[ cfg( windows) ]
204+ struct NamedMmap {
205+ map_handle : HANDLE ,
206+ view_ptr : * mut u8 ,
207+ len : usize ,
208+ }
209+
210+ #[ cfg( windows) ]
211+ // SAFETY: The memory mapping is managed by the OS and is safe to share
212+ // across threads. Access is synchronized by PyMutex in PyMmap.
213+ unsafe impl Send for NamedMmap { }
214+ #[ cfg( windows) ]
215+ unsafe impl Sync for NamedMmap { }
216+
217+ #[ cfg( windows) ]
218+ impl core:: fmt:: Debug for NamedMmap {
219+ fn fmt ( & self , f : & mut core:: fmt:: Formatter < ' _ > ) -> core:: fmt:: Result {
220+ f. debug_struct ( "NamedMmap" )
221+ . field ( "map_handle" , & self . map_handle )
222+ . field ( "view_ptr" , & self . view_ptr )
223+ . field ( "len" , & self . len )
224+ . finish ( )
225+ }
226+ }
227+
228+ #[ cfg( windows) ]
229+ impl Drop for NamedMmap {
230+ fn drop ( & mut self ) {
231+ unsafe {
232+ if !self . view_ptr . is_null ( ) {
233+ UnmapViewOfFile ( windows_sys:: Win32 :: System :: Memory :: MEMORY_MAPPED_VIEW_ADDRESS {
234+ Value : self . view_ptr as * mut _ ,
235+ } ) ;
236+ }
237+ if !self . map_handle . is_null ( ) {
238+ CloseHandle ( self . map_handle ) ;
239+ }
240+ }
241+ }
242+ }
243+
196244 #[ derive( Debug ) ]
197245 enum MmapObj {
198246 Write ( MmapMut ) ,
199247 Read ( Mmap ) ,
248+ #[ cfg( windows) ]
249+ Named ( NamedMmap ) ,
200250 }
201251
202252 impl MmapObj {
203253 fn as_slice ( & self ) -> & [ u8 ] {
204254 match self {
205255 MmapObj :: Read ( mmap) => & mmap[ ..] ,
206256 MmapObj :: Write ( mmap) => & mmap[ ..] ,
257+ #[ cfg( windows) ]
258+ MmapObj :: Named ( named) => unsafe {
259+ core:: slice:: from_raw_parts ( named. view_ptr , named. len )
260+ } ,
207261 }
208262 }
209263 }
@@ -276,7 +330,6 @@ mod mmap {
276330 #[ pyarg( any) ]
277331 length : isize ,
278332 #[ pyarg( any, default ) ]
279- #[ allow( dead_code) ]
280333 tagname : Option < PyObjectRef > ,
281334 #[ pyarg( any, default = AccessMode :: Default ) ]
282335 access : AccessMode ,
@@ -494,11 +547,22 @@ mod mmap {
494547 let mut map_size = args. validate_new_args ( vm) ?;
495548 let MmapNewArgs {
496549 fileno,
550+ tagname,
497551 access,
498552 offset,
499553 ..
500554 } = args;
501555
556+ // Parse tagname: None or a string
557+ let tag_str: Option < String > = match tagname {
558+ Some ( ref obj) if !vm. is_none ( obj) => {
559+ Some ( obj. try_to_value :: < String > ( vm) . map_err ( |_| {
560+ vm. new_type_error ( "tagname must be a string or None" . to_owned ( ) )
561+ } ) ?)
562+ }
563+ _ => None ,
564+ } ;
565+
502566 // Get file handle from fileno
503567 // fileno -1 or 0 means anonymous mapping
504568 let fh: Option < HANDLE > = if fileno != -1 && fileno != 0 {
@@ -595,6 +659,73 @@ mod mmap {
595659 }
596660 }
597661
662+ // When tagname is provided, use raw Win32 APIs for named shared memory
663+ if let Some ( ref tag) = tag_str {
664+ let ( fl_protect, desired_access) = match access {
665+ AccessMode :: Default | AccessMode :: Write => ( PAGE_READWRITE , FILE_MAP_WRITE ) ,
666+ AccessMode :: Read => ( PAGE_READONLY , FILE_MAP_READ ) ,
667+ AccessMode :: Copy => ( PAGE_WRITECOPY , FILE_MAP_COPY ) ,
668+ } ;
669+
670+ let fh = if let Some ( fh) = fh {
671+ // Close the duplicated handle - we'll use the original
672+ // file handle for CreateFileMappingW
673+ if duplicated_handle != INVALID_HANDLE_VALUE {
674+ unsafe { CloseHandle ( duplicated_handle) } ;
675+ }
676+ fh
677+ } else {
678+ INVALID_HANDLE_VALUE
679+ } ;
680+
681+ let tag_wide: Vec < u16 > = tag. encode_utf16 ( ) . chain ( core:: iter:: once ( 0 ) ) . collect ( ) ;
682+
683+ let total_size = ( offset as u64 ) + ( map_size as u64 ) ;
684+ let size_hi = ( total_size >> 32 ) as u32 ;
685+ let size_lo = total_size as u32 ;
686+
687+ let map_handle = unsafe {
688+ CreateFileMappingW (
689+ fh,
690+ core:: ptr:: null ( ) ,
691+ fl_protect,
692+ size_hi,
693+ size_lo,
694+ tag_wide. as_ptr ( ) ,
695+ )
696+ } ;
697+ if map_handle. is_null ( ) {
698+ return Err ( io:: Error :: last_os_error ( ) . to_pyexception ( vm) ) ;
699+ }
700+
701+ let off_hi = ( offset as u64 >> 32 ) as u32 ;
702+ let off_lo = offset as u32 ;
703+
704+ let view =
705+ unsafe { MapViewOfFile ( map_handle, desired_access, off_hi, off_lo, map_size) } ;
706+ if view. Value . is_null ( ) {
707+ unsafe { CloseHandle ( map_handle) } ;
708+ return Err ( io:: Error :: last_os_error ( ) . to_pyexception ( vm) ) ;
709+ }
710+
711+ let named = NamedMmap {
712+ map_handle,
713+ view_ptr : view. Value as * mut u8 ,
714+ len : map_size,
715+ } ;
716+
717+ return Ok ( Self {
718+ closed : AtomicCell :: new ( false ) ,
719+ mmap : PyMutex :: new ( Some ( MmapObj :: Named ( named) ) ) ,
720+ handle : AtomicCell :: new ( INVALID_HANDLE_VALUE as isize ) ,
721+ offset,
722+ size : AtomicCell :: new ( map_size) ,
723+ pos : AtomicCell :: new ( 0 ) ,
724+ exports : AtomicCell :: new ( 0 ) ,
725+ access,
726+ } ) ;
727+ }
728+
598729 let mut mmap_opt = MmapOptions :: new ( ) ;
599730 let mmap_opt = mmap_opt. offset ( offset as u64 ) . len ( map_size) ;
600731
@@ -719,6 +850,10 @@ mod mmap {
719850 match m. as_mut ( ) . expect ( "mmap closed or invalid" ) {
720851 MmapObj :: Read ( _) => panic ! ( "mmap can't modify a readonly memory map." ) ,
721852 MmapObj :: Write ( mmap) => & mut mmap[ ..] ,
853+ #[ cfg( windows) ]
854+ MmapObj :: Named ( named) => unsafe {
855+ core:: slice:: from_raw_parts_mut ( named. view_ptr , named. len )
856+ } ,
722857 }
723858 } )
724859 . into ( )
@@ -749,15 +884,19 @@ mod mmap {
749884 fn try_writable < R > (
750885 & self ,
751886 vm : & VirtualMachine ,
752- f : impl FnOnce ( & mut MmapMut ) -> R ,
887+ f : impl FnOnce ( & mut [ u8 ] ) -> R ,
753888 ) -> PyResult < R > {
754889 if matches ! ( self . access, AccessMode :: Read ) {
755890 return Err ( vm. new_type_error ( "mmap can't modify a readonly memory map." ) ) ;
756891 }
757892
758893 match self . check_valid ( vm) ?. deref_mut ( ) . as_mut ( ) . unwrap ( ) {
759- MmapObj :: Write ( mmap) => Ok ( f ( mmap) ) ,
760- _ => unreachable ! ( "already check" ) ,
894+ MmapObj :: Write ( mmap) => Ok ( f ( & mut mmap[ ..] ) ) ,
895+ #[ cfg( windows) ]
896+ MmapObj :: Named ( named) => Ok ( f ( unsafe {
897+ core:: slice:: from_raw_parts_mut ( named. view_ptr , named. len )
898+ } ) ) ,
899+ _ => unreachable ! ( "already checked" ) ,
761900 }
762901 }
763902
@@ -873,6 +1012,10 @@ mod mmap {
8731012 mmap. flush_range ( offset, size)
8741013 . map_err ( |e| e. to_pyexception ( vm) ) ?;
8751014 }
1015+ #[ cfg( windows) ]
1016+ MmapObj :: Named ( _) => {
1017+ // Named mmaps backed by page file don't need explicit flushing
1018+ }
8761019 }
8771020
8781021 Ok ( ( ) )
@@ -1067,10 +1210,7 @@ mod mmap {
10671210
10681211 // Copy data from old mmap to new mmap
10691212 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- } ;
1213+ let src = & old_mmap. as_slice ( ) [ ..copy_size] ;
10741214 new_mmap[ ..copy_size] . copy_from_slice ( src) ;
10751215 }
10761216
0 commit comments