ctypes — Python библиотека внешних функций


ctypes — Python библиотека внешних функций. Она предоставляет C-совместимые типы данных и позволяет вызывать функции из DLL или разделяемых библиотек. Её можно использовать для оборачивания этих библиотек в чистый Python.

Учебник по ctypes

Примечание. Примеры кода в данном учебнике используют doctest, чтобы убедиться в их работоспособности. Поскольку некоторые образцы кода ведут себя по-разному в Linux, Windows или Mac OS X, они содержат директивы doctest в комментариях.

Примечание. Некоторые примеры кода ссылаются на тип ctypes c_int. На платформах, где sizeof(long) == sizeof(int) является псевдонимом c_long. Таким образом, вы не должны запутаться, если будет напечатан c_long, а вы ожидаете, что это c_int,— они на самом деле одного типа.

Доступ к функциям из загруженных dll

Функции доступны как атрибуты объектов dll:

>>> from ctypes import *
>>> libc.printf
<_FuncPtr object at 0x...>
>>> print(windll.kernel32.GetModuleHandleA)  
<_FuncPtr object at 0x...>
>>> print(windll.kernel32.MyOwnFunction)     
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "ctypes.py", line 239, in __getattr__
    func = _StdcallFuncPtr(name, self)
AttributeError: function 'MyOwnFunction' not found
>>>

Обратите внимание, что win32 системные dll (например, kernel32 и user32) часто экспортируют ANSI, а также Юникод версии функции. Юникод версия экспортируется с добавлением к имени W, а версия ANSI экспортируется с добавлением к имени A. У функции win32 GetModuleHandle, которая возвращает обработчик модуля для данного имени модуля, есть следующий прототип C и макрос, используемый для предоставления одного из них как GetModuleHandle в зависимости от того, определен ли Юникод или нет:

/* ANSI версия */
HMODULE GetModuleHandleA(LPCSTR lpModuleName);
/* UNICODE версия */
HMODULE GetModuleHandleW(LPCWSTR lpModuleName);

windll не пытается выбрать один из них волшебным образом, вы должны получить доступ к нужной версии, явно указав GetModuleHandleA или GetModuleHandleW, а затем вызвать её с байтами или строковыми объектами соответственно.

Иногда dll экспортирует функции с именами, которые не являются действительными идентификаторами Python, например "??2@YAPAXI@Z". В этом случае вы должны использовать getattr(), чтобы запросить функцию:

>>> getattr(cdll.msvcrt, "??2@YAPAXI@Z")  
<_FuncPtr object at 0x...>
>>>

В Windows некоторые dll экспортируют функции не по имени, а по порядковому номеру. Доступ к этим функциям можно получить по индексу объекта dll с порядковым номером:

>>> cdll.kernel32[1]  
<_FuncPtr object at 0x...>
>>> cdll.kernel32[0]  
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "ctypes.py", line 310, in __getitem__
    func = _StdcallFuncPtr(name, self)
AttributeError: function ordinal 0 not found
>>>

Вызов функций

Вы можете вызвать эти функции как любую другую Python вызываемую функцию. В этом примере используется функция time(), которая возвращает системное время в секундах с момента начала эпохи Unix, и функция GetModuleHandleA(), возвращающая дескриптор модуля win32.

Далее пример вызывает обе функции с NULL указателем (None должен использоваться как указатель на NULL):

>>> print(libc.time(None))  
1150640792
>>> print(hex(windll.kernel32.GetModuleHandleA(None)))  
0x1d000000
>>>

Вызывается ValueError, когда вызывается функция stdcall с соглашением о вызовах cdecl, или наоборот:

>>> 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, чтобы предотвратить сбои общей защиты при вызове функций с недопустимыми значениями аргументов:

>>> windll.kernel32.GetModuleHandleA(32)  
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OSError: exception: access violation reading 0x00000020
>>>

Есть, однако, достаточно способов крушения Python с ctypes, таким образом, вы должны проявлять осторожность. Модуль faulthandler может быть полезен при отладке сбоев (например, из-за сбоев сегментации, вызванных ошибочными вызовами библиотеки C).

None, целые числа, байты объектов и (Юникод) строки являются единственными собственными объектами Python, которые могут непосредственно использоваться в качестве параметров в вызовах функции. None передается как указатель C NULL, байты объектов и строки передаются как указатель на блок памяти, который содержит их данные (char * или: c:type:wchar_t *). Python целые числа передаются как тип платформы по умолчанию C int, их значение маскируется для соответствия типу C.

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

Фундаментальные типы данных

ctypes определяет ряд примитивных C-совместимых типов данных:

тип ctypes тип C тип Python
c_bool _Bool bool (1)
c_char char 1-символьный байтовый объект
c_wchar wchar_t 1-символьная строка
c_byte char int
c_ubyte unsigned char int
c_short short int
c_ushort unsigned short int
c_int int int
c_uint unsigned int int
c_long long int
c_ulong unsigned long int
c_longlong __int64 or long long int
c_ulonglong unsigned __int64 or unsigned long long int
c_size_t size_t int
c_ssize_t ssize_t or Py_ssize_t int
c_float float float
c_double double float
c_longdouble long double float
c_char_p char * (оканчивающийся на NUL) байтовый объект или None
c_wchar_p wchar_t * (оканчивающийся на NUL) string или None
c_void_p void * int или None
  1. Конструктор принимает любой объект с истинным значением.

Все перечисленные типы могут быть созданы путём вызова их с помощью дополнительного инициализатора правильного типа и значения:

>>> c_int()
c_long(0)
>>> c_wchar_p("Hello, World")
c_wchar_p(140018365411392)
>>> c_ushort(-3)
c_ushort(65533)
>>>

Поскольку эти типы являются изменяемыми, их значение также могут измениться впоследствии:

>>> i = c_int(42)
>>> print(i)
c_long(42)
>>> print(i.value)
42
>>> i.value = -99
>>> print(i.value)
-99
>>>

Присвоение нового значения экземплярам типов указателей c_char_p, c_wchar_p и c_void_p изменяет адрес памяти, на который они указывают, а не содержимое блока памяти (конечно, нет, потому что байтовые объекты Python неизменяемы):

>>> s = "Hello, World"
>>> c_s = c_wchar_p(s)
>>> print(c_s)
c_wchar_p(139966785747344)
>>> print(c_s.value)
Hello World
>>> c_s.value = "Hi, there"
>>> print(c_s)              # место в памяти изменилось
c_wchar_p(139966783348904)
>>> print(c_s.value)
Hi, there
>>> print(s)                # первый объект остается неизменным
Hello, World
>>>

Однако следует быть осторожным, чтобы не передавать их функциям, ожидающим указатели в изменяемой памяти. Если вам нужны изменяемые блоки памяти, ctypes содержит функцию create_string_buffer(), которая создаёт их различными способами. К текущему содержимому блока памяти можно обращаться (или изменять) с помощью raw свойства; если вы хотите получить доступ к нему как к строке оканчивающейся на NUL, используйте свойство value:

>>> from ctypes import *
>>> p = create_string_buffer(3)            # создание 3-байтового буфера, инициализированного NUL байтами
>>> print(sizeof(p), repr(p.raw))
3 b'\x00\x00\x00'
>>> p = create_string_buffer(b"Hello")     # создание буфера, содержащего строку оканчивающуюся NUL
>>> print(sizeof(p), repr(p.raw))
6 b'Hello\x00'
>>> print(repr(p.value))
b'Hello'
>>> p = create_string_buffer(b"Hello", 10) # создание 10 байт буфера
>>> print(sizeof(p), repr(p.raw))
10 b'Hello\x00\x00\x00\x00\x00'
>>> p.value = b"Hi"
>>> print(sizeof(p), repr(p.raw))
10 b'Hi\x00lo\x00\x00\x00\x00\x00'
>>>

Функция create_string_buffer() заменяет функцию c_buffer() (которая по прежнему доступна как псевдоним), а также функцию c_string() из более ранних версий ctypes. Чтобы создать изменяемый блок памяти, содержащий Юникод символы C типа wchar_t, используют функцию create_unicode_buffer().

Вызов функций, продолжение

Обратите внимание, что printf печатает в реальный стандартный поток вывода, не в sys.stdout, таким образом, эти примеры будут работать в консольном приглашении, не из IDLE или PythonWin:

>>> 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:

>>> 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_ и использует его в качестве аргумента функции. Конечно, он должен быть целым числом, строкой или байтом:

>>> 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, вероятно, не является здесь хорошим примером, потому что она принимает переменное число и различные типы параметров в зависимости от строки формата, с другой стороны, это довольно удобно экспериментировать с этой особенностью):

