Выгрузить разделяемую библиотеку внутри загруженной разделяемой библиотеки ctypes - PullRequest
0 голосов
/ 05 сентября 2018

Я вызываю файл so из моего скрипта python. Насколько я понимаю, мне на самом деле не нужно освобождать разделяемую библиотеку, которая открывается в python с использованием ctypes. Тем не менее, внутри моего кода so, он удаляет другой файл so и не выполняет dlclose (). В этом случае безопасно ли использовать со стороны Python? Разве мне не нужно освобождать разделяемую библиотеку, которая открывалась внутри файла ctypes loade so?

1 Ответ

0 голосов
/ 07 сентября 2018

Правило Уборка за собой всегда применяется (хотя современные технологии заботятся об аспекте очистки для вас).

[Python 3.5]: ctypes - библиотека сторонних функций для Python содержит много полезной информации и должна быть вашим другом.

ctypes использует dlopen Когда загрузка .dll . Как я заметил, он не вызывает соответствующий dlclose , что означает, что .dll все его иждивенцы, которые были загружены при его загрузке ) будет оставаться в памяти до тех пор, пока процесс не прекратится (или пока явно не выгружен).

С [man7]: DLOPEN (3) :

Если объект, указанный в filename , имеет зависимости от других общих объектов, то они также автоматически загружаются динамическим компоновщиком с использованием тех же правил. (Этот процесс может происходить рекурсивно, если эти объекты в свою очередь имеют зависимости и т. Д.)
...
Если тот же общий объект загружается снова с помощью dlopen () , возвращается тот же дескриптор объекта. Динамический компоновщик поддерживает количество ссылок для дескрипторов объектов, поэтому динамически загруженный общий объект не освобождается до тех пор, пока dlclose () не будет вызываться для него столько раз, сколько dlopen () успешно выполнено Это. Любые возвраты инициализации (см. Ниже) вызываются только один раз.

Итак, я не думаю, что у вас возникнут проблемы (конечно, все зависит от контекста). Как вы заметили, загрузка библиотеки несколько раз на самом деле не загружает ее каждый раз, поэтому шанс исчерпать память довольно мал (ну, если вы не загружаете огромное количество различных .dll с, каждый с большим количеством различных зависимостей).

Один случай, о котором я могу подумать, это загрузка .dll , в которой используется символ из другого .dll . Если этот символ также определен в другом ( 3 rd ) .dll , который был загружен ранее, тогда код будет работать не так, как ожидалось.

В любом случае, вы можете вручную выгрузить (или лучше: уменьшить его refcount ) a .dll (я не уверен, как это вписывается в рекомендуемые способы или лучшие практики ), как показано в примере ниже.

dll.c

#include <stdio.h>


int test() {
    printf("[%s] (%d) - [%s]\n", __FILE__, __LINE__, __FUNCTION__);
    return 0;
}

code.py

import sys
from ctypes import CDLL, \
    c_int, c_void_p


DLL = "./dll.so"

dlclose_func = CDLL(None).dlclose  # This WON'T work on Win
dlclose_func.argtypes = [c_void_p]


def _load_dll(dll_name):
    dll_dll = CDLL(dll_name)
    print("{:}".format(dll_dll))
    return dll_dll


def _load_test_func(dll):
    test_func = dll.test
    test_func.restype = c_int
    return test_func


def main():
    print("Loading a dll via `ctypes`, then delete the object. The dll is not unloaded. Call `dlclose` to unload. A 2nd call will fail.")
    dll_dll = _load_dll(DLL)
    dll_handle = dll_dll._handle
    del dll_dll
    print("{:} returned {:d}".format(dlclose_func.__name__, dlclose_func(dll_handle)))  # Even if the ctypes dll object was destroyed, the dll wasn't unloaded
    print("{:} returned {:d}".format(dlclose_func.__name__, dlclose_func(dll_handle)))  # A new dlclose call will fail

    print("\nUse `ctypes` to load the dll twice. The dll is not actually loaded only the 1st time (both have the same handle), but its ref count is increased. `dlclose` must be also called twice.")
    dll0_dll = _load_dll(DLL)
    dll1_dll = _load_dll(DLL)
    print("{:} returned {:d}".format(dlclose_func.__name__, dlclose_func(dll0_dll._handle)))
    print("{:} returned {:d}".format(dlclose_func.__name__, dlclose_func(dll1_dll._handle)))
    print("{:} returned {:d}".format(dlclose_func.__name__, dlclose_func(dll1_dll._handle)))

    print("\nLoad a dll via `ctypes`, and load one of its funcs. Try calling it before and after unloading the dll.")
    dll_dll = _load_dll(DLL)
    test_func = _load_test_func(dll_dll)
    print("{:} returned {:d}".format(test_func.__name__, test_func()))
    print("{:} returned {:d}".format(dlclose_func.__name__, dlclose_func(dll_dll._handle)))
    print("{:} returned {:d}".format(test_func.__name__, test_func()))  # Comment this line as it would segfault !!!



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

выход

[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q052179325]> ls
code.py  dll.c
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q052179325]> gcc -fPIC -shared -o dll.so dll.c
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q052179325]> python3 ./code.py
Python 3.5.2 (default, Nov 23 2017, 16:37:01)
[GCC 5.4.0 20160609] on linux

Loading a dll via `ctypes`, then delete the object. The dll is not unloaded. Call `dlclose` to unload. A 2nd call will fail.
<CDLL './dll.so', handle 1d7aa20 at 0x7fa38715f240>
dlclose returned 0
dlclose returned -1

Use `ctypes` to load the dll twice. The dll is not actually loaded only the 1st time (both have the same handle), but its ref count is increased. `dlclose` must be also called twice.
<CDLL './dll.so', handle 1de2c80 at 0x7fa38715f240>
<CDLL './dll.so', handle 1de2c80 at 0x7fa38715f278>
dlclose returned 0
dlclose returned 0
dlclose returned -1

Load a dll via `ctypes`, and load one of its funcs. Try calling it before and after unloading the dll.
<CDLL './dll.so', handle 1de39c0 at 0x7fa38715f8d0>
[dll.c] (5) - [test]
test returned 0
dlclose returned 0
Segmentation fault (core dumped)
...