cdll.LoadLibrary 的行为不受 Python 控制,但它取决于运行 Python 的操作系统。 @MarkTolonen 答案显示了 Windows 上的行为,此答案集中在 Linux(和 MacO)上。
cdll.LoadLibrary 使用dlopen 加载共享对象,因此我们需要分析dlopen 的行为。
让我们看看下面的 C 代码 (foo.c):
#include <stdio.h>
static int init();
//global, initialized when so is loaded:
int my_global = init();
static int init(){
printf("initializing address %p\n", (void*)&my_global);
return 42;
}
extern "C" {
void set(int new_val){ my_global = new_val;}
int get() {return my_global;}
}
用g++ --shared -fPIC foo.c -o foo.so 编译。我使用 C++ 而不是 C,所以每次初始化全局变量 my_global 时,它都会记录到标准输出(这不会是 that straight forward in C)。
经过一番准备:
import ctypes
def init_functions(dll):
get = dll.get
get.argtypes = []
get.restype = ctypes.c_int
set = dll.set
set.argtypes = [ctypes.c_int]
set.restype = None
return get, set
我们观察到以下行为:
#first load:
get, set = init_functions(ctypes.CDLL("./foo.so"))
# initializing address 0x7f5ca4a8102c
print(get()) # 42
set(21)
print(get()) # 21
到目前为止:全局变量已初始化,我们可以读/写它。现在第二次加载:
get2, set2 = init_functions(ctypes.CDLL("./foo.so"))
Ups,我们没有看到初始化记录,这意味着......
print(get2()) # 21
全局变量没有重新初始化。这是dlopen 的预期行为:一旦共享对象被加载,它就不会被重新加载而是被重用。这就是为什么例如pyximport 或%%cython-magic 使用不同的names for resulting shared-objects。
要真正创建新版本,我们将共享对象foo.so 复制到foo.so.1,现在:
# load copied shared object:
get3, set3 = init_functions(ctypes.CDLL("./foo.so.1"))
# initializing address 0x7f5ca487102c
print(get3()) # 42
我们可以看到,全局变量被初始化了,但它是另一个地址,即不是旧变量,这很容易检查:
print(get()) # 21 - still the old value.
到目前为止,Windows 和 Linux 上的行为大致相同,但在 Linux 上,我们可以使用符号插入来确保使用相同的全局变量。
普通 CPython 使用 dlopen 和 RTLD_LOCAL,即不使用符号插入,通过使用 RTLD_GLOBAL,即
...
#first load:
get, set = init_functions(ctypes.CDLL("./foo.so", mode=ctypes.RTLD_GLOBAL))
# initializing address 0x7fd01efc102c
...
mode=ctypes.RTLD_GLOBAL 在 Windows 上将被忽略。
第一个区别可见
...
# load copied shared object:
get3, set3 = init_functions(ctypes.CDLL("./foo.so.1"))
# initializing address 0x7fd01efc102c
...
也使用与“foo.so”相同的地址“foo.so.1” - 由于RTLD_GLOBAL 插入了来自两个共享对象的符号n。
现在:
print(get3()) # 42
print(get()) # 42
也就是说,旧的全局变量被重新初始化了。
虽然很有趣,但我不建议依赖这些细节 - 它太聪明、太脆弱且不可移植:很容易引入内存泄漏或崩溃。
人们应该接受,一般来说,全局变量不能通过重新加载共享对象来重新初始化(以可移植的方式)。
通常,人们希望避免插入全局变量并确保它们在共享对象中具有内部链接(例如,通过使它们成为静态或在编译时使用hidden-attribute),因此即使加载也不会被插入RTLD_GLOBAL.