我正在使用 ctypes 在 Python 中加载 DLL。这很好用。
现在我们希望能够在运行时重新加载该 DLL。
直接的方法似乎是: 1. 卸载 DLL 2. 加载 DLL
不幸的是,我不确定卸载 DLL 的正确方法是什么。
_ctypes.FreeLibrary 可用,但私有。
还有其他方法可以卸载 DLL 吗?
你应该能够通过处理对象来做到这一点
mydll = ctypes.CDLL('...')
del mydll
mydll = ctypes.CDLL('...')
编辑: Hop 的评论是正确的,这解除了名称的绑定,但是垃圾收集并没有那么快发生,事实上我什至怀疑它甚至会释放加载的库。
Ctypes 似乎没有提供一种干净的方式来释放资源,它只为_handle
dlopen 句柄提供了一个字段......
所以我看到的唯一方法,一个非常非常不干净的方法,是系统依赖 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)
如果您使用 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')”那样干净,所以我选择了所示的方法。
Piotr 的回答帮助了我,但我确实在 64 位 Windows 上遇到了一个问题:
Traceback (most recent call last):
...
ctypes.ArgumentError: argument 1: <class 'OverflowError'>: int too long to convert
按照此答案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)
如果您需要此功能,您可以编写 2 个 dll,其中 dll_A 从 dll_B 加载/卸载库。使用 dll_A 作为 python 接口加载器和 dll_B 中函数的传递。
为了完全的交叉兼容性:我维护了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")
。
这里是类似讨论的概述(我从中构建了这个答案)。
这适用于 windows 和 linux,因此有 2 个脚本用于编译。测试下:
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 系统上进行了测试:
代码.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
当代码示例或到目前为止给出的解释有任何问题时,请告诉我。另外,如果您知道更好的方法!如果我们能一劳永逸地解决这个问题,那就太好了。