@@ -58,7 +58,11 @@ fn get_python_ranges() -> Vec<Range> {
5858 {
5959 parse_dyld_images ( )
6060 }
61- #[ cfg( not( any( target_os = "linux" , target_os = "macos" ) ) ) ]
61+ #[ cfg( target_os = "windows" ) ]
62+ {
63+ parse_windows_modules ( )
64+ }
65+ #[ cfg( not( any( target_os = "linux" , target_os = "macos" , target_os = "windows" ) ) ) ]
6266 {
6367 Default :: default ( )
6468 }
@@ -178,6 +182,98 @@ fn parse_dyld_images() -> Vec<Range> {
178182 ranges
179183}
180184
185+ /// Parse loaded modules to find libpython and .pyd/.dll ranges
186+ #[ cfg( target_os = "windows" ) ]
187+ fn parse_windows_modules ( ) -> Vec < Range > {
188+ use std:: mem:: MaybeUninit ;
189+ use std:: ptr;
190+
191+ use winapi:: shared:: minwindef:: DWORD ;
192+ use winapi:: shared:: minwindef:: FALSE ;
193+ use winapi:: shared:: minwindef:: HMODULE ;
194+ use winapi:: shared:: minwindef:: MAX_PATH ;
195+ use winapi:: um:: processthreadsapi:: GetCurrentProcess ;
196+ use winapi:: um:: psapi:: EnumProcessModules ;
197+ use winapi:: um:: psapi:: GetModuleFileNameExW ;
198+ use winapi:: um:: psapi:: GetModuleInformation ;
199+ use winapi:: um:: psapi:: MODULEINFO ;
200+
201+ let mut ranges = Vec :: new ( ) ;
202+
203+ let process = unsafe { GetCurrentProcess ( ) } ;
204+
205+ // First, get the number of modules
206+ let mut bytes_needed: DWORD = 0 ;
207+ if unsafe { EnumProcessModules ( process, ptr:: null_mut ( ) , 0 , & mut bytes_needed) } == FALSE {
208+ return ranges;
209+ }
210+
211+ let module_count = bytes_needed as usize / std:: mem:: size_of :: < HMODULE > ( ) ;
212+ if module_count == 0 {
213+ return ranges;
214+ }
215+
216+ // Allocate buffer for module handles
217+ let mut modules: Vec < HMODULE > = vec ! [ ptr:: null_mut( ) ; module_count] ;
218+
219+ if unsafe {
220+ EnumProcessModules (
221+ process,
222+ modules. as_mut_ptr ( ) ,
223+ bytes_needed,
224+ & mut bytes_needed,
225+ )
226+ } == FALSE
227+ {
228+ return ranges;
229+ }
230+
231+ // Iterate through modules
232+ for & module in & modules {
233+ if module. is_null ( ) {
234+ continue ;
235+ }
236+
237+ // Get module filename
238+ let mut filename: [ u16 ; MAX_PATH ] = [ 0 ; MAX_PATH ] ;
239+ let len = unsafe {
240+ GetModuleFileNameExW ( process, module, filename. as_mut_ptr ( ) , MAX_PATH as DWORD )
241+ } ;
242+
243+ if len == 0 {
244+ continue ;
245+ }
246+
247+ let path = String :: from_utf16_lossy ( & filename[ ..len as usize ] ) ;
248+
249+ if !is_python_library_path ( & path) {
250+ continue ;
251+ }
252+
253+ // Get module information (base address and size)
254+ let mut mod_info: MaybeUninit < MODULEINFO > = MaybeUninit :: uninit ( ) ;
255+ if unsafe {
256+ GetModuleInformation (
257+ process,
258+ module,
259+ mod_info. as_mut_ptr ( ) ,
260+ std:: mem:: size_of :: < MODULEINFO > ( ) as DWORD ,
261+ )
262+ } == FALSE
263+ {
264+ continue ;
265+ }
266+
267+ let mod_info = unsafe { mod_info. assume_init ( ) } ;
268+ let start = mod_info. lpBaseOfDll as usize ;
269+ let end = start + mod_info. SizeOfImage as usize ;
270+ ranges. push ( Range { start, end } ) ;
271+ }
272+
273+ ranges. sort ( ) ;
274+ ranges
275+ }
276+
181277/// Check if a pathname is a Python library or extension
182278fn is_python_library_path ( path : & str ) -> bool {
183279 // Linux examples:
@@ -189,5 +285,31 @@ fn is_python_library_path(path: &str) -> bool {
189285 // /usr/local/Cellar/python@3.11/3.11.4/Frameworks/Python.framework/Versions/3.11/Python
190286 // /Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/lib-dynload/_json.cpython-311-darwin.so
191287 // /opt/homebrew/lib/python3.11/site-packages/numpy/.dylibs/libopenblas64_.0.dylib
192- path. contains ( "/libpython" ) || path. contains ( "/python" ) || path. contains ( "/Python.framework/" )
288+ //
289+ // Windows examples:
290+ // C:\Python311\python311.dll
291+ // C:\Python311\python3.dll
292+ // C:\Python311\DLLs\_socket.pyd
293+ // C:\Users\...\AppData\Local\Programs\Python\Python311\python311.dll
294+ // C:\Users\...\site-packages\numpy\core\_multiarray_umath.cp311-win_amd64.pyd
295+
296+ // Unix-style paths
297+ if path. contains ( "/libpython" )
298+ || path. contains ( "/python" )
299+ || path. contains ( "/Python.framework/" )
300+ {
301+ return true ;
302+ }
303+
304+ // Windows-style paths (use backslash)
305+ if path. contains ( "\\ python" ) || path. contains ( "\\ Python" ) {
306+ return true ;
307+ }
308+
309+ // .pyd files are Python extension modules on Windows
310+ if path. ends_with ( ".pyd" ) {
311+ return true ;
312+ }
313+
314+ false
193315}
0 commit comments