Функции загруженных DLL объектов можно вызывать как обычные вызываемые объекты Python. В примере ниже используется функция time()
, которая возвращает системное время в секундах с начала эпохи Unix, и функция GetModuleHandleA()
, которая возвращает дескриптор модуля win32
.
В этом примере обе функции вызываются с указателем NULL
(не следует использовать None
как указатель на NULL
):
>>> from ctypes import * # в Linux необходимо загрузить # C-библиотеку следующим образом: # libc = CDLL("libc.so.6") # смотрите раздел "Загрузка C-библиотек" >>> c_time = libc.time(None) >>> print(c_time) # 1609952646 # далее только для Windows >>> descript = hex(windll.kernel32.GetModuleHandleA(None))) >>> print(descript) # 0x1d000000
При вызове функции stdcall с нарушением соглашения о вызовах cdecl
, или наоборот возникает ValueError
:
>>> from ctypes import * >>> cdll.kernel32.GetModuleHandleA(None) # Traceback (most recent call last): # File "<stdin>", line 1, in <module> # ValueError: Procedure probably called with not enough arguments (4 bytes missing) >>> windll.msvcrt.printf(b"spam") # Traceback (most recent call last): # File "<stdin>", line 1, in <module> # ValueError: Procedure probably called with too many arguments (4 bytes in excess)
Чтобы узнать правильное соглашение о вызовах, нужно заглянуть в заголовочный файл C или в документацию для функции, которую необходимо вызвать.
Когда функции вызываются с недопустимыми значениями аргументов, в Windows модуль ctypes
использует структурированную обработку исключений win32
для предотвращения сбоев из-за общих сбоев защиты:
>>> from ctypes import * >>> windll.kernel32.GetModuleHandleA(32) # Traceback (most recent call last): # File "<stdin>", line 1, in <module> # OSError: exception: access violation reading 0x00000020
Существует достаточно способов сломать Python с помощью модуля ctypes
, так что в любом случае следует быть осторожными. При отладке сбоев (например, из-за ошибок сегментации, вызванных ошибочными вызовами библиотеки C), может быть полезен модуль faulthandler
.
Объекты None
, целые числа, байтовые объекты и строки Unicode, это единственные собственные объекты Python, которые можно напрямую использовать в качестве параметров в вызовах этих функций. Чтобы соответствовать типу языка C, для платформ по умолчанию, их значение замаскировано.
None
передается как указатель C NULL
, char*
или wchar_t*
). int
, Обратите внимание, что функция printf()
печатает в реальном стандартном канале вывода, а не в sys.stdout
, поэтому эти примеры будут работать только в приглашении консоли, а не из среды IDLE или PythonWin:
>>> from ctypes import * >>> printf = libc.printf >>> printf(b"Hello, %s\n", b"World!") # Hello, World! # 14 >>> printf(b"Hello, %S\n", "World!") # Hello, World! # 14 >>> printf(b"%d bottles of beer\n", 42) # 42 bottles of beer # 19 >>> printf(b"%f bottles of beer\n", 42.5) # Traceback (most recent call last): # File "<stdin>", line 1, in <module> # ArgumentError: argument 2: exceptions.TypeError: Don't know how to convert parameter 2 >>>
Все типы Python, кроме целых чисел, строк и байтов, должны быть обернуты в соответствующие им типы модуля ctypes
, чтобы их можно было преобразовать в требуемый тип данных C.
>>> from ctypes import * >>> printf = libc.printf >>> printf(b"An int %d, a double %f\n", 1234, c_double(3.14)) # An int 1234, a double 3.140000 # 31
Также можно настроить преобразование аргументов ctypes
, чтобы разрешить использование экземпляров собственных классов в качестве аргументов функции. Модуль ctypes
ищет атрибут _as_parameter_
и использует его в качестве аргумента функции. Конечно, это должно быть целое число, строка или байты:
>>> from ctypes import * >>> printf = libc.printf >>> class Bottles: ... def __init__(self, number): ... self._as_parameter_ = number ... >>> bottles = Bottles(42) >>> printf(b"%d bottles of beer\n", bottles) # 42 bottles of beer # 19
Если не нужно хранить данные экземпляра в переменной экземпляра _as_parameter_
, то можно определить дескриптор property()
, который делает атрибут доступным по запросу.
Можно указать требуемые типы аргументов функций, экспортируемых из DLL, установив атрибут argtypes
.
Атрибут .argtypes
должны быть последовательностью типов данных языка C. Функция printf()
, не является хорошим примером, потому что она принимает номер переменной и различные типы параметров в зависимости от строки формата, с другой стороны это довольно удобно для экспериментов с этой функцией:
>>> from ctypes import * >>> printf = libc.printf >>> printf.argtypes = [c_char_p, c_char_p, c_int, c_double] >>> printf(b"String '%s', Int %d, Double %f\n", b"Hi", 10, 2.2) # String 'Hi', Int 10, Double 2.200000 # 37
Указание формата защищает от несовместимых типов аргументов (так же, как прототип функции C) и пытается преобразовать аргументы в допустимые типы:
>>> printf(b"%d %d %d", 1, 2, 3) # Traceback (most recent call last): # File "<stdin>", line 1, in <module> # ArgumentError: argument 2: exceptions.TypeError: wrong type >>> printf(b"%s %d %f\n", b"X", 2, 3) # X 2 3.000000 # 13
Если определить свои собственные классы, которые передаются вызовам функций, то необходимо реализовать метод класса from_param()
, чтобы они могли использовать их в последовательности .rgtypes
.
Метод класса from_param()
получает объект Python, переданный в вызов функции. Этот метод должен выполнить проверку и определить, что этот объект приемлем, а затем вернуть сам объект, его атрибут _as_parameter_
или то, что нужно передать в качестве аргумента функции C. Опять же, результатом должно быть целое число, строка, байты, экземпляр модуля ctypes
или объект с атрибутом _as_parameter_
.