>>> 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(), чтобы они могли использовать их в последовательности argtypes. Метод класса from_param() получает переданный в вызов функции объект Python, далее он должен выполнить проверку типа или всё, что необходимо, чтобы убедиться, что этот объект приемлем, а затем вернуть сам объект, его атрибут _as_parameter_ или всё, что нужно передать в качестве аргумента C функции. Опять же, результатом должно быть целое число, строка, байты, экземпляр ctypes или объект с атрибутом _as_parameter_.

Возвращаемые типы

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

Вот более продвинутый пример, использует функцию strchr, которая ожидает указатель строки и символ, и возвращает указатель на строку:

>>> strchr = libc.strchr
>>> strchr(b"abcdef", ord("d"))  
8059983
>>> strchr.restype = c_char_p    # c_char_p указатель на строку
>>> strchr(b"abcdef", ord("d"))
b'def'
>>> print(strchr(b"abcdef", ord("x")))
None
>>>

Если нужно избежать вышеупомянутых вызовов ord("x"), можно установить атрибут argtypes, и второй аргумент будет преобразован из односимвольного байтового объекта Python в C char:

>>> strchr.restype = c_char_p
>>> strchr.argtypes = [c_char_p, c_char]
>>> strchr(b"abcdef", b"d")
'def'
>>> strchr(b"abcdef", b"def")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ArgumentError: argument 2: exceptions.TypeError: one character string expected
>>> print(strchr(b"abcdef", b"x"))
None
>>> strchr(b"abcdef", b"d")
'def'
>>>

Также можно использовать вызываемый объект Python (например, функцию или класс) в качестве атрибута restype, если внешняя функция возвращает целое число. Вызываемый объект будет вызываться с целым числом, возвращаемым функцией C, и результат этого вызова будет использоваться как результат вызова вашей функции. Это полезно для проверки возвращаемых значений ошибок и автоматического создания исключения:

>>> GetModuleHandle = windll.kernel32.GetModuleHandleA  
>>> def ValidHandle(value):
...     if value == 0:
...         raise WinError()
...     return value
...
>>>
>>> GetModuleHandle.restype = ValidHandle  
>>> GetModuleHandle(None)  
486539264
>>> GetModuleHandle("something silly")  
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in ValidHandle
OSError: [Errno 126] The specified module could not be found.
>>>

WinError — это функция, которая вызывает API-интерфейс Windows FormatMessage() для получения строкового представления кода ошибки и возвращения исключения. WinError принимает необязательный параметр кода ошибки, если он не используется, она вызывает GetLastError() для его получения.

Обратите внимание, что гораздо более мощный механизм проверки ошибок доступен через атрибут errcheck; для получения дополнительной информации см. справочное руководство.

Передача указателей (или передача параметров по ссылке)

Иногда функция C api ожидает указатель на тип данных в качестве параметра, вероятно, для записи в соответствующее местоположение, или если данные слишком велики для передачи значения. Это также известно как передача параметров по ссылке.

ctypes экспортирует функцию byref(), которая используется, чтобы передать параметры по ссылке. Тот же эффект может быть достигнут с функцией pointer(), хотя pointer() делает гораздо больше работы, т. к. она создаёт реальный объект указателя, поэтому он быстрее использования byref(), если вы не нуждаетесь в объекте указателя в самом Python:

>>> i = c_int()
>>> f = c_float()
>>> s = create_string_buffer(b'\000' * 32)
>>> print(i.value, f.value, repr(s.value))
0 0.0 b''
>>> libc.sscanf(b"1 3.14 Hello", b"%d %f %s",
...             byref(i), byref(f), s)
3
>>> print(i.value, f.value, repr(s.value))
1 3.1400001049 b'Hello'
>>>

Структуры и объединения

Структуры и объединения должны произойти от базовых Structure и Union классов, которые определены в модуле ctypes. Каждый подкласс должен определить _fields_ атрибут. _fields_ должен быть списком 2 кортежей, содержащим имя поля и тип поля.

Тип поля должен быть типом ctypes, как c_int, или любым другим производным типом ctypes: структура, объединение, массив, указатель.

Вот простой пример структуры POINT, которая содержит два целых числа с именами x и y, а также показывает, как инициализировать структуру в конструкторе:

