@@ -67,6 +67,65 @@ def find_library(name):
6767 return fname
6868 return None
6969
70+ # Listing loaded DLLs on Windows relies on the following APIs:
71+ # https://learn.microsoft.com/windows/win32/api/psapi/nf-psapi-enumprocessmodules
72+ # https://learn.microsoft.com/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulefilenamew
73+ import ctypes
74+ from ctypes import wintypes
75+
76+ _kernel32 = ctypes .WinDLL ('kernel32' , use_last_error = True )
77+ _get_current_process = _kernel32 ["GetCurrentProcess" ]
78+ _get_current_process .restype = wintypes .HANDLE
79+
80+ _k32_get_module_file_name = _kernel32 ["GetModuleFileNameW" ]
81+ _k32_get_module_file_name .restype = wintypes .DWORD
82+ _k32_get_module_file_name .argtypes = (
83+ wintypes .HMODULE ,
84+ wintypes .LPWSTR ,
85+ wintypes .DWORD ,
86+ )
87+
88+ _psapi = ctypes .WinDLL ('psapi' , use_last_error = True )
89+ _enum_process_modules = _psapi ["EnumProcessModules" ]
90+ _enum_process_modules .restype = wintypes .BOOL
91+ _enum_process_modules .argtypes = (
92+ wintypes .HANDLE ,
93+ ctypes .POINTER (wintypes .HMODULE ),
94+ wintypes .DWORD ,
95+ wintypes .LPDWORD ,
96+ )
97+
98+ def _get_module_filename (module : wintypes .HMODULE ):
99+ name = (wintypes .WCHAR * 32767 )() # UNICODE_STRING_MAX_CHARS
100+ if _k32_get_module_file_name (module , name , len (name )):
101+ return name .value
102+ return None
103+
104+
105+ def _get_module_handles ():
106+ process = _get_current_process ()
107+ space_needed = wintypes .DWORD ()
108+ n = 1024
109+ while True :
110+ modules = (wintypes .HMODULE * n )()
111+ if not _enum_process_modules (process ,
112+ modules ,
113+ ctypes .sizeof (modules ),
114+ ctypes .byref (space_needed )):
115+ err = ctypes .get_last_error ()
116+ msg = ctypes .FormatError (err ).strip ()
117+ raise ctypes .WinError (err , f"EnumProcessModules failed: { msg } " )
118+ n = space_needed .value // ctypes .sizeof (wintypes .HMODULE )
119+ if n <= len (modules ):
120+ return modules [:n ]
121+
122+ def dllist ():
123+ """Return a list of loaded shared libraries in the current process."""
124+ modules = _get_module_handles ()
125+ libraries = [name for h in modules
126+ if (name := _get_module_filename (h )) is not None ]
127+ return libraries
128+
70129elif os .name == "posix" and sys .platform in {"darwin" , "ios" , "tvos" , "watchos" }:
71130 from ctypes .macholib .dyld import dyld_find as _dyld_find
72131 def find_library (name ):
@@ -80,6 +139,22 @@ def find_library(name):
80139 continue
81140 return None
82141
142+ # Listing loaded libraries on Apple systems relies on the following API:
143+ # https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/dyld.3.html
144+ import ctypes
145+
146+ _libc = ctypes .CDLL (find_library ("c" ))
147+ _dyld_get_image_name = _libc ["_dyld_get_image_name" ]
148+ _dyld_get_image_name .restype = ctypes .c_char_p
149+
150+ def dllist ():
151+ """Return a list of loaded shared libraries in the current process."""
152+ num_images = _libc ._dyld_image_count ()
153+ libraries = [os .fsdecode (name ) for i in range (num_images )
154+ if (name := _dyld_get_image_name (i )) is not None ]
155+
156+ return libraries
157+
83158elif sys .platform .startswith ("aix" ):
84159 # AIX has two styles of storing shared libraries
85160 # GNU auto_tools refer to these as svr4 and aix
@@ -341,6 +416,55 @@ def find_library(name):
341416 return _findSoname_ldconfig (name ) or \
342417 _get_soname (_findLib_gcc (name )) or _get_soname (_findLib_ld (name ))
343418
419+
420+ # Listing loaded libraries on other systems will try to use
421+ # functions common to Linux and a few other Unix-like systems.
422+ # See the following for several platforms' documentation of the same API:
423+ # https://man7.org/linux/man-pages/man3/dl_iterate_phdr.3.html
424+ # https://man.freebsd.org/cgi/man.cgi?query=dl_iterate_phdr
425+ # https://man.openbsd.org/dl_iterate_phdr
426+ # https://docs.oracle.com/cd/E88353_01/html/E37843/dl-iterate-phdr-3c.html
427+ if (os .name == "posix" and
428+ sys .platform not in {"darwin" , "ios" , "tvos" , "watchos" }):
429+ import ctypes
430+ if hasattr ((_libc := ctypes .CDLL (None )), "dl_iterate_phdr" ):
431+
432+ class _dl_phdr_info (ctypes .Structure ):
433+ _fields_ = [
434+ ("dlpi_addr" , ctypes .c_void_p ),
435+ ("dlpi_name" , ctypes .c_char_p ),
436+ ("dlpi_phdr" , ctypes .c_void_p ),
437+ ("dlpi_phnum" , ctypes .c_ushort ),
438+ ]
439+
440+ _dl_phdr_callback = ctypes .CFUNCTYPE (
441+ ctypes .c_int ,
442+ ctypes .POINTER (_dl_phdr_info ),
443+ ctypes .c_size_t ,
444+ ctypes .POINTER (ctypes .py_object ),
445+ )
446+
447+ @_dl_phdr_callback
448+ def _info_callback (info , _size , data ):
449+ libraries = data .contents .value
450+ name = os .fsdecode (info .contents .dlpi_name )
451+ libraries .append (name )
452+ return 0
453+
454+ _dl_iterate_phdr = _libc ["dl_iterate_phdr" ]
455+ _dl_iterate_phdr .argtypes = [
456+ _dl_phdr_callback ,
457+ ctypes .POINTER (ctypes .py_object ),
458+ ]
459+ _dl_iterate_phdr .restype = ctypes .c_int
460+
461+ def dllist ():
462+ """Return a list of loaded shared libraries in the current process."""
463+ libraries = []
464+ _dl_iterate_phdr (_info_callback ,
465+ ctypes .byref (ctypes .py_object (libraries )))
466+ return libraries
467+
344468################################################################
345469# test code
346470
@@ -384,5 +508,12 @@ def test():
384508 print (cdll .LoadLibrary ("libcrypt.so" ))
385509 print (find_library ("crypt" ))
386510
511+ try :
512+ dllist
513+ except NameError :
514+ print ('dllist() not available' )
515+ else :
516+ print (dllist ())
517+
387518if __name__ == "__main__" :
388519 test ()
0 commit comments