【问题标题】:How can I unload a DLL using ctypes in Python?如何在 Python 中使用 ctypes 卸载 DLL?
【发布时间】:2010-09-26 10:44:08
【问题描述】:

我正在使用 ctypes 在 Python 中加载 DLL。这很好用。

现在我们希望能够在运行时重新加载该 DLL。

直接的方法似乎是: 1.卸载DLL 2.加载DLL

很遗憾,我不确定卸载 DLL 的正确方法是什么。

_ctypes.FreeLibrary 可用,但私有。

还有其他方法可以卸载 DLL 吗?

【问题讨论】:

  • 你找到比我丑陋的方法更好的答案了吗?如果不是,您应该在他们的邮件列表中询问,如果它不存在,请报告错误。 'del' 应该调用函数来释放资源!

标签: python dll ctypes


【解决方案1】:

为了完全的交叉兼容性:我为每个平台维护了一个各种dlclose() 等效项的列表,以及从哪个库中获取它们。这是一个有点长的列表,但您可以随意复制/粘贴它。

import sys
import ctypes
import platform

OS = platform.system()

if OS == "Windows":  # pragma: Windows
    dll_close = ctypes.windll.kernel32.FreeLibrary

elif OS == "Darwin":
    try:
        try:
            # macOS 11 (Big Sur). Possibly also later macOS 10s.
            stdlib = ctypes.CDLL("libc.dylib")
        except OSError:
            stdlib = ctypes.CDLL("libSystem")
    except OSError:
        # Older macOSs. Not only is the name inconsistent but it's
        # not even in PATH.
        stdlib = ctypes.CDLL("/usr/lib/system/libsystem_c.dylib")
    dll_close = stdlib.dlclose

elif OS == "Linux":
    try:
        stdlib = ctypes.CDLL("")
    except OSError:
        # Alpine Linux.
        stdlib = ctypes.CDLL("libc.so")
    dll_close = stdlib.dlclose

elif sys.platform == "msys":
    # msys can also use `ctypes.CDLL("kernel32.dll").FreeLibrary()`. Not sure
    # if or what the difference is.
    stdlib = ctypes.CDLL("msys-2.0.dll")
    dll_close = stdlib.dlclose

elif sys.platform == "cygwin":
    stdlib = ctypes.CDLL("cygwin1.dll")
    dll_close = stdlib.dlclose

elif OS == "FreeBSD":
    # FreeBSD uses `/usr/lib/libc.so.7` where `7` is another version number.
    # It is not in PATH but using its name instead of its path is somehow the
    # only way to open it. The name must include the .so.7 suffix.
    stdlib = ctypes.CDLL("libc.so.7")
    dll_close = stdlib.close

else:
    raise NotImplementedError("Unknown platform.")

dll_close.argtypes = [ctypes.c_void_p]

然后您可以使用dll_close(dll._handle) 卸载库dll = ctypes.CDLL("your-library")

此列表取自this file。每次遇到新平台我都会更新the master branch

