https://github.com/python/cpython/commit/d64f83d07bf587dfd6e4ff9ad9d44541064d5f1c
commit: d64f83d07bf587dfd6e4ff9ad9d44541064d5f1c
branch: main
author: Petr Viktorin <[email protected]>
committer: encukou <[email protected]>
date: 2026-03-09T15:02:06+01:00
summary:

gh-78773: Improve ctypes dynamic library loading docs  (GH-145313)

files:
M Doc/library/ctypes.rst

diff --git a/Doc/library/ctypes.rst b/Doc/library/ctypes.rst
index c23e81e29df0f5..fcbe2122d9f1a7 100644
--- a/Doc/library/ctypes.rst
+++ b/Doc/library/ctypes.rst
@@ -20,10 +20,6 @@ used to wrap these libraries in pure Python.
 ctypes tutorial
 ---------------
 
-Note: The code samples in this tutorial use :mod:`doctest` to make sure that
-they actually work.  Since some code samples behave differently under Linux,
-Windows, or macOS, they contain doctest directives in comments.
-
 Note: Some code samples reference the ctypes :class:`c_int` type.  On platforms
 where ``sizeof(long) == sizeof(int)`` it is an alias to :class:`c_long`.
 So, you should not be confused if :class:`c_long` is printed if you would 
expect
@@ -34,13 +30,16 @@ So, you should not be confused if :class:`c_long` is 
printed if you would expect
 Loading dynamic link libraries
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-:mod:`!ctypes` exports the *cdll*, and on Windows *windll* and *oledll*
+:mod:`!ctypes` exports the :py:data:`~ctypes.cdll`, and on Windows
+:py:data:`~ctypes.windll` and :py:data:`~ctypes.oledll`
 objects, for loading dynamic link libraries.
 
-You load libraries by accessing them as attributes of these objects. *cdll*
-loads libraries which export functions using the standard ``cdecl`` calling
-convention, while *windll* libraries call functions using the ``stdcall``
-calling convention. *oledll* also uses the ``stdcall`` calling convention, and
+You load libraries by accessing them as attributes of these objects.
+:py:data:`!cdll` loads libraries which export functions using the
+standard ``cdecl`` calling convention, while :py:data:`!windll`
+libraries call functions using the ``stdcall``
+calling convention.
+:py:data:`~oledll` also uses the ``stdcall`` calling convention, and
 assumes the functions return a Windows :c:type:`!HRESULT` error code. The error
 code is used to automatically raise an :class:`OSError` exception when the
 function call fails.
@@ -70,11 +69,13 @@ Windows appends the usual ``.dll`` file suffix 
automatically.
     being used by Python. Where possible, use native Python functionality,
     or else import and use the ``msvcrt`` module.
 
-On Linux, it is required to specify the filename *including* the extension to
+Other systems require the filename *including* the extension to
 load a library, so attribute access can not be used to load libraries. Either 
the
 :meth:`~LibraryLoader.LoadLibrary` method of the dll loaders should be used,
-or you should load the library by creating an instance of CDLL by calling
-the constructor::
+or you should load the library by creating an instance of :py:class:`CDLL`
+by calling the constructor.
+
+For example, on Linux::
 
    >>> cdll.LoadLibrary("libc.so.6")  # doctest: +LINUX
    <CDLL 'libc.so.6', handle ... at ...>
@@ -83,7 +84,14 @@ the constructor::
    <CDLL 'libc.so.6', handle ... at ...>
    >>>
 
-.. XXX Add section for macOS.
+On macOS::
+
+   >>> cdll.LoadLibrary("libc.dylib")  # doctest: +MACOS
+   <CDLL 'libc.dylib', handle ... at ...>
+   >>> libc = CDLL("libc.dylib")       # doctest: +MACOS
+   >>> libc                            # doctest: +MACOS
+   <CDLL 'libc.dylib', handle ... at ...>
+
 
 
 .. _ctypes-accessing-functions-from-loaded-dlls:
@@ -1456,14 +1464,82 @@ Loading shared libraries
 ^^^^^^^^^^^^^^^^^^^^^^^^
 
 There are several ways to load shared libraries into the Python process.  One