>>> from ctypes import *
>>> class POINT(Structure):
...     _fields_ = [("x", c_int),
...                 ("y", c_int)]
...
>>> point = POINT(10, 20)
>>> print(point.x, point.y)
10 20
>>> point = POINT(y=5)
>>> print(point.x, point.y)
0 5
>>> POINT(1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: too many initializers
>>>

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

Вот структура RECT, которая содержит два POINT с именами upperleft и lowerright:

>>> class RECT(Structure):
...     _fields_ = [("upperleft", POINT),
...                 ("lowerright", POINT)]
...
>>> rc = RECT(point)
>>> print(rc.upperleft.x, rc.upperleft.y)
0 5
>>> print(rc.lowerright.x, rc.lowerright.y)
0 0
>>>

Вложенные структуры также можно инициализировать в конструкторе несколькими способами:

>>> r = RECT(POINT(1, 2), POINT(3, 4))
>>> r = RECT((1, 2), (3, 4))

Поле дескриптора может извлекаться из класса. Они полезны для отладки, т. к. могут предоставить полезную информацию:

>>> print(POINT.x)
<Field type=c_long, ofs=0, size=4>
>>> print(POINT.y)
<Field type=c_long, ofs=4, size=4>
>>>

Предупреждение

ctypes не поддерживает передачу объединения или структуры с битовыми полями в функции по значению. Хотя это может работать на 32-разрядных x86, библиотека не гарантирует работу в общем случае. Объединения и структуры с битовыми полями всегда должны передаваться функциям по указателю.

Выравнивание структуры/объединения и порядок байтов

По умолчанию поля Structure и Union выравниваются таким же образом, как это делает компилятор C. Возможно переопределить это поведение, определив _pack_ класс атрибут в определении подкласса. Значение должно быть положительным целым числом и указывать максимальное выравнивание для полей. Это то, что #pragma pack(n) также делает в MSVC.

ctypes использует собственный порядок байтов для структур и объединений. Для построения структур с неродным порядком байтов можно использовать один из BigEndianStructure, LittleEndianStructure, BigEndianUnion и LittleEndianUnion базовые классы. Эти классы не могут содержать поля указателя.

Битовые поля в структурах и объединениях

Можно создавать структуры и объединения, содержащие битовые поля. Битовые поля возможны только для целых полей, битовая ширина указывается в качестве третьего элемента в кортежах _fields_:

>>> class Int(Structure):
...     _fields_ = [("first_16", c_int, 16),
...                 ("second_16", c_int, 16)]
...
>>> print(Int.first_16)
<Field type=c_long, ofs=0:0, bits=16>
>>> print(Int.second_16)
<Field type=c_long, ofs=0:16, bits=16>
>>>

Массивы

Массивы — это последовательности, содержащие фиксированное число экземпляров одного типа.

Рекомендуется создавать типы массивов путём умножения типа данных на положительное целое число:

TenPointsArrayType = POINT * 10

Вот пример некоторого искусственного типа данных, структуры, содержащей 4 POINT среди прочего:

>>> from ctypes import *
>>> class POINT(Structure):
...     _fields_ = ("x", c_int), ("y", c_int)
...
>>> class MyStruct(Structure):
...     _fields_ = [("a", c_int),
...                 ("b", c_float),
...                 ("point_array", POINT * 4)]
>>>
>>> print(len(MyStruct().point_array))
4
>>>

экземпляры создаются обычным способом, путём вызова класса:

arr = TenPointsArrayType()
for pt in arr:
    print(pt.x, pt.y)

Вышеуказанные код печатают ряд строк 0 0, поскольку содержимое массива инициализируется нулями.

Также можно указать инициализаторы правильного типа:

>>> from ctypes import *
>>> TenIntegers = c_int * 10
>>> ii = TenIntegers(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
>>> print(ii)
<c_long_Array_10 object at 0x...>
>>> for i in ii: print(i, end=" ")
...
1 2 3 4 5 6 7 8 9 10
>>>

Указатели

Экземпляры указателя создаются путём вызова функции pointer() для типа ctypes:

>>> from ctypes import *
>>> i = c_int(42)
>>> pi = pointer(i)
>>>

У экземпляров указателя есть возвращающий объект атрибут contents, на который указывает указатель, объекта i выше:

>>> pi.contents
c_long(42)
>>>

Обратите внимание, что ctypes не содержит OOR (возвращение исходного объекта), он создаёт новый эквивалентный объект при каждом извлечении атрибута:

>>> pi.contents is i
False
>>> pi.contents is pi.contents
False
>>>

Назначение другой c_int экземпляры содержимому указателя атрибута приведёт к указанию указателя на расположение памяти, в котором он хранится:

>>> i = c_int(99)
>>> pi.contents = i
>>> pi.contents
c_long(99)
>>>

Экземпляры указателей также можно индексировать целыми числами:

>>> pi[0]
99
>>>

Назначение целочисленному индексу приводит к изменению указанного значения:

>>> print(i)
c_long(99)
>>> pi[0] = 22
>>> print(i)
c_long(22)
>>>

Также можно использовать индексы, отличные от 0, но вы должны знать, что вы делаете, так же, как в C: вы можете получить доступ или изменить произвольные местоположения памяти. Как правило, функция используется только в том случае, если вы получаете указатель от функции C, и вы знаете, что указатель фактически указывает на массив вместо одного элемента.

За кулисами функция pointer() делает больше, чем просто создание экземпляров указателя, — она должна сначала создать типы указателей. Это делается с помощью функции POINTER(), которая принимает любой тип ctypes и возвращает новый тип:

>>> PI = POINTER(c_int)
>>> PI
<class 'ctypes.LP_c_long'>
>>> PI(42)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: expected c_long instead of int
>>> PI(c_int(42))
<ctypes.LP_c_long object at 0x...>
>>>

Вызов типа указателя без аргумента создаёт указатель NULL. У NULL указателей логическое значение False:

>>> null_ptr = POINTER(c_int)()
>>> print(bool(null_ptr))
False
>>>

ctypes проверяет наличие NULL при различии указателей (но при расхождении недопустимых указателей, отличных от NULL, происходит сбой Python):

>>> null_ptr[0]
Traceback (most recent call last):
    ....
ValueError: NULL pointer access
>>>

>>> null_ptr[0] = 1234
Traceback (most recent call last):
    ....
ValueError: NULL pointer access
>>>

Преобразование типов

Обычно ctypes выполняет строгую проверку типов. Это означает, что если у вас есть POINTER(c_int) в списке argtypes функции или как тип поля члена в определении структуры, принимаются только экземпляры одного и того же типа. Есть некоторые исключения из этого правила, когда ctypes принимает другие объекты. Например, вы можете передавать совместимые экземпляры массивов вместо типов указателей. Итак, для POINTER(c_int) ctypes принимает массив c_int:

>>> class Bar(Structure):
...     _fields_ = [("count", c_int), ("values", POINTER(c_int))]
...
>>> bar = Bar()
>>> bar.values = (c_int * 3)(1, 2, 3)
>>> bar.count = 3
>>> for i in range(bar.count):
...     print(bar.values[i])
...
1
2
3
>>>

Кроме того, если аргумент функции явно объявлен как тип указателя (например, POINTER(c_int)) в argtypes, в функцию может быть передан объект типа указатель (c_int в этом случае). В этом случае ctypes автоматически применяет требуемое преобразование byref().

Чтобы задать для поля типа POINTER значение NULL, можно назначить значение None:

>>> bar.values = None
>>>

Иногда существуют экземпляры несовместимых типов. В C можно привести один тип в другой. ctypes предоставляет функцию cast(), которая может использоваться таким же образом. Структура Bar, определенная выше, принимает указатели POINTER(c_int) или массив c_int для его поля values, но не экземпляры других типов:

>>> bar.values = (c_byte * 4)()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: incompatible types, c_byte_Array_4 instance instead of LP_c_long instance
>>>

Для этих случаев удобна функция cast().

Функция cast() может использоваться, чтобы привести ctypes экземпляр в указатель на другой ctypes тип данных. cast() принимает два параметра, объект ctypes, который является или может быть преобразован в указатель какого-либо типа, и тип указателя ctypes. Возвращает экземпляр второго аргумента, который ссылается на тот же блок памяти, что и первый аргумент:

>>> a = (c_byte * 4)()
>>> cast(a, POINTER(c_int))
<ctypes.LP_c_long object at ...>
>>>

Итак, cast() можно использовать для присвоения полю values структуры Bar:

>>> bar = Bar()
>>> bar.values = cast((c_byte * 4)(), POINTER(c_int))
>>> print(bar.values[0])
0
>>>

Неполные типы

Неполные типы являются структурами, объединениями или массивами, члены которых ещё не определены. В C они определяются прямыми объявлениями, которые определяются позже:

struct cell; /* прямая декларация */

struct cell {
    char *name;
    struct cell *next;
};

Прямой перевод в код ctypes будет таким, но он не работает:

>>> class cell(Structure):
...     _fields_ = [("name", c_char_p),
...                 ("next", POINTER(cell))]
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in cell
NameError: name 'cell' is not defined
>>>

потому что новый class cell недоступен в самом операторе class. В ctypes можно определить cell класс и установить _fields_ атрибут позже, после оператора class:

>>> from ctypes import *
>>> class cell(Structure):
...     pass
...
>>> cell._fields_ = [("name", c_char_p),
...                  ("next", POINTER(cell))]
>>>

Проверим это. Создадим два экземпляра cell и позволяем им указывать друг на друга и в завершении, проследовать за цепочкой указателей несколько раз:

>>> c1 = cell()
>>> c1.name = "foo"
>>> c2 = cell()
>>> c2.name = "bar"
>>> c1.next = pointer(c2)
>>> c2.next = pointer(c1)
>>> p = c1
>>> for i in range(8):
...     print(p.name, end=" ")
...     p = p.next[0]
...
foo bar foo bar foo bar foo bar
>>>

Колбэк функции

ctypes позволяет создавать указатели вызываемых функции C из Python вызова. Их иногда называют колбэк функции.

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

Функция фабрика CFUNCTYPE() создаёт типы для функций колбэков с использованием соглашения о вызовах cdecl. В Windows функция фабрика WINFUNCTYPE() создаёт типы для колбэк функций с помощью соглашения о вызовах stdcall.

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

Я предлагаю рассмотреть использующий стандартную функцию библиотеки C qsort() пример, которая используется для сортировки элементов с помощью колбэк функции. qsort() будет использоваться для сортировки массива целых чисел:

>>> IntArray5 = c_int * 5
>>> ia = IntArray5(5, 1, 7, 33, 99)
>>> qsort = libc.qsort
>>> qsort.restype = None
>>>

qsort() должен вызываться с указателем на сортируемые данные, количество элементов в массиве данных, размер одного элемента и указатель на функцию сравнения, т.е. колбэк. Затем колбэк вызывается с двумя указателями на элементы и должен возвращать отрицательное целое число, если первый элемент меньше второго, ноль, если они равны, и положительное целое число в противном случае.

Так колбэк функция получает указатели на целые числа, и должна возвращать целое число. Сначала создадим type для колбэк функции:

>>> CMPFUNC = CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))
>>>

Для начала, далее представлен простой колбэк, показывающий переданные ему значения:

>>> def py_cmp_func(a, b):
...     print("py_cmp_func", a[0], b[0])
...     return 0
...
>>> cmp_func = CMPFUNC(py_cmp_func)
>>>

Результат:

>>> qsort(ia, len(ia), sizeof(c_int), cmp_func)  
py_cmp_func 5 1
py_cmp_func 33 99
py_cmp_func 7 33
py_cmp_func 5 7
py_cmp_func 1 7
>>>

Теперь мы можем сравнить два элемента и вернуть полезный результат:

>>> def py_cmp_func(a, b):
...     print("py_cmp_func", a[0], b[0])
...     return a[0] - b[0]
...
>>>
>>> qsort(ia, len(ia), sizeof(c_int), CMPFUNC(py_cmp_func)) 
py_cmp_func 5 1
py_cmp_func 33 99
py_cmp_func 7 33
py_cmp_func 1 7
py_cmp_func 5 7
>>>

Как мы можем легко проверить, наш массив теперь отсортирован:

>>> for i in ia: print(i, end=" ")
...
1 5 7 33 99
>>>

Фабрики функций могут использоваться как фабрики-декораторы, так что допустимо следующее:

>>> @CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))
... def py_cmp_func(a, b):
...     print("py_cmp_func", a[0], b[0])
...     return a[0] - b[0]
...
>>> qsort(ia, len(ia), sizeof(c_int), py_cmp_func)
py_cmp_func 5 1
py_cmp_func 33 99
py_cmp_func 7 33
py_cmp_func 1 7
py_cmp_func 5 7
>>>

Примечание

Убедитесь, что ссылки на объекты CFUNCTYPE() сохраняются до тех пор, пока они используются из C кода. В ctypes их нет, а если нет, то они могут быть мусором, разрушающим вашу программу при создании колбэков.

