如何在Python中使用ctypes卸载DLL?

Tom*_*nen 19 python dll ctypes

我正在使用ctypes在Python中加载DLL.这非常有效.

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

直截了当的方法似乎是:1.卸载DLL 2.加载DLL

不幸的是我不确定卸载DLL的正确方法是什么.

_ctypes.FreeLibrary可用,但是私有.

有没有其他方法来卸载DLL?

Pio*_*cki 16

你应该能够通过处理对象来做到这一点

mydll = ctypes.CDLL('...')
del mydll
mydll = ctypes.CDLL('...')
Run Code Online (Sandbox Code Playgroud)

编辑: Hop的评论是正确的,这解除了名称,但垃圾收集不会很快发生,事实上我甚至怀疑它甚至发布了加载的库.

Ctypes似乎没有提供一种释放资源的简洁方法,它只为_handledlopen句柄提供了一个字段...

因此,我看到的唯一方法,一种非常非常简洁的方式,是系统依赖于dlclose句柄,但它非常非常不洁,因为此外ctypes保持对此句柄的内部引用.所以卸载需要采取以下形式:

mydll = ctypes.CDLL('./mylib.so')
handle = mydll._handle
del mydll
while isLoaded('./mylib.so'):
    dlclose(handle)
Run Code Online (Sandbox Code Playgroud)

它是如此不洁,以至于我只检查它的工作原理:

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)
Run Code Online (Sandbox Code Playgroud)


小智 8

如果您使用的是 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)
Run Code Online (Sandbox Code Playgroud)

我不太了解内部结构,所以我不确定这有多干净。我认为删除 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` 扩展模块)。 (4认同)

Mar*_*hke 5

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_代码.cpp

extern "C" int my_fct(int n)
{
    int factor = 10;
    return factor * n;
}
Run Code Online (Sandbox Code Playgroud)

编译linux.sh

#!/bin/bash
g++ cpp_code.cpp -shared -o myso.so
Run Code Online (Sandbox Code Playgroud)

编译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
Run Code Online (Sandbox Code Playgroud)

Python代码

from sys import platform
import ctypes


if platform == "linux" or platform == "linux2":
    # /sf/answers/3569076241/
    # /sf/answers/3655621791/

    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
    # /sf/answers/919042351/
    # /sf/ask/25164891/

    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)
Run Code Online (Sandbox Code Playgroud)

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

如果共享库具有dependencies,则这不一定再有效(但它可以 - 取决于依赖项 ^^)。我没有调查细节,但看起来机制如下:加载库和依赖项。由于依赖项未卸载,因此无法卸载库。

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

代码.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;
}
Run Code Online (Sandbox Code Playgroud)

编译用 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
Run Code Online (Sandbox Code Playgroud)

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