- Классы для загрузки C-библиотек модулем ctypes.
- Содержание:
- ctypes.CDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False, winmode=0) :
- ctypes.OleDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False, winmode=0) :
- ctypes.WinDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False, winmode=0) :
- ctypes.PyDLL(name, mode=DEFAULT_MODE, handle=None) :
- Описание аргументов классов-загрузчиков.
- Альтернативный способ загрузки DLL библиотек.
- ctypes.LibraryLoader(dlltype) :
- Модуль ctypes имеет сборные библиотечные погрузчики:
- ctypes.cdll :
- ctypes.windll :
- ctypes.oledll :
- ctypes.pydll :
- Пример загрузки общих DLL библиотек в Windows.
- Пример загрузки общих C-библиотек в Linux.
- Installing Packages¶
- Requirements for Installing Packages¶
- Ensure you can run Python from the command line¶
- Ensure you can run pip from the command line¶
- Ensure pip, setuptools, and wheel are up to date¶
- Optionally, create a virtual environment¶
- Creating Virtual Environments¶
- DLL & Python
- Содержание
- Структура DLL
- DLL & Python
- Подключение DLL
- Типы данных в С и Python
- Аргументы функций и возвращаемые значения
- Своя DLL и ее использование
- Пример 1
- Пример 2
Классы для загрузки C-библиотек модулем ctypes.
Существует несколько способов загрузки общих библиотек в процесс Python. Один из способов-создать экземпляр одного из следующих классов:
Содержание:
- Класс ctypes.CDLL() загружает библиотеку в ОС Unix,
- Класс ctypes.OleDLL() загружает библиотеку в ОС Windows,
- Класс ctypes.WinDLL() загружает библиотеку в ОС Windows,
- Класс ctypes.PyDLL() загружает библиотеку API Python C,
- Описание аргументов классов-загрузчиков,
- Альтернативный способ загрузки C-библиотек,
- Пример загрузки DLL библиотек в Windows,
- Пример загрузки C-библиотек в Linux.
ctypes.CDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False, winmode=0) :
Экземпляры класса ctypes.CDLL() представляют собой загруженные библиотеки DLL. Функции в этих библиотеках используют стандартное соглашение о вызовах языка C и, как предполагается, возвращают число int .
В Windows создание экземпляра CDLL может завершиться ошибкой, даже если имя DLL существует. Когда зависимая DLL, загруженной DLL не найдена, то возникает ошибка OSError с сообщением «[WinError 126] The specified module could not be found«. Это сообщение об ошибке не содержит имени отсутствующей библиотеки DLL библиотеки, т.к. Windows API не возвращает эту информацию, что затрудняет диагностику этой ошибки. Чтобы устранить эту ошибку и определить, какая DLL не найдена, необходимо с помощью средств отладки и трассировки Windows найти список зависимых DLL и определить, какая из них не найдена.
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 .
Источник
Installing Packages¶
This section covers the basics of how to install Python packages .
It’s important to note that the term “package” in this context is being used to describe a bundle of software to be installed (i.e. as a synonym for a distribution ). It does not to refer to the kind of package that you import in your Python source code (i.e. a container of modules). It is common in the Python community to refer to a distribution using the term “package”. Using the term “distribution” is often not preferred, because it can easily be confused with a Linux distribution, or another larger software distribution like Python itself.
Requirements for Installing Packages¶
This section describes the steps to follow before installing other Python packages.
Ensure you can run Python from the command line¶
Before you go any further, make sure you have Python and that the expected version is available from your command line. You can check this by running:
You should get some output like Python 3.6.3 . If you do not have Python, please install the latest 3.x version from python.org or refer to the Installing Python section of the Hitchhiker’s Guide to Python.
If you’re a newcomer and you get an error like this:
It’s because this command and other suggested commands in this tutorial are intended to be run in a shell (also called a terminal or console). See the Python for Beginners getting started tutorial for an introduction to using your operating system’s shell and interacting with Python.
If you’re using an enhanced shell like IPython or the Jupyter notebook, you can run system commands like those in this tutorial by prefacing them with a ! character:
It’s recommended to write
Due to the way most Linux distributions are handling the Python 3 migration, Linux users using the system Python without creating a virtual environment first should replace the python command in this tutorial with python3 and the python -m pip command with python3 -m pip —user . Do not run any of the commands in this tutorial with sudo : if you get a permissions error, come back to the section on creating virtual environments, set one up, and then continue with the tutorial as written.
Ensure you can run pip from the command line¶
Additionally, you’ll need to make sure you have pip available. You can check this by running:
If you installed Python from source, with an installer from python.org, or via Homebrew you should already have pip. If you’re on Linux and installed using your OS package manager, you may have to install pip separately, see Installing pip/setuptools/wheel with Linux Package Managers .
If pip isn’t already installed, then first try to bootstrap it from the standard library:
If that still doesn’t allow you to run python -m pip :
Run python get-pip.py . 2 This will install or upgrade pip. Additionally, it will install setuptools and wheel if they’re not installed already.
Be cautious if you’re using a Python install that’s managed by your operating system or another package manager. get-pip.py does not coordinate with those tools, and may leave your system in an inconsistent state. You can use python get-pip.py —prefix=/usr/local/ to install in /usr/local which is designed for locally-installed software.
Ensure pip, setuptools, and wheel are up to date¶
While pip alone is sufficient to install from pre-built binary archives, up to date copies of the setuptools and wheel projects are useful to ensure you can also install from source archives:
Optionally, create a virtual environment¶
See section below for details, but here’s the basic venv 3 command to use on a typical Linux system:
This will create a new virtual environment in the tutorial_env subdirectory, and configure the current shell to use it as the default python environment.
Creating Virtual Environments¶
Python “Virtual Environments” allow Python packages to be installed in an isolated location for a particular application, rather than being installed globally. If you are looking to safely install global command line tools, see Installing stand alone command line tools .
Imagine you have an application that needs version 1 of LibFoo, but another application requires version 2. How can you use both these applications? If you install everything into /usr/lib/python3.6/site-packages (or whatever your platform’s standard location is), it’s easy to end up in a situation where you unintentionally upgrade an application that shouldn’t be upgraded.
Or more generally, what if you want to install an application and leave it be? If an application works, any change in its libraries or the versions of those libraries can break the application.
Also, what if you can’t install packages into the global site-packages directory? For instance, on a shared host.
In all these cases, virtual environments can help you. They have their own installation directories and they don’t share libraries with other virtual environments.
Currently, there are two common tools for creating Python virtual environments:
venv is available by default in Python 3.3 and later, and installs pip and setuptools into created virtual environments in Python 3.4 and later.
virtualenv needs to be installed separately, but supports Python 2.7+ and Python 3.3+, and pip , setuptools and wheel are always installed into created virtual environments by default (regardless of Python version).
Источник
DLL & Python
Недавно меня заинтересовала тема использования DLL из Python. Кроме того было интересно разобраться в их структуре, на тот случай, если придется менять исходники библиотек. После изучения различных ресурсов и примеров на эту тему, стало понятно, что применение динамических библиотек может сильно расширить возможности Python. Собственные цели были достигнуты, а чтобы опыт не был забыт, я решил подвести итог в виде статьи — структурировать свой знания и полезные источники, а заодно ещё лучше разобраться в данной теме.
Под катом вас ожидает статья с различными примерами, исходниками и пояснениями к ним.
Содержание
Надеюсь из содержания немного станет понятнее какую часть нужно открыть, чтобы найти ответы на свои вопросы.
Структура 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 — библиотека загружена с неявной компоновкой.
- Аргумент hinstDLL содержит описатель экземпляра DLL. Любому EXE- или DLL-модулю, загружаемому в адресное пространство процесса, присваивается уникальный описатель экземпляра.
О явной и неявной компоновке можно прочесть подробно в статье: Связывание исполняемого файла с библиотекой 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.
Источник