Кроме того, следует отметить, что если функция колбэк вызывается в потоке, созданном вне Python управления (например, внешним коде, вызывающим колбэк), ctypes создаёт новый фиктивный Python поток при каждом вызове. Это поведение правильно в большинстве применений, но это означает, что значения, находящиеся в threading.local, не будут выживать в различных вызовах, даже когда эти вызовы сделаны от того же C потока.

Доступ к значениям, экспортированным из dll

Некоторые разделяемые библиотеки не только экспортируют функции, но и экспортируют переменные. Примером в самой библиотеке Python является Py_OptimizeFlag, целое число, равное 0, 1 или 2, в зависимости от флага -O или -OO, заданного при запуске.

ctypes может обращаться к подобным значениям с помощью in_dll() методов класса типа. pythonapi является предопределенным символом, предоставляющим доступ к C API Python:

>>> opt_flag = c_int.in_dll(pythonapi, "Py_OptimizeFlag")
>>> print(opt_flag)
c_long(0)
>>>

Если бы интерпретатор начинался с -O, образец был бы напечатан c_long(1), или c_long(2), если бы был указан -OO.

Расширенный пример, который также демонстрирует использование указателей доступа PyImport_FrozenModules экспортируемому указателю Python.

Цитата из документации:

Указатель инициализируется, чтобы указать на массив записей struct _frozen, заканчивающийся одной, все члены которой равны NULL или нулю. При импорте замороженного модуля, его поиск выполняется в этой таблице. Сторонний код может сыграть с ним злую шутку, чтобы предоставить динамически создаваемую коллекцию замороженных модулей.

Поэтому манипулирование указателем может оказаться даже полезным. Чтобы ограничить размер примера, мы покажем только то, как можно прочитать таблицу с помощью ctypes:

>>> from ctypes import *
>>>
>>> class struct_frozen(Structure):
...     _fields_ = [("name", c_char_p),
...                 ("code", POINTER(c_ubyte)),
...                 ("size", c_int)]
...
>>>

После определения тип данных struct _frozen, можем получить указатель на таблицу:

>>> FrozenTable = POINTER(struct_frozen)
>>> table = FrozenTable.in_dll(pythonapi, "PyImport_FrozenModules")
>>>

Поскольку table является указателем для массива записей struct_frozen, мы можем перебирать его, но нам нужно убедиться, что наш цикл завершается, потому что указатели не имеют размера. Рано или поздно он, вероятно, выйдет из строя из-за нарушения доступа или чего-то ещё, поэтому лучше выйти из цикла, когда мы попадём на NULL запись:

>>> for item in table:
...     if item.name is None:
...         break
...     print(item.name.decode("ascii"), item.size)
...
_frozen_importlib 31764
_frozen_importlib_external 41499
__hello__ 161
__phello__ -161
__phello__.spam 161
>>>

Тот факт, что стандартный Python содержит замороженный модуль и замороженный пакет (обозначенный отрицательным атрибутом size) не очень хорошо известен, он используется только для тестирования. Проверьте это сами с import __hello__.

Неожиданности

Существуют некоторые странности в ctypes, когда ожидается одно, а по факту происходит что-то другое.

Рассмотрим следующий пример:

>>> from ctypes import *
>>> class POINT(Structure):
...     _fields_ = ("x", c_int), ("y", c_int)
...
>>> class RECT(Structure):
...     _fields_ = ("a", POINT), ("b", POINT)
...
>>> p1 = POINT(1, 2)
>>> p2 = POINT(3, 4)
>>> rc = RECT(p1, p2)
>>> print(rc.a.x, rc.a.y, rc.b.x, rc.b.y)
1 2 3 4
>>> #  теперь поменяем местами две точки
>>> rc.a, rc.b = rc.b, rc.a
>>> print(rc.a.x, rc.a.y, rc.b.x, rc.b.y)
3 4 3 4
>>>

Мы, ожидали, что последний оператор напечатает 3 4 1 2. Что случилось? Выполним по шагам вышеуказанную строку rc.a, rc.b = rc.b, rc.a:

>>> temp0, temp1 = rc.b, rc.a
>>> rc.a = temp0
>>> rc.b = temp1
>>>

Обратите внимание, что temp0 и temp1 являются объектами, по-прежнему использующими внутренний буфер указанного выше объекта rc. Таким образом, выполнение rc.a = temp0 копирует содержимое буфера temp0 в буфер rc. Это, в свою очередь, меняет содержание temp1. Таким образом, последнее назначение rc.b = temp1, не содержит ожидаемого эффекта.

Имейте в виду, что получение подобъектов из Structure, Unions и Arrays не копирует подобъект, вместо этого он получает объект-оболочку, обращающийся к базовому буферу корневого объекта.

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

>>> s = c_char_p()
>>> s.value = b"abc def ghi"
>>> s.value
b'abc def ghi'
>>> s.value is s.value
False
>>>

Примечание

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

Почему он печатает False? ctypes экземпляры — это объекты, содержащие блок памяти и некоторые дескриптор’ы, обращающиеся к содержимому памяти. Сохранение объекта Python в блоке памяти не сохраняет сам объект, вместо этого сохраняется содержимое объекта. При повторном доступе к содержимому, каждый раз будет создаваться новый объект Python!

Типы данных переменного размера

ctypes предоставляет некоторую поддержку массивов и структур переменного размера.

Функцию resize() можно использовать для изменения размера буфера памяти существующего объекта ctypes. Функция принимает объект в качестве первого аргумента и запрашиваемый размер в байтах в качестве второго аргумента. Блок памяти не может быть меньше, чем естественный блок памяти, указанный типом объекта, при такой попытке возникает ValueError:

>>> short_array = (c_short * 4)()
>>> print(sizeof(short_array))
8
>>> resize(short_array, 4)
Traceback (most recent call last):
    ...
ValueError: minimum size is 8
>>> resize(short_array, 32)
>>> sizeof(short_array)
32
>>> sizeof(type(short_array))
8
>>>

Это хорошо и замечательно, но как получить доступ к дополнительным элементам, содержащимся в этом массиве? Поскольку тип всё ещё знает только о 4 элементах, мы получаем ошибки при доступе к другим элементам:

>>> short_array[:]
[0, 0, 0, 0]
>>> short_array[7]
Traceback (most recent call last):
    ...
IndexError: invalid index
>>>

Другой способ использования типов данных переменного размера с ctypes - использовать динамическую природу Python и (повторно) определять тип данных после того, как требуемый размер уже известен, в каждом случае.

Справочник ctypes

Поиск разделяемых библиотек

При программировании на компилируемом языке доступ к разделяемым библиотекам осуществляется при компиляции/линковке программы и при запуске программы.

Цель функции find_library() состоит в том, чтобы определить местонахождение библиотеки, в некотором роде подобной тому, что делают компилятор или загрузчик во время выполнения (на платформах с несколькими версиями общей библиотеки, должна быть загружена самая новая), в то время как ctypes загрузчики библиотеки действуют, когда программа работает и вызов загрузчика во время непосредственного выполнения.

Модуль ctypes.util предоставляет функцию, которая может помочь определить загружаемую библиотеку.

ctypes.util.find_library(name)

Попытаться найти библиотеку и вернуть её путь. name — имя библиотеки без префикса типа lib, суффикса типа .so, .dylib или номера версии (это форма используется для опции posix линкера -l). Если не удается найти библиотеку, возвращает значение None.

Точная функциональность зависит от системы.

В Linux find_library() пытается запустить внешние программы (/sbin/ldconfig, gcc, objdump и ld), чтобы найти файл библиотеки. Возвращает имя файла библиотеки.

Изменено в версии 3.6: В Linux значение переменной среды LD_LIBRARY_PATH используется при поиске библиотек, если библиотека не может быть найдена каким-либо другим способом.

Далее несколько примеров:

>>> from ctypes.util import find_library
>>> find_library("m")
'libm.so.6'
>>> find_library("c")
'libc.so.6'
>>> find_library("bz2")
'libbz2.so.1.0'
>>>

На OS X, find_library() пытается проверить несколько предопределенных схем именования и путей, чтобы определить местонахождение библиотеки, и возвращает полное имя пути в случае успеха:

>>> from ctypes.util import find_library
>>> find_library("c")
'/usr/lib/libc.dylib'
>>> find_library("m")
'/usr/lib/libm.dylib'
>>> find_library("bz2")
'/usr/lib/libbz2.dylib'
>>> find_library("AGL")
'/System/Library/Frameworks/AGL.framework/AGL'
>>>

В Windows find_library() выполняет поиск по системному пути поиска и возвращает полный путь. Но т. к. нет предопределенной схемы именования, вызов подобного find_library("c") завершится неудачей и возвращает значение None.

