如何在Python中使用ctypes卸载DLL?
我在Python中使用ctypes来加载一个DLL文件,这个方法很好用。
现在我们希望能够在程序运行时重新加载这个DLL。
看起来简单的方法是:
1. 卸载DLL
2. 重新加载DLL
不过,我不太确定正确的卸载DLL的方法是什么。
虽然有一个叫_ctypes.FreeLibrary的函数可以用,但它是私有的。
有没有其他方法可以卸载这个DLL呢?
6 个回答
2020年适用于Windows和Linux的最小可复现示例
类似讨论的概述
这里是一些类似讨论的概述(我从中整理了这个回答)。
最小可复现示例
这个示例适用于Windows和Linux,因此提供了两个编译脚本。测试环境如下:
- Windows 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;
}
compile-linux.sh
#!/bin/bash
g++ cpp_code.cpp -shared -o myso.so
compile-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)
更通用的解决方案(面向对象的共享库及其依赖)
如果一个共享库有依赖
,那么这个方法可能就不再有效了(但有时可以,这取决于依赖的情况^^)。我没有深入研究具体细节,但看起来机制是这样的:库和依赖都被加载。如果依赖没有被卸载,那么库就无法被卸载。
我发现,如果我在我的共享库中包含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
如果代码示例或解释中有任何问题,请告诉我。如果你知道更好的方法也请分享!如果我们能彻底解决这个问题,那就太好了。
能够卸载DLL(动态链接库)是很有帮助的,这样你就可以在不重启会话的情况下重建DLL,特别是在使用iPython或类似的工作流程时。在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释放它。我原本以为先用FreeLibrary释放会出现问题,所以我保存了一份库的句柄,并按照示例中的顺序释放它。
我这个方法是基于ctypes卸载dll,这个方法是先明确加载句柄的。不过,这种加载方式没有简单的“ctypes.cdll.LoadLibrary('test.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)