Сообщить об ошибке.

Примеры вызова функций языка C, модуль ctypes в Python

Вызов функций из загруженных C библиотек

Функции загруженных 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*).
  • Целые числа Python передаются как тип C 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_.