-way is to instantiate one of the following classes:
+way is to instantiate :py:class:`CDLL` or one of its subclasses:
 
 
 .. class:: CDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, 
use_last_error=False, winmode=None)
 
-   Instances of this class represent loaded shared libraries. Functions in 
these
-   libraries use the standard C calling convention, and are assumed to return
-   :c:expr:`int`.
+   Represents a loaded shared library.
+
+   Functions in this library use the standard C calling convention, and are
+   assumed to return :c:expr:`int`.
+   The Python :term:`global interpreter lock` is released before calling any
+   function exported by these libraries, and reacquired afterwards.
+   For different function behavior, use a subclass: :py:class:`~ctypes.OleDLL`,
+   :py:class:`~ctypes.WinDLL`, or :py:class:`~ctypes.PyDLL`.
+
+   If you have an existing :py:attr:`handle <ctypes.CDLL._handle>` to an 
already
+   loaded shared library, it can be passed as the *handle* argument to wrap
+   the opened library in a new :py:class:`!CDLL` object.
+   In this case, *name* is only used to set the :py:attr:`~ctypes.CDLL._name`
+   attribute, but it may be adjusted and/or validated.
+
+   If *handle* is ``None``, the underlying platform's :manpage:`dlopen(3)` or
+   :c:func:`!LoadLibrary` function is used to load the library into
+   the process, and to get a handle to it.
+
+   *name* is the pathname of the shared library to open.
+   If *name* does not contain a path separator, the library is found
+   in a platform-specific way.
+
+   On non-Windows systems, *name* can  be ``None``. In this case,
+   :c:func:`!dlopen` is called with ``NULL``, which opens the main program
+   as a "library".
+   (Some systems do the same is *name* is empty; ``None``/``NULL`` is more
+   portable.)
+
+   .. admonition:: CPython implementation detail
+
+      Since CPython is linked to ``libc``, a ``None`` *name* is often used
+      to access the C standard library::
+
+         >>> printf = ctypes.CDLL(None).printf
+         >>> printf.argtypes = [ctypes.c_char_p]
+         >>> printf(b"hello\n")
+         hello
+         6
+
+      To access the Python C API, prefer :py:data:`ctypes.pythonapi` which
+      works across platforms.
+
+   The *mode* parameter can be used to specify how the library is loaded.  For
+   details, consult the :manpage:`dlopen(3)` manpage.  On Windows, *mode* is
+   ignored.  On posix systems, RTLD_NOW is always added, and is not
+   configurable.
+
+   The *use_errno* parameter, when set to true, enables a ctypes mechanism that
+   allows accessing the system :data:`errno` error number in a safe way.
+   :mod:`!ctypes` maintains a thread-local copy of the system's :data:`errno`
+   variable; if you call foreign functions created with ``use_errno=True`` 
then the
+   :data:`errno` value before the function call is swapped with the ctypes 
private
+   copy, the same happens immediately after the function call.
+
+   The function :func:`ctypes.get_errno` returns the value of the ctypes 
private
+   copy, and the function :func:`ctypes.set_errno` changes the ctypes private 
copy
+   to a new value and returns the former value.
+
+   The *use_last_error* parameter, when set to true, enables the same 
mechanism for
+   the Windows error code which is managed by the :func:`GetLastError` and
+   :func:`!SetLastError` Windows API functions; :func:`ctypes.get_last_error` 
and
+   :func:`ctypes.set_last_error` are used to request and change the ctypes 
private
+   copy of the windows error code.
+
+   The *winmode* parameter is used on Windows to specify how the library is 
loaded
+   (since *mode* is ignored). It takes any value that is valid for the Win32 
API
+   ``LoadLibraryEx`` flags parameter. When omitted, the default is to use the
+   flags that result in the most secure DLL load, which avoids issues such as 
DLL
+   hijacking. Passing the full path to the DLL is the safest way to ensure the
+   correct library and dependencies are loaded.
 
    On Windows creating a :class:`CDLL` instance may fail even if the DLL name
    exists. When a dependent DLL of the loaded DLL is not found, a
@@ -1475,20 +1551,47 @@ way is to instantiate one of the following classes:
    DLLs and determine which one is not found using Windows debugging and
    tracing tools.
 