Если обернуть разделяемую библиотеку с ctypes, это возможно лучше определения названия разделяемой библиотеки во время разработки и жёстко запрограммировать её в модуль обёртки, вместо того, чтобы использовать find_library() для поиска местонахождения библиотеки во времени выполнения.

Загрузка общих библиотек

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

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

Сущности этого класса представляют загруженные общие библиотеки. Функции в этих библиотеках используют стандартное соглашение о вызове на языке C, и предполагается, что они возвращают int.

В Windows создание экземпляра CDLL может завершиться ошибкой, даже если имя DLL существует. Если зависимая DLL загруженной DLL не найдена, возникает ошибка OSError с сообщением «[WinError 126] Указанный модуль не может быть найден». Это сообщение об ошибке не содержит имя отсутствующей DLL, поскольку Windows API не возвращает эту информацию, что затрудняет диагностику этой ошибки. Чтобы устранить эту ошибку и определить, какая DLL не найдена, вам необходимо найти список зависимых DLL и определить, какая из них не найдена, с помощью средств отладки и трассировки Windows.

См.также

Инструмент от Microsoft DUMPBIN — Программа для поиска зависимостей DLL.

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

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

Изменено в версии 3.3: Используется для вызова исключения WindowsError.

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

Только Windows: экземпляры этого класса представляют загруженные разделяемые библиотеки, функции в этих библиотеках используют соглашение о вызовах stdcall и предполагается, что по умолчанию возвращают int.

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

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

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

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

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

Все эти классы можно инстанцировать, вызвав их хотя бы с одним аргументом — pathname разделяемой библиотеки. Если у вас есть существующий обработчик в уже загруженной общей библиотеке, она может быть передана как именованный handle параметр, иначе используются функции основной платформы dlopen или LoadLibrary для загрузки библиотеки в процесс и получения её обработчика.

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

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

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

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

Параметр winmode, используется на Windows, чтобы определить, как библиотека загружена (т. к. mode игнорируется). Он принимает любое значение, который действителен для Win32 API LoadLibraryEx параметра флагов. Если не указано, то по умолчанию используются флаги, которые приводят к наиболее безопасной загрузке DLL, чтобы избежать таких проблем, как похищение DLL. Передача полного пути к DLL является наиболее безопасным способом обеспечения правильной загрузки библиотеки и зависимостей.

Изменено в версии 3.8: Добавлен параметр winmode.

ctypes.RTLD_GLOBAL

Флаг, используемый в параметре mode. На платформах, где этот флаг недоступен, он определяется как целочисленный ноль.

ctypes.RTLD_LOCAL

Флаг, используемый в параметре mode. На платформах, где он недоступен, это то же самое, что и RTLD_GLOBAL.

ctypes.DEFAULT_MODE

Режим по умолчанию, который используется для загрузки разделяемой библиотеки. В OSX 10.3 это RTLD_GLOBAL, в противном случае оно совпадает с RTLD_LOCAL.

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

>>> from ctypes import CDLL
>>> libc = CDLL("libc.so.6")  # На Linux
>>> libc.time == libc.time
True
>>> libc['time'] == libc['time']
False

Доступны следующие публичные атрибуты, их имя начинается с подчеркивания, чтобы не конфликтовать с экспортируемыми именами функций:

PyDLL._handle

Системный дескриптор используемый для доступа к библиотеке.

PyDLL._name

Имя библиотеки переданной в конструкторе.

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

class ctypes.LibraryLoader(dlltype)

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

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

LoadLibrary(name)

Загрузит общую библиотеку в процесс и возвратит её. Этот метод всегда возвращает новую экземпляр библиотеки.

Также доступны префабричные библиотеки загрузчиков:

ctypes.cdll

Создаёт CDLL экземпляры.

ctypes.windll

Только Windows: создаёт WinDLL экземпляры.

ctypes.oledll

Только Windows: создаёт OleDLL экземпляры.

ctypes.pydll

Создаёт PyDLL экземпляры.

Для непосредственного доступа к C api Python, доступен готовый к использованию разделяемый Python объект библиотеки:

ctypes.pythonapi

Сущность PyDLL, которая выставляет функции Python C API как атрибуты. Обратите внимание, что все функции возвращают C int, что, конечно, не всегда является истиной, поэтому для использования этих функций необходимо назначить правильный restype атрибут.

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

Обращение к функции загруженной библиотеки вызывает событие аудита ctypes.dlsym с аргументами library (объект библиотеки) и name (символическое имя как строка или целое число).

В случаях, когда доступен только обработчик библиотеки, а не объект, доступ к функции вызывает событие аудита ctypes.dlsym/handle с аргументами handle (сырой обработчик библиотеки) и name.

Внешние функции

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

class ctypes._FuncPtr

Базовый класс для вызываемых внешних C функций.

Сущности внешних функций также являются C совместимыми типами данных; они представляют указатели C функций.

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

restype

Назначает тип ctypes для определения типа результата внешней функции. Используйте None для void, если функция ничего не возвращает.

Можно назначить вызываемый объект Python, который не является типом ctypes, в этом случае предполагается, что функция возвращает C int, и вызываемый объект будет вызван с этим целым числом, позволяя дальнейшую обработку или проверку ошибок. Это использование запрещается, поскольку более гибкая пост обработка или проверка ошибок используют ctype тип данных в качестве restype и назначает вызываемый на errcheck атрибут.

argtypes

Назначает кортеж типов ctypes, чтобы указать типы аргументов, которые принимает функция. Функции, использующие соглашение о вызове stdcall, могут вызываться только с тем же количеством аргументов, что и длина этого кортежа; функции, использующие соглашение о вызове C, также принимают дополнительные, не указанные аргументы.

Когда вызывается внешняя функция, каждый фактический аргумент передается методу класса from_param() элементов в кортеже argtypes, этот метод позволяет адаптировать фактический аргумент к объекту, который принимает внешняя функция. Например, элемент c_char_p в кортеже argtypes преобразует строку, переданную в качестве аргумента, в объект байтов, используя правила преобразования ctypes.

Новое: теперь можно поместить элементы в argtypes, которые не являются типами ctypes, но каждый элемент должен содержать возвращающий значение метод from_param(), используемое в качестве аргумента (целое число, строка, экземпляр ctypes). Это позволяет определить адаптеры, которые могут адаптировать пользовательские объекты в качестве функциональных параметров.

errcheck

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

callable(result, func, arguments)

result — это то, что возвращает внешняя функция, как указан restype атрибут.

func является самим объектом внешней функции, что позволяет повторно использовать один и тот же вызываемый объект для проверки или постобработки результатов нескольких функций.

arguments — кортеж, содержащий параметры, первоначально переданные вызову функции, что позволяет специализироваться на поведении используемых аргументов.

Возвращаемый данной функцией объект, будет возвращён из вызова внешней функции, но он также может проверить значение результата и вызвать исключение, если вызов внешней функции не удался.

exception ctypes.ArgumentError

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

В Windows, когда вызов внешней функции вызывает системное исключение (например, из-за нарушения доступа), оно будет захвачено и заменено подходящим исключением Python. Кроме того, будет вызвано событие аудита ctypes.seh_exception с аргументом code, что позволит хуку аудита заменить исключение своим собственным.

Некоторые способы вызова внешних функций могут вызвать события аудита ctypes.call_function с аргументами function pointer и arguments.

Прототипы функции

Внешние функции также могут быть созданы путём создания прототипов функций. Прототипы функций аналогичны прототипам функций в C; они определяют функцию (возвращаемый тип, типы аргументов, соглашение о вызове) без определения реализации. Функции фабрики должны вызываться с требуемым типом результата и типами аргументов функции и могут использоваться как фабрики-декораторы и, как таковые, применяться к функциям через синтаксис @wrapper. Примеры см. в разделе Колбэк функции.

ctypes.CFUNCTYPE(restype, *argtypes, use_errno=False, use_last_error=False)

Прототип возвращенной функции создаёт функции, которые используют стандартное соглашение о вызовах C. Функция освободит GIL во время вызова. Если для use_errno установлено значение true, частная копия ctypes системной переменной errno заменяется реальным значением errno до и после вызова; use_last_error делает то же самое для кода ошибки Windows.

ctypes.WINFUNCTYPE(restype, *argtypes, use_errno=False, use_last_error=False)

Только для Windows: возвращенный прототип функции создаёт функции, использующие соглашение о вызовах stdcall, за исключением Windows CE, где WINFUNCTYPE() совпадает с CFUNCTYPE(). Функция освободит GIL во время вызова. У use_errno и use_last_error то же значение, что и выше.

