具有Ctypes的独立CDLL库实例

时间:2019-01-17 19:41:23

标签: python fortran ctypes

我试图使用ctypes并两次加载相同的已编译Fortran库,以使我有两个独立的实例,以便该库包含的任何模块变量都不会存储在相同的内存位置。所描述的一般解决方案(例如,在这里https://mail.python.org/pipermail/python-list/2010-May/575368.html)是提供库的完整路径,而不仅仅是提供其名称。但是,我无法使它像这样工作。这是一个演示问题的最小工作示例:

test.f90:

module test
    use iso_c_binding, only: c_int
    implicit none
    integer :: n
contains
    integer(c_int) function get() bind(c, name='get')
        get = n
    end function get

    subroutine set(new_n) bind(c, name='set')
        integer(c_int), intent(in) :: new_n
        n = new_n
    end subroutine set
end module test

test.py:

import os
from ctypes import cdll, c_int, byref

if __name__ == '__main__':
    lib1 = cdll.LoadLibrary(os.path.abspath('test.so'))
    lib2 = cdll.LoadLibrary(os.path.abspath('test.so'))

    lib1.set(byref(c_int(0)))
    lib2.set(byref(c_int(1)))

    print(lib1.get())

使用以下命令编译Fortran库:

gfortran -shared -fPIC -o test.so test.f90

当我运行python test.py时,我得到1作为输出,而我想得到0。有人知道如何使这项工作吗?

1 个答案:

答案 0 :(得分:2)

ctypes [Python 3]: ctypes - A foreign function library for Python)使用 dlopen 加载库(在 Nix 上)。根据{{​​3}}:

  

如果使用 dlopen()再次加载相同的共享对象,则返回相同的对象句柄。动态链接器维护对象句柄的引用计数,因此直到对其调用 dlclose()的次数达到 dlopen()的次数之后,才会释放动态加载的共享对象。成功了。

我准备了一个小例子。

dll.c

#if defined(_WIN32)
#  define DLL_EXPORT __declspec(dllexport)
#else
#  define DLL_EXPORT
#endif


static int val = -1;


DLL_EXPORT int get() {
    return val;
}


DLL_EXPORT void set(int i) {
    val = i;
}

code.py

#!/usr/bin/env python3

import sys
import os
import shutil
import ctypes


DLL0_NAME = "./dll0.so"
DLL1_NAME = "./dll1.so"
DIR0_NAME = "dir0"


def get_dll_funcs(dll):
    get_func = dll.get
    get_func.argtypes = None
    get_func.restype = ctypes.c_int
    set_func = dll.set
    set_func.argtypes = [ctypes.c_int]
    set_func.restype = None
    return get_func, set_func


def main():
    os.makedirs(DIR0_NAME, exist_ok=True)
    shutil.copy(DLL0_NAME, DIR0_NAME)
    shutil.copy(DLL0_NAME, DLL1_NAME)

    dll_names = [DLL0_NAME, os.path.abspath(DLL0_NAME), os.path.join(DIR0_NAME, DLL0_NAME), DLL1_NAME]
    dlls = [ctypes.CDLL(item) for item in dll_names]

    for idx, dll in enumerate(dlls):
        print("Item {:d} ({:s}) was loaded at {:08X}".format(idx, dll_names[idx], dll._handle))
        set_func = get_dll_funcs(dll)[1]
        set_func(idx * 10)

    for idx, dll in enumerate(dlls):
        get_func = get_dll_funcs(dll)[0]
        print("Item {:d} get() returned {: d}".format(idx, get_func()))


if __name__ == "__main__":
    print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
    main()

输出

[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q054243176]> ls
code.py  dll.c
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q054243176]> gcc -o dll0.so -shared dll.c
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q054243176]> ls
code.py  dll0.so  dll.c
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q054243176]> ./code.py
Python 3.5.2 (default, Nov 12 2018, 13:43:14)
[GCC 5.4.0 20160609] on linux

Item 0 (./dll0.so) was loaded at 02437A80
Item 1 (/home/cfati/Work/Dev/StackOverflow/q054243176/dll0.so) was loaded at 02437A80
Item 2 (dir0/./dll0.so) was loaded at 02438690
Item 3 (./dll1.so) was loaded at 02438EF0
Item 0 get() returned  10
Item 1 get() returned  10
Item 2 get() returned  20
Item 3 get() returned  30
从输出中看到

(还要注意 _handle 属性),尝试多次(通过其路径)加载同一 .dll (行为相同)在 Win 上):

  • 如果位于同一路径中(即使指定的路径不同),实际上也不会再次加载,只会增加其 refcount
  • 如果名称或位置不同,则会重新加载

简而言之,要回答您的问题:只需使用其他名称复制并加载该名称即可。