module instead of using :func:`~ctypes.util.find_library` to locate the library at runtime.
+.. _ctypes-listing-loaded-shared-libraries:
+
+Listing loaded shared libraries
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+When writing code that relies on code loaded from shared libraries, it can be
+useful to know which shared libraries have already been loaded into the current
+process.
+
+The :mod:`!ctypes.util` module provides the :func:`~ctypes.util.dllist` function,
+which calls the different APIs provided by the various platforms to help determine
+which shared libraries have already been loaded into the current process.
+
+The exact output of this function will be system dependent. On most platforms,
+the first entry of this list represents the current process itself, which may
+be an empty string.
+For example, on glibc-based Linux, the return may look like::
+
+ >>> from ctypes.util import dllist
+ >>> dllist()
+ ['', 'linux-vdso.so.1', '/lib/x86_64-linux-gnu/libm.so.6', '/lib/x86_64-linux-gnu/libc.so.6', ... ]
+
.. _ctypes-loading-shared-libraries:
Loading shared libraries
.. availability:: Windows
+.. function:: dllist()
+ :module: ctypes.util
+
+ Try to provide a list of paths of the shared libraries loaded into the current
+ process. These paths are not normalized or processed in any way. The function
+ can raise :exc:`OSError` if the underlying platform APIs fail.
+ The exact functionality is system dependent.
+
+ On most platforms, the first element of the list represents the current
+ executable file. It may be an empty string.
+
+ .. availability:: Windows, macOS, iOS, glibc, BSD libc, musl
+ .. versionadded:: next
+
.. function:: FormatError([code])
Returns a textual description of the error code *code*. If no error code is
return fname
return None
+ # Listing loaded DLLs on Windows relies on the following APIs:
+ # https://learn.microsoft.com/windows/win32/api/psapi/nf-psapi-enumprocessmodules
+ # https://learn.microsoft.com/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulefilenamew
+ import ctypes
+ from ctypes import wintypes
+
+ _kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
+ _get_current_process = _kernel32["GetCurrentProcess"]
+ _get_current_process.restype = wintypes.HANDLE
+
+ _k32_get_module_file_name = _kernel32["GetModuleFileNameW"]
+ _k32_get_module_file_name.restype = wintypes.DWORD
+ _k32_get_module_file_name.argtypes = (
+ wintypes.HMODULE,
+ wintypes.LPWSTR,
+ wintypes.DWORD,
+ )
+
+ _psapi = ctypes.WinDLL('psapi', use_last_error=True)
+ _enum_process_modules = _psapi["EnumProcessModules"]
+ _enum_process_modules.restype = wintypes.BOOL
+ _enum_process_modules.argtypes = (
+ wintypes.HANDLE,
+ ctypes.POINTER(wintypes.HMODULE),
+ wintypes.DWORD,
+ wintypes.LPDWORD,
+ )
+
+ def _get_module_filename(module: wintypes.HMODULE):
+ name = (wintypes.WCHAR * 32767)() # UNICODE_STRING_MAX_CHARS
+ if _k32_get_module_file_name(module, name, len(name)):
+ return name.value
+ return None
+
+
+ def _get_module_handles():
+ process = _get_current_process()
+ space_needed = wintypes.DWORD()
+ n = 1024
+ while True:
+ modules = (wintypes.HMODULE * n)()
+ if not _enum_process_modules(process,
+ modules,
+ ctypes.sizeof(modules),
+ ctypes.byref(space_needed)):
+ err = ctypes.get_last_error()
+ msg = ctypes.FormatError(err).strip()
+ raise ctypes.WinError(err, f"EnumProcessModules failed: {msg}")
+ n = space_needed.value // ctypes.sizeof(wintypes.HMODULE)
+ if n <= len(modules):
+ return modules[:n]
+
+ def dllist():
+ """Return a list of loaded shared libraries in the current process."""
+ modules = _get_module_handles()
+ libraries = [name for h in modules
+ if (name := _get_module_filename(h)) is not None]
+ return libraries
+
elif os.name == "posix" and sys.platform in {"darwin", "ios", "tvos", "watchos"}:
from ctypes.macholib.dyld import dyld_find as _dyld_find
def find_library(name):
continue
return None
+ # Listing loaded libraries on Apple systems relies on the following API:
+ # https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/dyld.3.html
+ import ctypes
+
+ _libc = ctypes.CDLL(find_library("c"))
+ _dyld_get_image_name = _libc["_dyld_get_image_name"]
+ _dyld_get_image_name.restype = ctypes.c_char_p
+
+ def dllist():
+ """Return a list of loaded shared libraries in the current process."""
+ num_images = _libc._dyld_image_count()
+ libraries = [os.fsdecode(name) for i in range(num_images)
+ if (name := _dyld_get_image_name(i)) is not None]
+
+ return libraries
+
elif sys.platform.startswith("aix"):
# AIX has two styles of storing shared libraries
# GNU auto_tools refer to these as svr4 and aix
return _findSoname_ldconfig(name) or \
_get_soname(_findLib_gcc(name)) or _get_soname(_findLib_ld(name))
+
+# Listing loaded libraries on other systems will try to use
+# functions common to Linux and a few other Unix-like systems.
+# See the following for several platforms' documentation of the same API:
+# https://man7.org/linux/man-pages/man3/dl_iterate_phdr.3.html
+# https://man.freebsd.org/cgi/man.cgi?query=dl_iterate_phdr
+# https://man.openbsd.org/dl_iterate_phdr
+# https://docs.oracle.com/cd/E88353_01/html/E37843/dl-iterate-phdr-3c.html
+if (os.name == "posix" and
+ sys.platform not in {"darwin", "ios", "tvos", "watchos"}):
+ import ctypes
+ if hasattr((_libc := ctypes.CDLL(None)), "dl_iterate_phdr"):
+
+ class _dl_phdr_info(ctypes.Structure):
+ _fields_ = [
+ ("dlpi_addr", ctypes.c_void_p),
+ ("dlpi_name", ctypes.c_char_p),
+ ("dlpi_phdr", ctypes.c_void_p),
+ ("dlpi_phnum", ctypes.c_ushort),
+ ]
+
+ _dl_phdr_callback = ctypes.CFUNCTYPE(
+ ctypes.c_int,
+ ctypes.POINTER(_dl_phdr_info),
+ ctypes.c_size_t,
+ ctypes.POINTER(ctypes.py_object),
+ )
+
+ @_dl_phdr_callback
+ def _info_callback(info, _size, data):
+ libraries = data.contents.value
+ name = os.fsdecode(info.contents.dlpi_name)
+ libraries.append(name)
+ return 0
+
+ _dl_iterate_phdr = _libc["dl_iterate_phdr"]
+ _dl_iterate_phdr.argtypes = [
+ _dl_phdr_callback,
+ ctypes.POINTER(ctypes.py_object),
+ ]
+ _dl_iterate_phdr.restype = ctypes.c_int
+
+ def dllist():
+ """Return a list of loaded shared libraries in the current process."""
+ libraries = []
+ _dl_iterate_phdr(_info_callback,
+ ctypes.byref(ctypes.py_object(libraries)))
+ return libraries
+
################################################################
# test code
print(cdll.LoadLibrary("libcrypt.so"))
print(find_library("crypt"))
+ try:
+ dllist
+ except NameError:
+ print('dllist() not available')
+ else:
+ print(dllist())
+
if __name__ == "__main__":
test()
--- /dev/null
+import os
+import sys
+import unittest
+from ctypes import CDLL
+import ctypes.util
+from test.support import import_helper
+
+
+WINDOWS = os.name == "nt"
+APPLE = sys.platform in {"darwin", "ios", "tvos", "watchos"}
+
+if WINDOWS:
+ KNOWN_LIBRARIES = ["KERNEL32.DLL"]
+elif APPLE:
+ KNOWN_LIBRARIES = ["libSystem.B.dylib"]
+else:
+ # trickier than it seems, because libc may not be present
+ # on musl systems, and sometimes goes by different names.
+ # However, ctypes itself loads libffi
+ KNOWN_LIBRARIES = ["libc.so", "libffi.so"]
+
+
+@unittest.skipUnless(
+ hasattr(ctypes.util, "dllist"),
+ "ctypes.util.dllist is not available on this platform",
+)
+class ListSharedLibraries(unittest.TestCase):
+
+ def test_lists_system(self):
+ dlls = ctypes.util.dllist()
+
+ self.assertGreater(len(dlls), 0, f"loaded={dlls}")
+ self.assertTrue(
+ any(lib in dll for dll in dlls for lib in KNOWN_LIBRARIES), f"loaded={dlls}"
+ )
+
+ def test_lists_updates(self):
+ dlls = ctypes.util.dllist()
+
+ # this test relies on being able to import a library which is
+ # not already loaded.
+ # If it is (e.g. by a previous test in the same process), we skip
+ if any("_ctypes_test" in dll for dll in dlls):
+ self.skipTest("Test library is already loaded")
+
+ _ctypes_test = import_helper.import_module("_ctypes_test")
+ test_module = CDLL(_ctypes_test.__file__)
+ dlls2 = ctypes.util.dllist()
+ self.assertIsNotNone(dlls2)
+
+ dlls1 = set(dlls)
+ dlls2 = set(dlls2)
+
+ self.assertGreater(dlls2, dlls1, f"newly loaded libraries: {dlls2 - dlls1}")
+ self.assertTrue(any("_ctypes_test" in dll for dll in dlls2), f"loaded={dlls2}")
+
+
+if __name__ == "__main__":
+ unittest.main()