+   .. seealso::
+
+      `Microsoft DUMPBIN tool 
<https://learn.microsoft.com/en-us/cpp/build/reference/dumpbin-reference?view=msvc-170>`_
+      -- A tool to find DLL dependents.
+
+   .. versionchanged:: 3.8
+      Added *winmode* parameter.
+
    .. versionchanged:: 3.12
 
       The *name* parameter can now be a :term:`path-like object`.
 
-.. seealso::
+   Instances of this class have no public methods.  Functions exported by the
+   shared library can be accessed as attributes or by index.  Please note that
+   accessing the function through an attribute caches the result and therefore
+   accessing it repeatedly returns the same object each time.  On the other 
hand,
+   accessing it through an index returns a new object each time::
+
+      >>> from ctypes import CDLL
+      >>> libc = CDLL("libc.so.6")  # On Linux
+      >>> libc.time == libc.time
+      True
+      >>> libc['time'] == libc['time']
+      False
+
+   The following public attributes are available. Their name starts with an
+   underscore to not clash with exported function names:
+
+   .. attribute:: _handle
+
+      The system handle used to access the library.
 
-    `Microsoft DUMPBIN tool 
<https://docs.microsoft.com/cpp/build/reference/dependents>`_
-    -- A tool to find DLL dependents.
+   .. attribute:: _name
 
+      The name of the library passed in the constructor.
 
-.. class:: OleDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, 
use_last_error=False, winmode=None)
+.. class:: OleDLL
 
-   Instances of this class represent loaded shared libraries,
-   functions in these libraries use the ``stdcall`` calling convention, and are
+   See :py:class:`~ctypes.CDLL`, the superclass, for common information.
+
+   Functions in this library use the ``stdcall`` calling convention, and are
    assumed to return the windows specific :class:`HRESULT` code.  
:class:`HRESULT`
    values contain information specifying whether the function call failed or
    succeeded, together with additional error code.  If the return value 
signals a
@@ -1500,133 +1603,51 @@ way is to instantiate one of the following classes:
       :exc:`WindowsError` used to be raised,
       which is now an alias of :exc:`OSError`.
 
-   .. versionchanged:: 3.12
-
-      The *name* parameter can now be a :term:`path-like object`.
 
+.. class:: WinDLL
 
-.. class:: WinDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, 
use_last_error=False, winmode=None)
+   See :py:class:`~ctypes.CDLL`, the superclass, for common information.
 
-   Instances of this class represent loaded shared libraries,
-   functions in these libraries use the ``stdcall`` calling convention, and are
+   Functions in these libraries use the ``stdcall`` calling convention, and are
    assumed to return :c:expr:`int` by default.
 
    .. availability:: Windows
 
-   .. versionchanged:: 3.12
+.. class:: PyDLL
 
-      The *name* parameter can now be a :term:`path-like object`.
+   See :py:class:`~ctypes.CDLL`, the superclass, for common information.
 
-
-The Python :term:`global interpreter lock` is released before calling any
-function exported by these libraries, and reacquired afterwards.
-
-
-.. class:: PyDLL(name, mode=DEFAULT_MODE, handle=None)
-
-   Instances of this class behave like :class:`CDLL` instances, except that the
+   When functions in this library are called, the
    Python GIL is *not* released during the function call, and after the 
function
    execution the Python error flag is checked. If the error flag is set, a 
Python
    exception is raised.
 
