Как подключить dll к python

Обновлено: 06.07.2024

каков самый простой способ использовать DLL-файл из Python?

в частности, как это можно сделать без написание любого дополнительного кода оболочки C++ для предоставления функциональности Python?

встроенные функции Python является строго предпочтительным по сравнению с использованием сторонней библиотеки.

для удобства использования ctypes - это путь.

следующий пример ctypes - это фактический код, который я написал (в Python 2.5). Это был, безусловно, самый простой способ, который я нашел для выполнения того, что вы просите.

The ctypes материал имеет все типы данных C-типа ( int , char , short , void* и так далее) и может передавать по значению или по ссылке. Он также может возвращать определенные типы данных, хотя мой пример этого не делает (HLL API возвращает значения, изменяя переменную, переданную по ссылке).

С точки зрения конкретного примера, показанного выше, IBM EHLLAPI является довольно последовательным интерфейсом.

все вызовы проходят четыре указателя void (EHLLAPI отправляет код возврата обратно через четвертый параметр, указатель на int итак, пока я указываю int как тип возврата, я могу спокойно игнорировать его) в соответствии с документацией IBM здесь. Другими словами, вариант C функция будет:

это делает для одного, простого ctypes функция, способная делать все, что предоставляет библиотека EHLLAPI, но вполне вероятно, что другим библиотекам потребуется отдельный ctypes настройка функции для каждой библиотечной функции.

возвращаемое значение WINFUNCTYPE - это прототип функции, но вам все равно придется настроить несколько параметров (и типов). Каждый Кортеж в hllApiParams имеет параметр "направление" (1 = вход, 2 = выход и так далее о), имя параметра и значение по умолчанию - см. ctypes doco для деталей

как только у вас есть прототип и информация о параметрах, вы можете создать Python "вызываемый" hllApi С помощью которого можно вызвать функцию. Вы просто создаете необходимую переменную ( p1 через p4 в моем случае) и вызвать функцию с ними.

image

Недавно меня заинтересовала тема использования DLL из Python. Кроме того было интересно разобраться в их структуре, на тот случай, если придется менять исходники библиотек. После изучения различных ресурсов и примеров на эту тему, стало понятно, что применение динамических библиотек может сильно расширить возможности Python. Собственные цели были достигнуты, а чтобы опыт не был забыт, я решил подвести итог в виде статьи — структурировать свой знания и полезные источники, а заодно ещё лучше разобраться в данной теме.

Под катом вас ожидает статья с различными примерами, исходниками и пояснениями к ним.