ctypes.PYFUNCTYPE(restype, *argtypes)

Возвращенный прототип функции создаёт функции, которые используют соглашение о вызове Python. Функция не отпускает GIL во время вызова.

Прототипы функций, созданные этими фабричными функциями, могут создаваться различными способами в зависимости от типа и номера параметров в вызове:

prototype(address)

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

prototype(callable)

Создаёт вызываемую C функцию (функцию колбэк) из Python callable.

prototype(func_spec[, paramflags])

Возвращает внешнюю функцию, экспортированную разделяемой библиотекой. func_spec должен быть кортежем из двух элементов (name_or_ordinal, library). Первый элемент — это имя экспортируемой функции в виде строки или порядкового номера экспортируемой функции в виде малого целого числа. Второй элемент — экземпляр разделяемой библиотеки.

prototype(vtbl_index, name[, paramflags[, iid]])

Возвращает внешнюю функцию, вызывающую COM методом. vtbl_index — индекс в таблице виртуальных функций, небольшое неотрицательное целое число. name — имя COM метода. iid является необязательным указателем на идентификатор интерфейса, который используемый в расширенном отчете об ошибках.

COM методы используют специальное соглашение о вызовах: они требуют указателя на интерфейс COM как первый аргумент, в дополнение к тем параметрам, которые определены в кортеже argtypes.

Дополнительный параметр paramflags создаёт внешние функциональные обёртки с гораздо большей функциональностью, чем описанные выше функции.

paramflags должен быть кортежем той же длины, что и argtypes.

Каждый элемент этого кортежа содержит дополнительную информацию о параметре, он должен быть кортежем, содержащим один, два или три элемента.

Первый элемент представляет собой целое число, содержащее комбинацию флагов направления для параметра:

1
Задаёт входной параметр для функции.
2
Выходной параметр. Внешняя функция заполняет значение.
4
Входной параметр, который по умолчанию равен целому нулю.

Необязательным вторым элементом является имя параметра как строка. Если он указан, внешняя функция может быть вызвана с именованными параметрами.

Дополнительный третий элемент — значение по умолчанию для этого параметра.

Далее пример демонстрирует, как обернуть функцию Windows MessageBoxW так, чтобы она поддерживала параметры по умолчанию и именованные аргументы. Объявление C из заголовочного файла Windows таково:

WINUSERAPI int WINAPI
MessageBoxW(
    HWND hWnd,
    LPCWSTR lpText,
    LPCWSTR lpCaption,
    UINT uType);

Обертка с ctypes:

>>> from ctypes import c_int, WINFUNCTYPE, windll
>>> from ctypes.wintypes import HWND, LPCWSTR, UINT
>>> prototype = WINFUNCTYPE(c_int, HWND, LPCWSTR, LPCWSTR, UINT)
>>> paramflags = (1, "hwnd", 0), (1, "text", "Hi"), (1, "caption", "Hello from ctypes"), (1, "flags", 0)
>>> MessageBox = prototype(("MessageBoxW", windll.user32), paramflags)

Внешняя функция MessageBox может теперь быть вызвана следующими способами:

>>> MessageBox()
>>> MessageBox(text="Spam, spam, spam")
>>> MessageBox(flags=2, text="foo bar")

Второй пример демонстрирует выходные параметры. Функция win32 GetWindowRect восстанавливает размеры указанного окна, копируя их в структуру RECT, которую должен вызвать потребитель. Далее C декларация:

WINUSERAPI BOOL WINAPI
GetWindowRect(
     HWND hWnd,
     LPRECT lpRect);

Обёртка с ctypes:

>>> from ctypes import POINTER, WINFUNCTYPE, windll, WinError
>>> from ctypes.wintypes import BOOL, HWND, RECT
>>> prototype = WINFUNCTYPE(BOOL, HWND, POINTER(RECT))
>>> paramflags = (1, "hwnd"), (2, "lprect")
>>> GetWindowRect = prototype(("GetWindowRect", windll.user32), paramflags)
>>>

Функции с выходными параметрами автоматически возвращают значение выходного параметра, если есть один, или кортеж, содержащий значения выходного параметра, если их несколько, то функция GetCreateRect возвращает экземпляр RECT, когда вызывается.

Выходные параметры могут быть объединены с протоколом errcheck для дальнейшей обработки выходных данных и проверки ошибок. Функция win32 GetWindowRect api возвращает BOOL, чтобы сигнализировать об успехе или провале, таким образом, функция может сделать проверку на наличие ошибок и вызывает исключение, когда вызов API потерпело неудачу:

>>> def errcheck(result, func, args):
...     if not result:
...         raise WinError()
...     return args
...
>>> GetWindowRect.errcheck = errcheck
>>>

Если функция errcheck возвращает полученный кортеж аргументов без изменений, ctypes продолжает обычную обработку, которую он выполняет для выходных параметрах. Если вы хотите возвратить кортеж координат окна вместо экземпляра RECT, вы можете получить поля в функции и возвратить их, вместо обычной обработки:

>>> def errcheck(result, func, args):
...     if not result:
...         raise WinError()
...     rc = args[1]
...     return rc.left, rc.top, rc.bottom, rc.right
...
>>> GetWindowRect.errcheck = errcheck
>>>

Полезные функции

ctypes.addressof(obj)

Возвращает адрес буфера памяти как целое число. obj должен быть сущностью типа ctypes.

Вызывает событие аудита ctypes.addressof с аргументом obj.

ctypes.alignment(obj_or_type)

Возвращает требования к выравниванию типа ctypes. obj_or_type должен быть типом ctypes или сущностью.

ctypes.byref(obj[, offset])

Возвращает облегченный указатель на obj, который должен являться сущностью типа ctypes. offset по умолчанию равен нулю и должен быть целым числом, которое будет добавлено к внутреннему указателю значения.

byref(obj, offset) соответствует этому коду C:

(((char *)&obj) + offset)

Возвращаемый объект может использоваться только как параметр вызова внешней функции. Ведёт себя подобно pointer(obj), но строительство идёт намного быстрее.

ctypes.cast(obj, type)

Функция подобна оператору cast в C. Она возвращает новую сущность type, которая указывает на тот же блок памяти как obj. type должен быть типом указателя, а obj должен быть объектом, который может быть интерпретирован как указатель.

ctypes.create_string_buffer(init_or_size, size=None)

Функция создаёт изменяемый символьный буфер. Возвращаемый объект является массивом ctypes c_char.

init_or_size должен быть целым числом, определяющим размер массива или объект байтов, который будет использован для инициализации элементов массива.

Если в качестве первого аргумента указан объект байтов, буфер становится на один элемент больше, чем его длина, так что последний элемент в массиве является символом завершения NUL. В качестве второго аргумента можно передать целое число, которое позволяет указать размер массива, если длина байтов не должна использоваться.

Вызывает событие аудита ctypes.create_string_buffer с аргументами init, size.

ctypes.create_unicode_buffer(init_or_size, size=None)

Функция создаёт изменяемый Юникод символьный буфер. Возвращаемый объект является массивом ctypes c_wchar.

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

Если в качестве первого аргумента указана строка, буфер делается на один элемент больше, чем длина строки, так что последний элемент в массиве является символом завершения NUL. В качестве второго аргумента можно передать целое число, которое позволяет указать размер массива, если длина строки не должна использоваться.

Вызывает событие аудита ctypes.create_unicode_buffer с аргументами init, size.

ctypes.DllCanUnloadNow()

Только Windows: функция является хуком, который позволяет реализовать внутрипроцессные COM серверы с ctypes. Она вызывается из функции DllCanUnloadNow, которую экспортирует _ctypes расширение dll.

ctypes.DllGetClassObject()

Только Windows: функция является хуком, который позволяет реализовать внутрипроцессные COM серверы с ctypes. Она вызывается из функции CallGetClassObject, которую экспортирует библиотека расширения _ctypes.

ctypes.util.find_library(name)

Попытаться найти библиотеку и вернуть pathname. name — имя библиотеки без префикса типа lib, суффикса типа .so, .dylib или номера версии (форма используется для опции posix линкера -l). Если не удается найти библиотеку, возвращает значение None.

Точная функциональность зависит от системы.

ctypes.util.find_msvcrt()

Только Windows: возвращает имя файла библиотеки времени выполнения VC используемой Python и модулями расширений. Если имя библиотеки не может быть определено, возвращается значение None.

Если требуется освободить память, например, выделенную внутренним модулем с вызовом free(void *), важно использовать функцию в той же библиотеки, которая выделила память.

ctypes.FormatError([code])