-   Thus, this is only useful to call Python C api functions directly.
-
-   .. versionchanged:: 3.12
-
-      The *name* parameter can now be a :term:`path-like object`.
-
-All these classes can be instantiated by calling them with at least one
-argument, the pathname of the shared library.  If you have an existing handle 
to
-an already loaded shared library, it can be passed as the ``handle`` named
-parameter, otherwise the underlying platform's :c:func:`!dlopen` or
-:c:func:`!LoadLibrary` function is used to load the library into
-the process, and to get a handle to it.
-
-The *mode* parameter can be used to specify how the library is loaded.  For
-details, consult the :manpage:`dlopen(3)` manpage.  On Windows, *mode* is
-ignored.  On posix systems, RTLD_NOW is always added, and is not
-configurable.
-
-The *use_errno* parameter, when set to true, enables a ctypes mechanism that
-allows accessing the system :data:`errno` error number in a safe way.
-:mod:`!ctypes` maintains a thread-local copy of the system's :data:`errno`
-variable; if you call foreign functions created with ``use_errno=True`` then 
the
-:data:`errno` value before the function call is swapped with the ctypes private
-copy, the same happens immediately after the function call.
-
-The function :func:`ctypes.get_errno` returns the value of the ctypes private
-copy, and the function :func:`ctypes.set_errno` changes the ctypes private copy
-to a new value and returns the former value.
-
-The *use_last_error* parameter, when set to true, enables the same mechanism 
for
-the Windows error code which is managed by the :func:`GetLastError` and
-:func:`!SetLastError` Windows API functions; :func:`ctypes.get_last_error` and
-:func:`ctypes.set_last_error` are used to request and change the ctypes private
-copy of the windows error code.
-
-The *winmode* parameter is used on Windows to specify how the library is loaded
-(since *mode* is ignored). It takes any value that is valid for the Win32 API
-``LoadLibraryEx`` flags parameter. When omitted, the default is to use the
-flags that result in the most secure DLL load, which avoids issues such as DLL
-hijacking. Passing the full path to the DLL is the safest way to ensure the
-correct library and dependencies are loaded.
-
-.. versionchanged:: 3.8
-   Added *winmode* parameter.
+   Thus, this is only useful to call Python C API functions directly.
 
 
 .. data:: RTLD_GLOBAL
-   :noindex:
 
    Flag to use as *mode* parameter.  On platforms where this flag is not 
available,
    it is defined as the integer zero.
 
 
 .. data:: RTLD_LOCAL
-   :noindex:
 
    Flag to use as *mode* parameter.  On platforms where this is not available, 
it
    is the same as *RTLD_GLOBAL*.
 
 
 .. data:: DEFAULT_MODE
-   :noindex:
 
    The default mode which is used to load shared libraries.  On OSX 10.3, this 
is
    *RTLD_GLOBAL*, otherwise it is the same as *RTLD_LOCAL*.
 
-Instances of these classes have no public methods.  Functions exported by the
-shared library can be accessed as attributes or by index.  Please note that
-accessing the function through an attribute caches the result and therefore
-accessing it repeatedly returns the same object each time.  On the other hand,
-accessing it through an index returns a new object each time::
-
-   >>> from ctypes import CDLL
-   >>> libc = CDLL("libc.so.6")  # On Linux
-   >>> libc.time == libc.time
-   True
-   >>> libc['time'] == libc['time']
-   False
-
-The following public attributes are available, their name starts with an
-underscore to not clash with exported function names:
-
-
-.. attribute:: PyDLL._handle
-
-   The system handle used to access the library.
-
-
-.. attribute:: PyDLL._name
-
-   The name of the library passed in the constructor.
 
 Shared libraries can also be loaded by using one of the prefabricated objects,
 which are instances of the :class:`LibraryLoader` class, either by calling the
 :meth:`~LibraryLoader.LoadLibrary` method, or by retrieving the library as
 attribute of the loader instance.
 
-
 .. class:: LibraryLoader(dlltype)
 
    Class which loads shared libraries.  *dlltype* should be one of the
@@ -1645,13 +1666,11 @@ attribute of the loader instance.
 These prefabricated library loaders are available:
 
 .. data:: cdll
-   :noindex:
 
    Creates :class:`CDLL` instances.
 
 
 .. data:: windll
-   :noindex:
 
    Creates :class:`WinDLL` instances.
 
@@ -1659,7 +1678,6 @@ These prefabricated library loaders are available:
 
 
 .. data:: oledll
-   :noindex:
 
    Creates :class:`OleDLL` instances.
 
@@ -1667,7 +1685,6 @@ These prefabricated library loaders are available:
 
 
 .. data:: pydll
-   :noindex:
 
    Creates :class:`PyDLL` instances.
 
@@ -1676,7 +1693,6 @@ For accessing the C Python api directly, a ready-to-use 
Python shared library
 object is available:
 
 .. data:: pythonapi
-   :noindex:
 
    An instance of :class:`PyDLL` that exposes Python C API functions as
    attributes.  Note that all these functions are assumed to return C

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to