Содержание

  • Структура DLL
  • DLL & Python
    • Подключение DLL
    • Типы данных в С и Python
    • Аргументы функция и возвращаемые значения
    • Своя DLL и ее использование

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

    Структура DLL

    DLL — Dynamic Link Library — динамическая подключаемая библиотека в операционной системе (ОС) Windows. Динамические библиотеки позволяют сделать архитектуру более модульной, уменьшить количество используемых ресурсов и упрощают модификацию системы. Основное отличие от .EXE файлов — функции, содержащиеся в DLL можно использовать по одной.

    Учитывая, что статья не о самих библиотеках, лучше просто оставить здесь ссылку на довольно информативную статью от Microsoft: Что такое DLL?.

    Для того, чтобы понять, как использовать динамические библиотеки, нужно вникнуть в их структуру.

    DLL содержит набор различных функций, которые потом можно использовать по-отдельности. Но также есть возможность дополнительно указать функцию точки входа в библиотеку. Такая функция обычно имеет имя DllMain и вызывается, когда процессы или потоки прикрепляются к DLL или отделяются от неё. Это можно использовать для инициализации различных структур данных или их уничтожения.

    Рисунок 1 — Пустой template, предлагаемый Code Blocks для проекта DLL.

    На рисунке 1 приведен шаблон, который предлагает Code Blocks, при выборе проекта типа DLL. В представленном шаблоне есть две функции:

    Для начала стоит подробнее рассмотреть функцию DllMain . Через нее ОС может уведомлять библиотеку о нескольких событиях (fdwReason):

    DLL_PROCESS_ATTACH – подключение DLL. Процесс проецирования DLL на адресное пространство процесса. С этим значением DllMain вызывается всякий раз, когда какой-то процесс загружает библиотеку с явной или неявной компоновкой.

    DLL_PROCESS_DETACH – отключение DLL от адресного пространства процесса. С этим значением DllMain вызывается при отключении библиотеки.

    DLL_THREAD_ATTACH – создание процессом, подключившим DLL, нового потока. Зачем DLL знать о каких-то там потоках? А вот зачем, далеко не каждая динамическая библиотека может работать в многопоточной среде.

    DLL_THREAD_DETACH – завершение потока, созданного процессом, подключившим DLL. Если динамическая библиотека создает для каждого потока свои "персональные" ресурсы (локальные переменные и буфера), то это уведомление позволяет их своевременно освобождать.

    Опять же, в тему структуры DLL можно углубляться до бесконечности, там есть много различных нюансов, о которых немного изложено в этой статье.

    У DllMain не так много аргументов, самый важный fdwReason уже рассмотрен выше, теперь о двух других:

    • Аргумент lpvReserved указывает на способ подключения DLL:
      • 0 — библиотека загружена с явной компоновкой.
      • 1 — библиотека загружена с неявной компоновкой.

      О явной и неявной компоновке можно прочесть подробно в статье: Связывание исполняемого файла с библиотекой DLL.

      В предложенном на рисунке 1 шаблоне есть функция SomeFunction , которая может быть экспортирована из динамической библиотеки. Для того, чтобы это показать, при объявлении функции указывается __declspec(dllexport) . Например, так:

      Функции, не объявленные таким образом, нельзя будет вызывать снаружи.

      DLL & Python

      Первым делом, расскажу, как подключать уже собранные DLL, затем, как вызывать из них функции и передавать аргументы, а уже после этого, постепенно доделаю шаблон из Code Blocks и приведу примеры работы с собственной DLL.

      Подключение DLL

      Основной библиотекой в Python для работы с типами данных, совместимыми с типами языка С является ctypes . В документации на ctypes представлено много примеров, которым стоит уделить внимание.

      Чтобы начать работать с DLL, необходимо подключить библиотеку к программе на Python. Сделать это можно тремя способами:

      • cdll — загружает динамическую библиотеку и возвращает объект, а для использования функций DLL нужно будет просто обращаться к атрибутам этого объекта. Использует соглашение вызовов cdecl.
      • windll — использует соглашение вызовов stdcall. В остальном идентична cdll.
      • oledll — использует соглашение вызовов stdcall и предполагается, что функции возвращают код ошибки Windows HRESULT. Код ошибки используется для автоматического вызова исключения WindowsError.

      Для первого примера будем использовать стандартную Windows DLL библиотеку, которая содержит всем известную функцию языка С — printf() . Библиотека msvcrt.dll находится в папке C:\WINDOWS\System32 .

      Код Python:

      Результат:

      Можно использовать подключение библиотеки с помощью метода windll либо oledll , для данного кода разницы не будет, вывод не изменится.

      Если речь не идет о стандартной библиотеке, то конечно следует использовать вызов с указанием пути на dll. В ctypes для загрузки библиотек предусмотрен метод LoadLibrary . Но есть еще более эффективный конструктор CDLL , он заменяет конструкцию cdll.LoadLibrary . В общем, ниже показано два примера вызова одной и той же библиотеки msvcrt.dll.

      Код Python:

      Иногда случается, что необходимо получить доступ к функции или атрибуту DLL, имя которого Python не "примет"… ну бывает. На этот случай имеется функции getattr(lib, attr_name) . Данная функция принимает два аргумента: объект библиотеки и имя атрибута, а возвращает объект атрибута.

      Код Python:

      Результат:

      Теперь становится понятно, как подключить библиотеку и использовать функции. Однако, не всегда в DLL нужно передавать простые строки или цифры. Бывают случаи, когда требуется передавать указатели на строки, переменные или структуры. Кроме того, функции могут и возвращать структуры, указатели и много другое.

      Типы данных в С и Python

      Модуль ctypes предоставляет возможность использовать типы данных совместимые с типами в языке С. Ниже приведена таблица соответствия типов данных.

      Сtypes type C type Python type
      c_bool _Bool bool (1)
      c_char char 1-character string
      c_wchar wchar_t 1-character unicode string
      c_byte char int/long
      c_ubyte unsigned char int/long
      c_short short int/long
      c_ushort unsigned short int/long
      c_int int int/long
      c_uint unsigned int int/long
      c_long long int/long
      c_ulong unsigned long int/long
      c_longlong __int64 or long long int/long
      c_ulonglong unsigned __int64 or unsigned long long int/long
      c_float float float
      c_double double float
      c_longdouble long double float
      c_char_p char * (NUL terminated) string or None
      c_wchar_p wchar_t * (NUL terminated) unicode or None
      c_void_p void * int/long or None

      Таблица 1 — Соответствие типов данных языка Python и языка C, которое предоставляет модуль ctypes .

      Первое, что стоит попробовать — это использовать указатели, куда без них? Давайте напишем программу, где создадим строку и указатель на неё, а потом вызовем printf() для них:

      Результат:

      Если вы создали указатель, то разыменовать (получить доступ к значению, на которое он указывает) можно с использованием атрибута value , пример выше.

      Аргументы функций и возвращаемые значения

      По умолчанию предполагается, что любая экспортируемая функция из динамической библиотеки возвращает тип int . Другие возвращаемые типы можно указать при помощи атрибута restype . При этом, чтобы указать типы аргументов функции можно воспользоваться атрибутом argtypes .

      Например, стандартная функция strcat принимает два указателя на строки и возвращает один указатель на новую строку. Давайте попробуем ей воспользоваться.

      Код Python:

      Результат:

      На этом закончим с примерами использования готовых DLL. Давайте попробуем применить знания о структуре DLL и модуле ctypes для того, чтобы собрать и начать использовать собственную библиотеку.

      Своя DLL и ее использование

      Пример 1

      Шаблон DLL уже был рассмотрен выше, а сейчас, когда дело дошло до написания своей DLL и работы с ней, выскочили первые и очевидные грабли — несовместимость разрядности DLL и Python. У меня на ПК установлен Python x64, оказалось, что как бы не были DLL универсальны, разрядность DLL должна соответствовать разрядности Python. То есть, либо ставить компилятор x64 и Python x64, либо и то и то x32. Хорошо, что это не сложно сделать.

      Ниже привожу код шаблона DLL, в который добавил вывод строки при подключении библиотеки, а также небольшой разбор и вывод аргументов, с которыми вызвалась DllMain . В примере можно понаблюдать, какие участки кода библиотеки вызываются и когда это происходит.

      Код DLL на С:

      Код Python:

      Функция SomeFunction получает указатель на строку и выводит её в окно. На рисунке ниже показана работа программы.




      Рисунок 2 — Демонстрация работы шаблона библиотеки из Code Blocks.

      Все действия происходящие в кейсе DLL_PROCESS_ATTACH , код которого приведен ниже, вызываются лишь одной строкой в Python коде:



      Рисунок 3 — Действия происходящие при подключении DLL.

      Пример 2

      Чтобы подвести итог по использованию DLL библиотек из Python, приведу пример, в котором есть начальная инициализация параметров и передача новых через указатели на строки и структуры данных. Этот код дает понять, как написать аналог структуры С в Python. Ниже привожу код main.c , man.h и main.py .

      Код DLL на С:

      В коде main.h определена структура Passport с тремя полями: два указателя и целочисленная переменная. Кроме того, четыре функции объявлены, как экспортируемые.

      Код DLL на С:

      Внутри кейса DLL_PROCESS_ATTACH происходит выделение памяти под строки и начальная инициализация полей структуры. Выше DllMain определены функции:

      GetPassport — вывод полей структуры passport в консоль.

      *SetName(char new_name)** — установка поля name структуры passport .

      *SetSurname(char new_surname)** — установка поля surname структуры passport .

      *SetPassport(Passport new_passport)** — установка всех полей структуры passport . Принимает в качестве аргумента указатель на структуру с новыми полями.

      Теперь можно подключить библиотеку в Python.

      Код на Python

      В коде выше многое уже знакомо, кроме создания структуры аналогичной той, которая объявлена в DLL и передачи указателя на эту структуру из Python в DLL.

      Прочитав документацию к модулю ctypes и несколько статей, сделал вывод, что подключить библиотеку можно одним из двух способов:

      Но при вызове библиотеки генерируется ошибка: OSError: [WinError 126] Не найден указанный модуль

      В чем ошибка? Таким способом точно можно вызывать библиотеки, написаннные на Паскале?

      __________________
      Помощь в написании контрольных, курсовых и дипломных работ здесь

      Вызов из Python функций сторонней DLL
      Подскажите пожалуйста по такому вопросу. Нужно из Python вызвать функцию из сторонней DLL. Проблема.

      Подключение VB-макроса к Python-у
      Всем привет! Нужно подключить макрос к Python, который будет считать значение формулы.В макрос.

      kousnetsov, вы пробовали указывать полный путь?
      У вас ошибка не связана с архитектурой библиотеки.

      Пробовал, но видимо неверно

      Есть вероятность, что для работы вашего dll файла требуются другие dll файлы, которые система не может найти.
      (либо проблемы с порядком подключения нескольких dll файлов. сколько времени убил на поиск подобной ошибки) kousnetsov, Можете приложить полный текст ошибки? Ошибка одна, или, возможно, их несколько? Какая версия Python? Alli_Lupin, она одна. Классика для WinError 126. kousnetsov, Можете приложить полный текст ошибки? Ошибка одна, или, возможно, их несколько? Какая версия Python?

      python 3.7
      На самом деле сейчас обратил вниманиие, что при указании полного пути генерируется уже другая ошибка:

      C:\Users\Zakhar\Anaconda3\python.exe D:/Dropbox/python_projects/taly/mainer.py
      Traceback (most recent call last):
      File "D:/Dropbox/python_projects/taly/mainer.py", line 8, in <module>
      newlib=ctypes.CDLL(r'D:\Dropbox\python_projects\taly\Esc51.d ll')
      File "C:\Users\Zakhar\Anaconda3\lib\ctypes\__init__.py", line 356, in __init__
      self._handle = _dlopen(self._name, mode)
      OSError: [WinError 193] %1 не является приложением Win32

      Поэтому и возникло подозрение в какой-то несовместимости инструмента и библиотеки

      Существует несколько способов загрузки общих библиотек в процесс Python. Один из способов-создать экземпляр одного из следующих классов:

      Содержание:

      • Класс ctypes.CDLL() загружает библиотеку в ОС Unix,
      • Класс ctypes.OleDLL() загружает библиотеку в ОС Windows,
      • Класс ctypes.WinDLL() загружает библиотеку в ОС Windows,
      • Класс ctypes.PyDLL() загружает библиотеку API Python C, , , , .

      ctypes.CDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False, winmode=0) :

      Экземпляры класса ctypes.CDLL() представляют собой загруженные библиотеки DLL. Функции в этих библиотеках используют стандартное соглашение о вызовах языка C и, как предполагается, возвращают число int .

      ctypes.OleDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False, winmode=0) :

      Класс ctypes.OleDLL() используется только в Windows. Экземпляры этого класса представляют собой загруженные общие библиотеки DLL, функции в этих библиотеках используют соглашение о вызовах stdcall и, как предполагается, возвращают специфичный для Windows код HRESULT .

      Значения HRESULT содержат информацию, определяющую, завершился ли вызов функции успешно или неудачно, вместе с дополнительным кодом ошибки. Если возвращаемое значение сигнализирует об ошибке, то автоматически возникает исключение OSError .

      ctypes.WinDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False, winmode=0) :

      Класс ctypes.WinDLL() используется только в Windows. Экземпляры этого класса представляют собой загруженные библиотеки DLL, функции в этих библиотеках используют соглашение о вызовах stdcall и, как предполагается, по умолчанию возвращают число int .

      В Windows CE используется только стандартное соглашение о вызовах, для удобства WinDLL и OleDLL используют стандартное соглашение о вызовах на этой платформе.

      Глобальная блокировка интерпретатора Python снимается перед вызовом любой функции, экспортируемой этими библиотеками, а затем запрашивается повторно.

      ctypes.PyDLL(name, mode=DEFAULT_MODE, handle=None) :

      Экземпляры класса ctypes.PyDLL() ведут себя как экземпляры ctypes.CDLL() , за исключением того, что Python GIL не освобождается во время вызова функции, а после выполнения функции проверяется флаг ошибки Python. Если установлен флаг ошибки, то возникает исключение Python.

      Таким образом, класс ctypes.PyDLL() полезен только для прямого вызова функций API Python C.

      Описание аргументов классов-загрузчиков.

      Все эти классы можно создать и вызвав используя только один обязательный аргументом name - путь к DLL библиотеке. Если имеется уже существующий дескриптор загруженной библиотеки, то его можно передать как параметр name , в противном случае для загрузки библиотеки и для получения дескриптора используется функция dlopen() или LoadLibrary() базовой платформы.

      Аргумент mode можно использовать, для указания режима загрузки библиотеки. За подробностями обращайтесь к странице руководства dlopen(3) , можно посмотреть консольной командой $ man dlopen . В Windows режим mode игнорируется. В системах posix всегда добавляется RTLD_NOW , и его нельзя настроить.

      Аргумент use_errno , если он установлен в значение True , то он включает механизм ctypes , который позволяет безопасно получить доступ к системному номеру ошибки errno . Модуль ctypes поддерживает локальную для потока копию системной переменной errno ; Если вызывать внешние функции, созданные с помощью use_errno=True , то тогда значение errno перед вызовом функции заменяется частной копией ctypes , то же самое происходит сразу после вызова функции.

      Функция ctypes.get_errno() возвращает значение частной копии ctypes , а функция ctypes.set_errno() изменяет частную копию ctypes на новое значение и возвращает прежнее значение.

      Аргумент use_last_error , если ему задано значение True , то он включает тот же механизм, только для кода ошибки Windows, которым управляют функции Windows API GetLastError() и SetLastError() . Функции модуля ctypes.get_last_error() и ctypes.set_last_error() используются для запроса и изменения частной копии ctypes кода ошибки Windows.

      Изменено в Python 3.8: добавлен параметр winmode .

      Экземпляры этих классов не имеют общедоступных методов. Функции, экспортируемые из C-библиотек, могут быть доступны как их атрибуты или по индексу. Обратите внимание, что доступ к функции через атрибут кеширует результат и поэтому повторный доступ к нему каждый раз возвращает один и тот же объект. С другой стороны, доступ к нему через индекс каждый раз возвращает новый объект:

      Альтернативный способ загрузки DLL библиотек.

      Общие DLL библиотеки также могут быть загружены с помощью одного из готовых объектов, которые являются экземплярами класса ctypes.LibraryLoader , либо путем вызова метода .LoadLibrary() , либо путем получения библиотеки как атрибута экземпляра загрузчика.

      ctypes.LibraryLoader(dlltype) :

      Класс загружающий библиотеки языка C. Аргумент dlltype должен быть одним из типов ctypes.CDLL , ctypes.PyDLL , ctypes.WinDLL или ctypes.OleDLL .

      Магический метод __getattr__() в этом классе имеет особое поведение: он позволяет загружать библиотеку языка C, обращаясь к ней как к атрибуту экземпляра загрузчика библиотеки. Результат кэшируется, поэтому повторные обращения к атрибутам каждый раз возвращают одну и ту же библиотеку.

      • Метод LoadLibrary(name) загружает библиотеку языка C в процесс Python и возвращает ее объект. Этот метод всегда возвращает новый экземпляр библиотеки.

      Модуль ctypes имеет сборные библиотечные погрузчики:

      ctypes.cdll :

      ctypes.windll :

      • создает экземпляр класса ctypes.WinDLL . Используется только в Windows!

      ctypes.oledll :

      • создает экземпляр класса ctypes.OleDLL . Используется только в Windows!

      ctypes.pydll :

      Пример загрузки общих DLL библиотек в Windows.

      Для загрузки общих библиотек в Windows, модуль ctypes автоматически экспортирует windll и oledll .

      Обратите внимание, что msvcrt - это стандартная библиотека C MS, содержащая большинство стандартных функций языка C и использующая соглашение о вызовах cdecl :

      Windows автоматически добавляет суффикс файла .dll .

      Примечание. Для доступа к стандартной библиотеке C через cdll.msvcrt будет использоваться устаревшая версия библиотеки, которая может быть несовместима с той, которая используется Python. По возможности используйте встроенные функции Python или импортируйте и используйте модуль msvcrt .

      Пример загрузки общих C-библиотек в Linux.

      Для загрузки общих библиотек в Linux, модуль ctypes автоматически экспортирует cdll .

      Читайте также: