Независимые библиотечные экземпляры CDLL с Ctypes - PullRequest
0 голосов
/ 17 января 2019

Я пытаюсь использовать 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())

Библиотека Фортрана компилируется с помощью команды:

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

Когда я запускаю python test.py, я получаю 1 как вывод, в то время как я хочу получить 0. Кто-нибудь знает, как заставить это работать?

1 Ответ

0 голосов
/ 18 января 2019

ctypes ( [Python 3]: ctypes - библиотека сторонних функций для Python ) загружает библиотеки (на Nix ), используя dlopen . Согласно [man7]: DLOPEN (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
  • Если его имя или местоположение отличается, он загружается снова

Короче говоря, чтобы ответить на ваш вопрос: просто скопируйте его под другим именем и загрузите его.

...