【讨论】:

    【解决方案2】:

    从 2020 年开始兼容 windows 和 linux 的最小可重现示例

    类似讨论的概述

    这里是类似讨论的概述(我从中构建了这个答案)。

    最小的可重现示例

    这是针对 windows 和 linux 的,因此提供了 2 个用于编译的脚本。 测试条件:

    • Win 8.1、Python 3.8.3 (anaconda)、ctypes 1.1.0、mingw-w64 x86_64-8.1.0-posix-seh-rt_v6-rev0
    • Linux Fedora 32、Python 3.7.6 (anaconda)、ctypes 1.1.0、g++ 10.2.1

    cpp_code.cpp

    extern "C" int my_fct(int n)
    {
        int factor = 10;
        return factor * n;
    }
    

    编译-linux.sh

    #!/bin/bash
    g++ cpp_code.cpp -shared -o myso.so
    

    编译-windows.cmd

    set gpp="C:\Program Files\mingw-w64\x86_64-8.1.0-posix-seh-rt_v6-rev0\mingw64\bin\g++.exe"
    %gpp% cpp_code.cpp -shared -o mydll.dll
    PAUSE
    

    Python 代码

    from sys import platform
    import ctypes
    
    
    if platform == "linux" or platform == "linux2":
        # https://stackoverflow.com/a/50986803/7128154
        # https://stackoverflow.com/a/52223168/7128154
    
        dlclose_func = ctypes.cdll.LoadLibrary('').dlclose
        dlclose_func.argtypes = [ctypes.c_void_p]
    
        fn_lib = './myso.so'
        ctypes_lib = ctypes.cdll.LoadLibrary(fn_lib)
        handle = ctypes_lib._handle
    
        valIn = 42
        valOut = ctypes_lib.my_fct(valIn)
        print(valIn, valOut)
    
        del ctypes_lib
        dlclose_func(handle)
    
    elif platform == "win32": # Windows
        # https://stackoverflow.com/a/13129176/7128154
        # https://stackoverflow.com/questions/359498/how-can-i-unload-a-dll-using-ctypes-in-python
    
        lib = ctypes.WinDLL('./mydll.dll')
        libHandle = lib._handle
    
        # do stuff with lib in the usual way
        valIn = 42
        valOut = lib.my_fct(valIn)
        print(valIn, valOut)
    
        del lib
        ctypes.windll.kernel32.FreeLibrary(libHandle)
    

    更通用的解决方案(面向对象的具有依赖关系的共享库)

    如果一个共享库有dependencies,这不一定工作(但它可以 - 取决于依赖关系^^)。我没有调查细节,但看起来机制如下:加载了库和依赖项。由于依赖没有卸载,所以库无法卸载。

    我发现,如果我将OpenCv(4.2 版)包含到我的共享库中,这会打乱卸载过程。以下示例仅在linux系统上测试过:

    code.cpp

    #include <opencv2/core/core.hpp>
    #include <iostream> 
    
    
    extern "C" int my_fct(int n)
    {
        cv::Mat1b mat = cv::Mat1b(10,8,(unsigned char) 1 );  // change 1 to test unloading
        
        return mat(0,1) * n;
    }
    

    编译 g++ code.cpp -shared -fPIC -Wall -std=c++17 -I/usr/include/opencv4 -lopencv_core -o so_opencv.so

    Python 代码

    from sys import platform
    import ctypes
    
    
    class CtypesLib:
    
        def __init__(self, fp_lib, dependencies=[]):
            self._dependencies = [CtypesLib(fp_dep) for fp_dep in dependencies]
    
            if platform == "linux" or platform == "linux2":  # Linux
                self._dlclose_func = ctypes.cdll.LoadLibrary('').dlclose
                self._dlclose_func.argtypes = [ctypes.c_void_p]
                self._ctypes_lib = ctypes.cdll.LoadLibrary(fp_lib)
            elif platform == "win32":  # Windows
                self._ctypes_lib = ctypes.WinDLL(fp_lib)
    
            self._handle = self._ctypes_lib._handle
    
        def __getattr__(self, attr):
            return self._ctypes_lib.__getattr__(attr)
    
        def __del__(self):
            for dep in self._dependencies:
                del dep
    
            del self._ctypes_lib
    
            if platform == "linux" or platform == "linux2":  # Linux
                self._dlclose_func(self._handle)
            elif platform == "win32":  # Windows
                ctypes.windll.kernel32.FreeLibrary(self._handle)
    
    
    fp_lib = './so_opencv.so'
    
    ctypes_lib = CtypesLib(fp_lib, ['/usr/lib64/libopencv_core.so'])
    
    valIn = 1
    ctypes_lib.my_fct.argtypes = [ctypes.c_int]
    ctypes_lib.my_fct.restype = ctypes.c_int
    valOut = ctypes_lib.my_fct(valIn)
    print(valIn, valOut)
    
    del ctypes_lib
    

    如果代码示例或到目前为止给出的解释有任何问题,请告诉我。另外,如果您知道更好的方法!如果我们能一劳永逸地解决这个问题,那就太好了。

    【讨论】:

      【解决方案3】:

      Piotr's answer 帮助了我,但我确实在 64 位 Windows 上遇到了一个问题:

      Traceback (most recent call last):
      ...
      ctypes.ArgumentError: argument 1: <class 'OverflowError'>: int too long to convert
      

      按照this answer 中的建议调整FreeLibrary 调用的参数类型为我解决了这个问题。

      因此我们得出以下完整的解决方案:

      import ctypes, ctypes.windll
      
      def free_library(handle):
          kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
          kernel32.FreeLibrary.argtypes = [ctypes.wintypes.HMODULE]
          kernel32.FreeLibrary(handle)
      

      用法:

      lib = ctypes.CDLL("foobar.dll")
      free_library(lib._handle)
      

      【讨论】:

      • 不错!我需要这个
      【解决方案4】:

      如果您需要此功能,您可以编写 2 个 dll,其中 dll_A 从 dll_B 加载/卸载库。使用 dll_A 作为 python 接口加载器和 dll_B 中函数的传递。

      【讨论】:

        【解决方案5】:

        如果您使用 iPython 或类似的工作流程,能够卸载 DLL 以便您无需重新启动会话即可重建 DLL 会很有帮助。在 windows 中工作我只尝试使用 windows DLL 相关的方法。

        REBUILD = True
        if REBUILD:
          from subprocess import call
          call('g++ -c -DBUILDING_EXAMPLE_DLL test.cpp')
          call('g++ -shared -o test.dll test.o -Wl,--out-implib,test.a')
        
        import ctypes
        import numpy
        
        # Simplest way to load the DLL
        mydll = ctypes.cdll.LoadLibrary('test.dll')
        
        # Call a function in the DLL
        print mydll.test(10)
        
        # Unload the DLL so that it can be rebuilt
        libHandle = mydll._handle
        del mydll
        ctypes.windll.kernel32.FreeLibrary(libHandle)
        

        我不太了解内部结构,所以我不确定这有多干净。我认为删除 mydll 会释放 Python 资源,并且 FreeLibrary 调用会告诉 Windows 释放它。我认为首先使用 FreeLibary 释放会产生问题,所以我保存了库句柄的副本并按照示例中显示的顺序释放它。

        我将此方法基于ctypes unload dll,它预先显式加载了句柄。然而,加载约定不像简单的“ctypes.cdll.LoadLibrary('test.dll')”那样干净,所以我选择了显示的方法。

        【讨论】:

        • 这是错误的。 DLL/EXE 模块句柄是指向模块基地址的指针,在 64 位 Python 中通常是 64 位值。但是 ctypes 将整数作为 32 位 C int 值传递;这将截断 64 位指针的值。您要么必须将句柄包装为指针,即ctypes.c_void_p(mydll._handle),要么声明kernel32.FreeLibrary.argtypes = (ctypes.c_void_p,),或者改为调用_ctypes.FreeLibrary(注意初始下划线;它是底层_ctypes扩展模块)。
        【解决方案6】:

        你应该可以通过处理对象来做到这一点

        mydll = ctypes.CDLL('...')
        del mydll
        mydll = ctypes.CDLL('...')
        

        编辑: Hop 的评论是正确的,这解除了名称的绑定,但垃圾收集并没有那么快发生,事实上我什至怀疑它是否会释放加载的库。

        Ctypes 似乎没有提供一种干净的方式来释放资源,它只为 dlopen 句柄提供了一个_handle 字段...

        所以我看到的唯一方法,真正非常不干净的方法,是系统依赖 dlclose 句柄,但它非常非常不干净,因为 ctypes 保持对这个句柄的内部引用.所以卸载需要以下形式:

        mydll = ctypes.CDLL('./mylib.so')
        handle = mydll._handle
        del mydll
        while isLoaded('./mylib.so'):
            dlclose(handle)
        

        它太不干净了,我只检查了它是否可以使用:

        def isLoaded(lib):
           libp = os.path.abspath(lib)
           ret = os.system("lsof -p %d | grep %s > /dev/null" % (os.getpid(), libp))
           return (ret == 0)
        
        def dlclose(handle)
           libdl = ctypes.CDLL("libdl.so")
           libdl.dlclose(handle)
        

        【讨论】:

        • 我不知道,但我怀疑这会卸载 dll。我猜它只会从当前命名空间中的名称中删除绑定(根据语言参考)
        • 当共享库不能被引用计数递减时,POSIX dlclose 返回非零,Windows FreeLibrary 返回零。 _ctypes.dlclose_ctypes.FreeLibrary(注意下划线)在这种情况下引发 OSError
        • 对于那些希望在 Windows 上也能做到这一点的人,请参阅 stackoverflow.com/questions/19547084/…
        猜你喜欢
        • 2012-10-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-12-20
        • 1970-01-01
        • 2013-11-25
        • 1970-01-01
        相关资源
        最近更新 更多