What is the difference between ctypes.CDLL() and ctypes.cdll.LoadLibrary()?

Question:

Both methods seem to work (for me), but it seems the CDLL() method returns an object that has a _handle attribute, which can be used to unload the library via ctypes.windll.kernel32.FreeLibrary() (at least on Windows – I don’t yet know how to do that on Linux).

What are the differences between these two methods – why would I choose one over the other?

Ultimately, my goal is to be able to load and unload libraries on both Windows on Linux (because I have a 3rd party library that seems to get into a broken state sometimes – I’m hoping unloading/reloading will reset it).

Asked By: nerdfever.com

||

Answers:

Everything is well explained in [Python.Docs]: ctypes – A foreign function library for Python:

class ctypes.CDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False, winmode=0)
       Instances of this class represent loaded shared libraries. Functions in these libraries use the standard C calling convention, and are assumed to return int.

class ctypes.LibraryLoader(dlltype)

LoadLibrary(name)
      Load a shared library into the process and return it. This method always returns a new instance of the library.

These prefabricated library loaders are available:

ctypes.cdll
      Creates CDLL instances.

So, the 2nd form is just a convenience wrapper, there’s absolutely no functional difference between them, as shown below:

  • Win:

    [cfati@CFATI-5510-0:e:WorkDevStackOverflowq067049436]> "e:WorkDevVEnvspy_pc064_03.10_test0Scriptspython.exe"
    Python 3.10.9 (tags/v3.10.9:1dd9be6, Dec  6 2022, 20:01:21) [MSC v.1934 64 bit (AMD64)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    >>>
    >>> import ctypes as cts
    >>>
    >>>
    >>> k32_1 = cts.CDLL("kernel32.dll")  # 1.
    >>> k32_21 = cts.cdll.LoadLibrary("kernel32.dll")  # 2.1.
    >>> k32_22 = cts.cdll.kernel32  # 2.2.
    >>>
    >>> k32_1, k32_21, k32_22
    (<CDLL 'kernel32.dll', handle 7fff59100000 at 0x2335c444ee0>, <CDLL 'kernel32.dll', handle 7fff59100000 at 0x2335b44bc10>, <CDLL 'kernel32', handle 7fff59100000 at 0x2335c45a790>)
    >>>
    >>> type(k32_1), type(k32_21), type(k32_22)
    (<class 'ctypes.CDLL'>, <class 'ctypes.CDLL'>, <class 'ctypes.CDLL'>)
    >>>
    >>> k32_1._handle == k32_21._handle == k32_22._handle
    True
    

    Technically, WinDLL (windll) should be used here, but since Python is 064bit (pc064), CDLL (cdll) is also fine

  • Nix:

    (qaic-env) [cfati@cfati-5510-0:/mnt/e/Work/Dev/StackOverflow/q067049436]> ll $(pwd)/../q074171783/*.so
    -rwxr-xr-x 1 cfati cfati 16376 Oct 23 22:37 /mnt/e/Work/Dev/StackOverflow/q067049436/../q074171783/dll00.so*
    -rwxr--r-- 1 cfati cfati  9728 Oct 23 22:40 /mnt/e/Work/Dev/StackOverflow/q067049436/../q074171783/dll00_wincopy.so*
    lrwxrwxrwx 1 cfati cfati     8 Jan 11 10:50 /mnt/e/Work/Dev/StackOverflow/q067049436/../q074171783/libdll00.so -> dll00.so*
    (qaic-env) [cfati@cfati-5510-0:/mnt/e/Work/Dev/StackOverflow/q067049436]>
    (qaic-env) [cfati@cfati-5510-0:/mnt/e/Work/Dev/StackOverflow/q067049436]> LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:$(pwd)/../q074171783 python
    Python 3.8.10 (default, Nov 14 2022, 12:59:47)
    [GCC 9.4.0] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>>
    >>> import ctypes as cts
    >>>
    >>>
    >>> ld0_1 = cts.CDLL("libdll00.so")  # 1.
    >>> ld0_21 = cts.cdll.LoadLibrary("libdll00.so")  # 2.1.
    >>> #ld0_22 = cts.cdll.libdll00  # 2.2.
    >>>
    >>> ld0_1, ld0_21
    (<CDLL 'libdll00.so', handle 11f9510 at 0x7fbb9576cca0>, <CDLL 'libdll00.so', handle 11f9510 at 0x7fbb95754eb0>)
    >>>
    >>> type(ld0_1), type(ld0_21)
    (<class 'ctypes.CDLL'>, <class 'ctypes.CDLL'>)
    >>>
    >>> ld0_1._handle == ld0_21._handle
    True
    

    #2.2. (commented) doesn’t work here, since the file extension must be part of the string passed to [Man7]: DLOPEN (3) (otherwise it doesn’t find it – maybe this could be worked around a Ld script?).
    The closest thing that I could find is cts.cdll["libdll00.so"] (but it still looks more related to the other options)

Use whatever suits you best.
2nd form (#2.2.) is shorter (I suppose this is its purpose).
#1. and #2.1. are the same (#2.1. is probably more explanatory (as it has LoadLibrary)) and they allow you to load a library from a custom path, or with an extension different than the default. Personally, #1. is the one I prefer.

For more details, you can take a look at [GitHub]: python/cpython – (master) cpython/Lib/ctypes/__init__.py, especially the LibraryLoader at implementation which (cdll actually is, and) is easy to understand.

Just a heads-up (probably you already know what you’re getting into): loading and unloading libraries can sometimes be tricky:

You might also want to check [SO]: C function called from Python via ctypes returns incorrect value (@CristiFati’s answer) for a common pitfall encountered when calling functions via CTypes.

Answered By: CristiFati
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.