Только Windows: возвращает текстовое описание кода ошибки code. Если не указан код ошибки, используется последний код ошибки используемый при вызове API функции Windows GetLastError.

ctypes.GetLastError()

Только Windows: возвращает последний код ошибки, установленный Windows в вызывающем потоке. Функция вызывает функцию GetLastError() Windows напрямую, она не возвращает ctypes-приватную копию кода ошибки.

ctypes.get_errno()

Возвращает текущее значение ctypes-приватной копии системной переменной errno в вызываемом потоке.

Вызывает событие аудита ctypes.get_errno без аргументов.

ctypes.get_last_error()

Только Windows: возвращает текущее значение ctypes-приватной копии системной переменной LastError в вызываемом потоке.

Вызывает событие аудита ctypes.get_last_error без аргументов.

ctypes.memmove(dst, src, count)

То же, что и стандартная функция библиотеки C memmove: копирование count байт из src в dst. dst и src должны быть целыми числами или ctypes сущностями, которые могут быть преобразованы в указатели.

ctypes.memset(dst, c, count)

То же, что и стандартная функция библиотеки C memset: заполняет блок памяти по адресу dst count байтами значением c. dst должно быть целым числом, указывающим адрес или ctypes сущностью.

ctypes.POINTER(type)

Функция фабрика создающая и возвращающая новый тип указателя ctypes. Типы указателей кэшируются и повторно используются внутри системы, поэтому повторный вызов этой функции является дешевым. type должен быть типом ctypes.

ctypes.pointer(obj)

Функция создаёт новый экземпляр указателя, указывающий на obj. Возвращаемый объект содержит тип POINTER(type(obj)).

Примечание: если вы просто хотите передать указатель на объект на вызов чужой функции, вы должны использовать byref(obj), который гораздо быстрее.

ctypes.resize(obj, size)

Функция изменяет размер внутреннего буфера памяти obj, который должен быть сущностью типа ctypes. Невозможно сделать буфер меньше, чем собственный размер объектов типа указанного в параметре sizeof(type(obj)), но можно увеличить буфер.

ctypes.set_errno(value)

Установить текущее значение ctypes-приватной копии системной переменной errno в вызываемом потоке в value и возвратить предыдущее значение.

Вызывает событие аудита ctypes.set_errno с аргументом errno.

ctypes.set_last_error(value)

Только Windows: установить текущий значение ctypes-приватной копии системной переменной LastError в вызываемом потоке в value и возвратить предыдущий значение.

Вызывает событие аудита ctypes.set_last_error с аргументом error.

ctypes.sizeof(obj_or_type)

Возвращает размер в байтах типа ctypes или экземпляры буфера памяти. Выполняет то же как и оператор C sizeof.

ctypes.string_at(address, size=-1)

Функция возвращает C строку, начиная с адреса памяти address как байтовый объект. Если указан размер, он используется как размер, в противном случае предполагается, что строка заканчивается нулем.

Вызывает событие аудита ctypes.string_at с аргументами address, size.

ctypes.WinError(code=None, descr=None)

Только Windows: функция, вероятно, наихудшая в ctypes. Создаётся сущность OSEror. Если code не указан, вызывается GetLastError для определения кода ошибки. Если параметр descr не указан, вызывается метод FormatError() для получения текстового описания ошибки.

Изменено в версии 3.3: Создана используемая экземпляр WindowsError.

ctypes.wstring_at(address, size=-1)

Функция возвращает широкую символьную строку, начиная с адреса памяти address в виде строки. Если определен size, то он используется как количество символов строки, иначе строка, как предполагается, заканчивается нулем.

Вызывает событие аудита ctypes.wstring_at с аргументами address, size.

Типы данных

class ctypes._CData

Не публичный класс, являющийся общим базовым классом всех ctypes типов данных. Среди прочего, все типы ctypes экземпляры содержат блок памяти, который содержит C-совместимые данные; адрес блока памяти возвращается функцией модуля хелпера addressof(). Другая переменная экземпляра отображается как _objects; она содержит другие объекты Python, которые необходимо сохранить в случае, если блок памяти содержит указатели.

Общие методы типов данных ctypes, это все методы класса (чтобы быть точным, это методы метакласса):

from_buffer(source[, offset])

Метод возвращает ctypes сущность, которую совместно использует буфер объекта source. Объект source должен поддерживать интерфейс буфера, доступный для записи. Необязательный параметр offset задаёт смещение в исходном буфере в байтах; значение по умолчанию равно нулю. Если исходный буфер не достаточно большой, вызывается ValueError.

Вызывает событие аудита ctypes.cdata/buffer с аргументами pointer, size, offset.

from_buffer_copy(source[, offset])

Метод создаёт экземпляр ctypes, копируя буфер из буфера объекта source, который должен быть читаемым. Необязательный параметр offset задаёт смещение в исходном буфере в байтах; значение по умолчанию равно нулю. Если исходный буфер не достаточно большой, вызывается ValueError.

Вызывает событие аудита ctypes.cdata/buffer с аргументами pointer, size, offset.

from_address(address)

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

Метод, а также другие, которые косвенно вызывают этот метод, вызывает событие аудита ctypes.cdata с аргументом address.

from_param(obj)

Метод приспосабливает obj к типу ctypes. Его вызывают с фактическим объектом используемый в внешнем вызове функции, когда тип присутствует в кортеже внешней функции argtypes; он должен возвращать объект, который может быть использован как параметр вызова функции.

Все типы данных ctypes содержат реализацию по умолчанию данного классметода, который обычно возвращает obj, если является сущностью type. Некоторые типы также принимают другие объекты.

in_dll(library, name)

Метод возвращает ctypes тип экземпляра, экспортируемый общей библиотекой. name — название символа, который экспортирует данные, library — загружаемая общая библиотека.

Общие переменные экземпляры ctypes типов данных:

_b_base_

Иногда данные ctypes экземпляры не владеют блоком памяти, в котором они содержатся, вместо этого они разделяют часть блока памяти базового объекта. _b_base_ — атрибут только для чтения коревого ctypes объекта, который владеет блоком памяти.

_b_needsfree_

Переменная только для чтения, содержит true, когда данные экземпляры ctypes аллоцировали блок памяти самостоятельно, false иначе.

_objects

Атрибут, либо None или словарь, содержащий объекты Python, которые должны быть сохранены, чтобы содержание блока памяти было сохранено действительным. Объект доступен только для отладки; никогда не меняйте содержимое этого словаря.

Фундаментальные типы данных

class ctypes._SimpleCData

Непубличный класс, являющийся базовым классом всех фундаментальных типов данных ctypes. Здесь он упоминается, поскольку содержит общие атрибуты фундаментальных типов данных ctypes. _SimpleCData является подклассом _CData, поэтому наследует его методы и атрибуты. Типы данных ctypes, которых нет и не содержат указатели, теперь могут быть пиклены (pickled).

Сущности содержат один атрибут:

value

Атрибут содержит фактическое значение экземпляра. Для целочисленных и типов указателей это целое число, для символьных типов — одиночный символ байтового объекта или строка, для указателей символьного типа — это байтовый объект Python или строка.

Когда value атрибут извлекается из экземпляра ctypes, обычно каждый раз возвращается новый объект. ctypes не реализует оригинальный возвращаемый объект, всегда конструируется новый объект. То же самое верно для всех остальных объектов экземпляра ctypes.

Фундаментальные типы данных, возвращаемые как результаты вызова внешней функции или, например, путём извлечения элементов поля структуры или элементов массива, прозрачным образом преобразуются в собственные типы Python. Другими словами, если внешняя функция содержит restype c_char_p, всегда будет получен байтовый объект Python, не сущности c_char_p.

Подклассы фундаментальных типов данных не наследуют это поведение. Так, если внешние функции restype будет подклассом c_void_p, то вы получите экземпляр этого подкласса из вызова функции. Конечно, вы можете получить значение указателя, обращаясь к атрибуту value.

Фундаментальные типы данных ctypes:

class ctypes.c_byte

Представляет тип данных C signed char и интерпретирует значение как малое целое число. Конструктор принимает необязательный инициализатор целого числа; проверка переполнения не выполняется.

class ctypes.c_char

Представляет тип данных C char и интерпретирует значение как один символ. Конструктор принимает необязательный инициализатор строки, длина строки должна быть ровно одному символу.

class ctypes.c_char_p

Представляет тип данных C char *, когда он указывает на строку с нулевым символом в конце. Для общего указателя символа, который также может указывать на двоичные данные, необходимо использовать POINTER(c_char). Конструктор принимает целочисленный адрес или байтовый объект.

class ctypes.c_double

Представляет тип данных C double. Конструктор принимает дополнительный инициализатор float.

