Как я могу выгрузить DLL с помощью ctypes в Python?

Я использую ctypes для загрузки DLL в Python. Это прекрасно работает.

Теперь мы хотели бы иметь возможность перезагружать эту DLL во время выполнения.

Казалось бы, простой подход: 1. Выгрузите DLL. 2. Загрузите DLL.

К сожалению, я не уверен, как правильно выгрузить DLL.

_ctypes.FreeLibrary доступен, но частный.

Есть ли другой способ выгрузить DLL?

Вы нашли лучший ответ, чем мой уродливый способ? Если нет, возможно, вам следует спросить об этом в их списке рассылки, и если его нет, сообщите об этом. 'del' должен вызывать функцию для повторного использования ресурсов!

Piotr Lesnicki 22.01.2009 18:00
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
22
1
21 229
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Ответ принят как подходящий

вы должны иметь возможность сделать это, избавившись от объекта

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)

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

user3850 11.12.2008 19:17

Если для общей библиотеки невозможно уменьшить счетчик ссылок, POSIX dlclose возвращает ненулевое значение, а Windows FreeLibrary возвращает ноль. _ctypes.dlclose и _ctypes.FreeLibrary (обратите внимание на подчеркивание) в этом случае повышают значение OSError.

Eryk Sun 14.02.2014 15:35

Для людей, которые хотят найти способ сделать это и в Windows, см. stackoverflow.com/questions/19547084/…

Gamrix 29.07.2016 21:25

Полезно иметь возможность выгрузить 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, чтобы освободить его. Я предполагал, что сначала освобождение с помощью FreeLibary вызовет проблемы, поэтому я сохранил копию дескриптора библиотеки и освободил ее в порядке, показанном в примере.

Я основал этот метод на ctypes выгрузить dll, который загружал дескриптор явно впереди. Однако соглашение о загрузке не работает так чисто, как простой "ctypes.cdll.LoadLibrary ('test.dll')", поэтому я выбрал показанный метод.

Это не правильно. Дескриптор модуля DLL / EXE - это указатель на базовый адрес модуля, который обычно является 64-битным значением в 64-битном Python. Но ctypes передает целые числа как 32-битные значения C int; который усекает значение 64-битного указателя. Вам нужно либо обернуть дескриптор как указатель, то есть ctypes.c_void_p(mydll._handle), либо объявить kernel32.FreeLibrary.argtypes = (ctypes.c_void_p,), либо вместо этого вызвать _ctypes.FreeLibrary (обратите внимание на начальное подчеркивание; это базовый модуль расширения _ctypes).

Eryk Sun 23.09.2015 15:37

Если вам нужна эта функция, вы можете написать 2 библиотеки DLL, где dll_A загружает / выгружает библиотеку из dll_B. Используйте dll_A в качестве загрузчика интерфейса python и сквозной передачи для функций в dll_B.

Петр ответ мне помог, но я столкнулся с одной проблемой в 64-битной Windows:

Traceback (most recent call last):
...
ctypes.ArgumentError: argument 1: <class 'OverflowError'>: int too long to convert

Настройка типа аргумента вызова FreeLibrary, как предложено в этот ответ, решила эту проблему для меня.

Таким образом, мы приходим к следующему законченному решению:

import ctypes, ctypes.windll

def free_library(handle):
    kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
    kernel32.FreeLibrary.argtypes = [ctypes.wintypes.HMODULE]
    kernel32.FreeLibrary(handle)

Использование:

lib = ctypes.CDLL("foobar.dll")
free_library(lib._handle)

Хороший! мне нужно это

Alen Wesker 17.07.2020 10:56

Минимальный воспроизводимый пример, совместимый с Windows и Linux, из 2020

обзор аналогичного обсуждения

Вот обзор подобных обсуждений (откуда я построил этот ответ).

минимальный воспроизводимый пример

Это для windows и linux, поэтому для компиляции дано 2 скрипта. Протестировано под:

  • Win 8.1, Python 3.8.3 (анаконда), ctypes 1.1.0, mingw-w64 x86_64-8.1.0-posix-seh-rt_v6-rev0
  • Linux Fedora 32, Python 3.7.6 (анаконда), 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

компилировать-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)

Более общее решение (объектно-ориентированное для разделяемых библиотек с зависимостями)

Если в общей библиотеке есть dependencies, это больше не обязательно работает (но может - в зависимости от зависимости ^^). Я не разбирался в деталях, но похоже, что механизм следующий: загружаются библиотека и зависимость. Поскольку зависимость не выгружается, библиотека не может быть выгружена.

Я обнаружил, что если я включу 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

Сообщите мне, если возникнут какие-либо проблемы с примерами кода или приведенными объяснениями. Также, если вы знаете способ получше! Было бы здорово, если бы мы могли решить этот вопрос раз и навсегда.

Другие вопросы по теме