class ctypes.c_longdouble

Представляет тип данных C long double. Конструктор принимает дополнительный инициализатор float. На платформах, где sizeof(long double) == sizeof(double) это алиас c_double.

class ctypes.c_float

Представляет тип данных C float. Конструктор принимает дополнительный инициализатор float.

class ctypes.c_int

Представляет тип данных C signed int. Конструктор принимает необязательный инициализатор целого числа; проверка переполнения не выполняется. На платформах, где sizeof(int) == sizeof(long) это алиас c_long.

class ctypes.c_int8

Представляет 8-битный signed int тип данных C. Обычно алиас для c_byte.

class ctypes.c_int16

Представляет 16-битный signed int тип данных C. Обычно алиас для c_short.

class ctypes.c_int32

Представляет 32-битный signed int тип данных C. Обычно алиас для c_int.

class ctypes.c_int64

Представляет 64-битный signed int тип данных C. Обычно алиас для c_longlong.

class ctypes.c_long

Представляет тип данных C signed long. Конструктор принимает необязательный инициализатор целого числа; проверка переполнения не выполняется.

class ctypes.c_longlong

Представляет тип данных C signed long long. Конструктор принимает необязательный инициализатор целого числа; проверка переполнения не выполняется.

class ctypes.c_short

Представляет тип данных C signed short. Конструктор принимает необязательный инициализатор целого числа; проверка переполнения не выполняется.

class ctypes.c_size_t

Представляет тип данных C size_t.

class ctypes.c_ssize_t

Представляет тип данных C ssize_t.

Добавлено в версии 3.2.

class ctypes.c_ubyte

Представляет тип данных C unsigned char, интерпретирует значение как малое целое число. Конструктор принимает необязательный инициализатор целого числа; проверка переполнения не выполняется.

class ctypes.c_uint

Представляет тип данных C unsigned int. Конструктор принимает необязательный инициализатор целого числа; проверка переполнения не выполняется. На платформах, где sizeof(int) == sizeof(long) это алиас для c_ulong.

class ctypes.c_uint8

Представляет 8-битный unsigned int тип данных C. Обычно алиас для c_ubyte.

class ctypes.c_uint16

Представляет 16-битный unsigned int тип данных C. Обычно алиас для c_ushort.

class ctypes.c_uint32

Представляет 32-битный unsigned int тип данных C. Обычно алиас для c_uint.

class ctypes.c_uint64

Представляет 64-битный unsigned int тип данных C. Обычно алиас для c_ulonglong.

class ctypes.c_ulong

Представляет тип данных C unsigned long. Конструктор принимает необязательный инициализатор целого числа; проверка переполнения не выполняется.

class ctypes.c_ulonglong

Представляет тип данных C unsigned long long. Конструктор принимает необязательный инициализатор целого числа; проверка переполнения не выполняется.

class ctypes.c_ushort

Представляет тип данных C unsigned short. Конструктор принимает необязательный инициализатор целого числа; проверка переполнения не выполняется.

class ctypes.c_void_p

Представляет тип C void *. значение представлен как целое число. Конструктор принимает необязательный инициализатор целого числа.

class ctypes.c_wchar

Представляет тип данных C wchar_t и интерпретирует значение как один символ Юникод строки. Конструктор принимает необязательный инициализатор строки, длина строки должна быть ровно одному символу.

class ctypes.c_wchar_p

Представляет тип данных C wchar_t *, который должен быть указателем на широкую символьную строку оканчивающуюся нулём. Конструктор принимает целочисленный адрес или строку.

class ctypes.c_bool

Представляет тип данных C bool (более точно: c:type:_Bool от C99). Его значение может быть True или False, и конструктор принимает любой объект, принимающий истинное значение.

class ctypes.HRESULT

Только для Windows: представляет значение HRESULT, которое содержит информацию об успехе или ошибке для вызова функции или метода.

class ctypes.py_object

Представляет тип данных C PyObject *. Вызов этого без аргумента создаёт указатель NULLPyObject *.

Модуль ctypes.wintypes предоставляет некоторые другие Windows специфические типы данных например HWND:, c:type:WPARAM или DWORD. Также определены некоторые полезные структуры, например MSG или RECT.

Типы структурированных данных

class ctypes.Union(*args, **kw)

Абстрактный базовый класс для объединений в родном порядке байтов.

class ctypes.BigEndianStructure(*args, **kw)

Абстрактный базовый класс для структур (с обратным порядком байт) big endian.

class ctypes.LittleEndianStructure(*args, **kw)

Абстрактный базовый класс для структур в прямом порядке байт (little endian).

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

class ctypes.Structure(*args, **kw)

Абстрактный базовый класс для структур в родном порядке байт.

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

_fields_

Последовательность, определяющая поля структуры. Элементы должны быть 2-кортежами или 3-кортежами. Первый элемент — имя поля, второй элемент — тип поля; он может быть любым типом данных ctypes.

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

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

Возможно определить переменную _fields_ класса после class оператора, который определяет подкласс Structure, это позволяет создавать типы данных что прямо или косвенно ссылаются сами на себя:

class List(Structure):
    pass
List._fields_ = [("pnext", POINTER(List)),
                 ...
                ]

Однако, переменная класса _fields_ должна, быть определена до того, как тип впервые используется (создаётся экземпляр, sizeof() вызывается на нём, и так далее). Более позднее присвоение переменной класса _fields_ будет вызывать AttributeError.

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

_pack_

Необязательное малое целое число, позволяющее переопределять выравнивание полей структуры в экземпляры. _pack_ уже должен быть определен при присвоении _fields_, в противном случае он не будет иметь эффекта.

_anonymous_

Необязательная последовательность, в которой перечислены имена безымянных (анонимных) полей. _anonymous_ должен быть уже определён при назначении _fields_, в противном случае он не будет иметь эффекта.

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

Вот пример типа (Windows):

class _U(Union):
    _fields_ = [("lptdesc", POINTER(TYPEDESC)),
                ("lpadesc", POINTER(ARRAYDESC)),
                ("hreftype", HREFTYPE)]

class TYPEDESC(Structure):
    _anonymous_ = ("u",)
    _fields_ = [("u", _U),
                ("vt", VARTYPE)]

Структура TYPEDESC определяет тип данных COM, поле vt указывает, какое из полей объединения является допустимым. Поскольку поле u определено как анонимное поле, теперь можно получить доступ к членам непосредственно вне TYPEDESC экземпляра. td.lptdesc и td.u.lptdesc эквивалентны, но первый быстрее, т. к. ему не нужно создавать временный экземпляр объединения:

td = TYPEDESC()
td.vt = VT_PTR
td.lptdesc = POINTER(some_type)
td.u.lptdesc = POINTER(some_type)

Можно определить вложенные подклассы структур, они наследуют поля базового класса. Если определение подкласс содержит отдельную переменную _fields_, поля, указанные в нём, добавляются к полям базового класса.

Конструкторы структур и объединений принимают как позиционные, так и ключевой аргументы. Позиционные аргументы используются, для инициализации поля членов в том же порядке, как они, появляются в _fields_. Ключевые аргументы в конструкторе интерпретируются как назначения атрибутов, поэтому они инициализируются _fields_ с тем же именем или создадут новые атрибуты для имён, отсутствующих в _fields_.

Массивы и указатели

class ctypes.Array(*args)

Абстрактный базовый класс для массивов.

Рекомендуется создавать типы массивов путём умножения любого типа данных ctypes на положительное целое число. Кроме того, можно создать подкласс этого типа и определить переменные _length_ и _type_ класса. Элементы массива могут быть прочитаны и записаны с помощью стандартного доступа по индексу и нарезке; для чтения нарезки, результирующий объект не является самим Array.

_length_

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

_type_

Указывает тип каждого элемента в массиве.

Конструкторы подкласса массива принимают позиционные аргументы, используемые для инициализации элементов в порядке.

class ctypes._Pointer

Приватный, абстрактный базовый класс для указателей.

Типы указателей создаются путём вызова POINTER() с типом, на который будет указываться; это сделано автоматически pointer().

Если указатель указывает на массив, его элементы можно считывать и записывать с помощью стандартного доступа подстрочными индексами и нарезками. Объекты-указатели не имеют размера, поэтому len() вызывает TypeError. Отрицательные нижние индексы прочитают из памяти до указателя (как в C), и нижние индексы из диапазона, вероятно, потерпят крах с нарушением доступа (если вы будете удачливы).

_type_

Указывает тип, на который указывает.

contents

Возвращает объект, на который указывает указатель. Присвоение этому атрибуту изменяет указатель, чтобы указать на назначенный объект.