Документация Python неофициальный перевод
Содержание страницы

15.15. ctypes – библиотека для вызова внешних функций из Pythonctypes – A foreign function library for Python

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

15.15.1. Руководство по ctypesctypes tutorial

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

Примечание: некоторые примеры кода ссылаются на тип ctypes c_int. Этот тип является псевдонимом для типа c_long в 32-битных системах. Так что не стоит путаться, если выводится c_long, когда вы ожидаете c_int – на самом деле это один и тот же тип.

15.15.1.2. Доступ к функциям загруженных DLLAccessing functions from loaded dlls

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

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

Обратите внимание, что системные DLL win32, такие как kernel32 и user32, часто экспортируют как ANSI-, так и UNICODE-версии функций. Версия UNICODE экспортируется с добавлением W к имени, а версия ANSI – с добавлением A. Функция win32 GetModuleHandle, которая возвращает дескриптор модуля для заданного имени модуля, имеет следующий прототип на C, и макрос используется для предоставления одной из них как GetModuleHandle в зависимости от того, определён ли UNICODE:

/* ANSI version */
HMODULE GetModuleHandleA(LPCSTR lpModuleName);
/* UNICODE version */
HMODULE GetModuleHandleW(LPCWSTR lpModuleName);

windll не пытается выбрать одну из них магическим образом; необходимо явно указывать нужную версию, обращаясь к GetModuleHandleA или GetModuleHandleW, и затем вызывать её с объектами bytes или string соответственно.

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

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

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

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

15.15.1.3. Вызов функцийCalling functions

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

В этом примере обе функции вызываются с нулевым указателем (в качестве нулевого указателя следует использовать None):

>>> print(libc.time(None)) # doctest: +SKIP
1150640792
>>> print(hex(windll.kernel32.GetModuleHandleA(None))) # doctest: +WINDOWS
0x1d000000
>>>

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

>>> windll.kernel32.GetModuleHandleA() # doctest: +WINDOWS
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ValueError: Procedure probably called with not enough arguments (4 bytes missing)
>>> windll.kernel32.GetModuleHandleA(0, 0) # doctest: +WINDOWS
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ValueError: Procedure probably called with too many arguments (4 bytes in excess)
>>>

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

>>> cdll.kernel32.GetModuleHandleA(None) # doctest: +WINDOWS
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ValueError: Procedure probably called with not enough arguments (4 bytes missing)
>>>

>>> windll.msvcrt.printf(b"spam") # doctest: +WINDOWS
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ValueError: Procedure probably called with too many arguments (4 bytes in excess)
>>>

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

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

>>> windll.kernel32.GetModuleHandleA(32) # doctest: +WINDOWS
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
WindowsError: exception: access violation reading 0x00000020
>>>

Однако есть достаточно способов привести к краху Python с помощью ctypes, поэтому в любом случае следует соблюдать осторожность.

None, целые числа, объекты bytes и (юникодные) строки – единственные нативные объекты Python, которые можно напрямую использовать в качестве параметров при вызовах этих функций. None передаётся как указатель C NULL, объекты bytes и строки передаются как указатель на блок памяти, содержащий их данные (char * или wchar_t *). Целые числа Python передаются как тип C int, используемый по умолчанию на данной платформе, их значение маскируется, чтобы поместиться в тип C.

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

15.15.1.4. Базовые типы данныхFundamental data types

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

Тип ctypes Тип C Тип Python
c_bool _Bool bool (1)
c_char char односимвольный объект bytes
c_wchar wchar_t односимвольная строка
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 или long long int
c_ulonglong unsigned __int64 или unsigned long long int
c_float float float
c_double double float
c_longdouble long double float
c_char_p char * (с завершающим NUL) объект bytes или None
c_wchar_p wchar_t * (с завершающим NUL) строка или None
c_void_p void * int или None
  1. Конструктор принимает любой объект с истинностным значением.

Все эти типы можно создать, вызвав их с необязательным инициализатором правильного типа и значения:

>>> c_int()
c_long(0)
>>> c_wchar_p("Hello, World")
c_wchar_p('Hello, World')
>>> 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 изменяет адрес памяти, на который они указывают, а не содержимое блока памяти (конечно, нет, потому что объекты bytes в Python неизменяемы):

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

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

>>> from ctypes import *
>>> p = create_string_buffer(3)            # создать буфер размером 3 байта, инициализированный нулевыми байтами
>>> print(sizeof(p), repr(p.raw))
3 b'\x00\x00\x00'
>>> p = create_string_buffer(b"Hello")     # создать буфер, содержащий строку, завершающуюся нулевым байтом
>>> 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. Чтобы создать изменяемый блок памяти, содержащий символы Unicode типа C wchar_t, используйте функцию create_unicode_buffer().

15.15.1.5. Вызов функций, продолжениеCalling functions, continued

Обратите внимание, что 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 ?
ArgumentError: argument 2: exceptions.TypeError: Don't know how to convert parameter 2
>>>

Как упоминалось ранее, все типы Python, кроме целых чисел, строк и объектов bytes, должны быть обёрнуты в соответствующий тип ctypes, чтобы их можно было преобразовать в требуемый тип данных C:

>>> printf(b"An int %d, a double %f\n", 1234, c_double(3.14))
An int 1234, a double 3.140000
31
>>>

15.15.1.6. Вызов функций с собственными пользовательскими типами данныхCalling functions with your own custom data types

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

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

15.15.1.7. Задание требуемых типов аргументов (прототипы функций)Specifying the required argument types (function prototypes)

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

15.15.1.8. Типы возвращаемых значенийReturn types

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

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

>>> strchr = libc.strchr
>>> strchr(b"abcdef", ord("d")) # doctest: +SKIP
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 bytes, содержащего один символ, в 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 ?
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 # doctest: +WINDOWS
>>> def ValidHandle(value):
...     if value == 0:
...         raise WinError()
...     return value
...
>>>
>>> GetModuleHandle.restype = ValidHandle # doctest: +WINDOWS
>>> GetModuleHandle(None) # doctest: +WINDOWS
486539264
>>> GetModuleHandle("something silly") # doctest: +WINDOWS
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "<stdin>", line 3, in ValidHandle
WindowsError: [Errno 126] The specified module could not be found.
>>>

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

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

15.15.1.9. Передача указателей (или: передача параметров по ссылке)Passing pointers (or: passing parameters by reference)

Иногда функция 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'
>>>

15.15.1.10. Структуры и объединенияStructures and unions

Структуры и объединения должны быть производными от базовых классов 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 ?
ValueError: too many initializers
>>>

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

Вот структура RECT, которая содержит две точки с именами 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))

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

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

15.15.1.11. Выравнивание структур/объединений и порядок байтовStructure/union alignment and byte order

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

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

15.15.1.12. Битовые поля в структурах и объединенияхBit fields in structures and unions

Можно создавать структуры и объединения, содержащие битовые поля. Битовые поля возможны только для целочисленных полей; ширина битового поля задается третьим элементом в кортежах _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>
>>>

15.15.1.13. МассивыArrays

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

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

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

15.15.1.14. УказателиPointers

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

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

It is also possible to use indexes different from 0, but you must know what you’re doing, just as in C: You can access or change arbitrary memory locations. Generally you only use this feature if you receive a pointer from a C function, and you know that the pointer actually points to an array instead of a single item.

Под капотом функция 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 ?
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
>>>

15.15.1.15. Преобразования типовType conversions

Обычно 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 в 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 ?
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
>>>

15.15.1.16. Неполные типыIncomplete Types

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

struct cell; /* forward declaration */

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

Прямой перевод в код ctypes выглядел бы так, но это не работает:

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

поскольку новый класс 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
>>>

15.15.1.17. Функции обратного вызоваCallback functions

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

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

Фабричная функция CFUNCTYPE создаёт типы для функций обратного вызова с использованием обычного соглашения вызова cdecl, а на Windows фабричная функция WINFUNCTYPE создаёт типы для функций обратного вызова с использованием соглашения вызова stdcall.

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

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

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

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

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

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

Для первой реализации функции обратного вызова мы просто выводим полученные аргументы и возвращаем 0 (итеративная разработка ;-)):

>>> def py_cmp_func(a, b):
...     print("py_cmp_func", a, b)
...     return 0
...
>>>

Создайте вызываемый из C обратный вызов:

>>> cmp_func = CMPFUNC(py_cmp_func)
>>>

И мы готовы начать:

>>> qsort(ia, len(ia), sizeof(c_int), cmp_func) # doctest: +WINDOWS
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
>>>

Мы знаем, как получить доступ к содержимому указателя, поэтому переопределим наш колбэк:

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

Вот что мы получаем в Windows:

>>> qsort(ia, len(ia), sizeof(c_int), cmp_func) # doctest: +WINDOWS
py_cmp_func 7 1
py_cmp_func 33 1
py_cmp_func 99 1
py_cmp_func 5 1
py_cmp_func 7 5
py_cmp_func 33 5
py_cmp_func 99 5
py_cmp_func 7 99
py_cmp_func 33 99
py_cmp_func 7 33
>>>

Забавно, что на linux функция сортировки, похоже, работает гораздо эффективнее – она делает меньше сравнений:

>>> qsort(ia, len(ia), sizeof(c_int), cmp_func) # doctest: +LINUX
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]
...
>>>

Финальный запуск в Windows:

>>> qsort(ia, len(ia), sizeof(c_int), CMPFUNC(py_cmp_func)) # doctest: +WINDOWS
py_cmp_func 33 7
py_cmp_func 99 33
py_cmp_func 5 99
py_cmp_func 1 99
py_cmp_func 33 7
py_cmp_func 1 33
py_cmp_func 5 33
py_cmp_func 5 7
py_cmp_func 1 7
py_cmp_func 5 1
>>>

и в Linux:

>>> qsort(ia, len(ia), sizeof(c_int), CMPFUNC(py_cmp_func)) # doctest: +LINUX
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
>>>

Довольно интересно, что функция qsort() в Windows требует больше сравнений, чем версия в linux!

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

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

Важное замечание для функций обратного вызова:

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

15.15.1.18. Доступ к значениям, экспортируемым из DLLAccessing values exported from dlls

Некоторые разделяемые библиотеки экспортируют не только функции, но и переменные. Примером в самой библиотеке 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:
...    print(item.name, item.size)
...    if item.name is None:
...        break
...
__hello__ 104
__phello__ -104
__phello__.spam 104
None 0
>>>

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

15.15.1.19. НеожиданностиSurprises

В 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 = "abc def ghi"
>>> s.value
'abc def ghi'
>>> s.value is s.value
False
>>>

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

15.15.1.20. Типы данных переменного размераVariable-sized data types

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

15.15.2. Справочник ctypesctypes reference

15.15.2.1. Поиск разделяемых библиотекFinding shared libraries

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

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

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

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

Точное поведение зависит от системы.

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

>>> 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() для поиска библиотеки во время выполнения.

15.15.2.2. Загрузка разделяемых библиотекLoading shared libraries

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

class ctypes.CDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False)
Экземпляры этого класса представляют загруженные разделяемые библиотеки. Функции в этих библиотеках используют стандартное соглашение о вызове C и считаются возвращающими int.
class ctypes.OleDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False)
Только для Windows: экземпляры этого класса представляют загруженные разделяемые библиотеки; функции в этих библиотеках используют соглашение о вызове stdcall и, как предполагается, возвращают специфичный для Windows код HRESULT. Значения HRESULT содержат информацию, указывающую, успешно ли завершился вызов функции или произошла ошибка, вместе с дополнительным кодом ошибки. Если возвращаемое значение сигнализирует об ошибке, автоматически возбуждается исключение WindowsError.
class ctypes.WinDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False)

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

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

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

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

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

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

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

Параметр mode можно использовать для указания способа загрузки библиотеки. Подробнее см. страницу руководства dlopen(3); в Windows параметр mode игнорируется.

Параметр 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() используются для получения и изменения частной копии кода ошибки Windows в ctypes.

ctypes.RTLD_GLOBAL
Флаг, используемый в качестве параметра mode. На платформах, где этот флаг недоступен, он определён как целочисленный ноль.
ctypes.RTLD_LOCAL
Флаг, используемый в качестве параметра mode. На платформах, где он недоступен, он совпадает с RTLD_GLOBAL.
ctypes.DEFAULT_MODE
Режим по умолчанию, используемый для загрузки разделяемых библиотек. На OSX 10.3 это RTLD_GLOBAL, в противном случае он совпадает с RTLD_LOCAL.

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

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

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 Python API доступен готовый к использованию объект общей библиотеки Python:

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

15.15.2.3. Внешние функцииForeign functions

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

class ctypes._FuncPtr

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

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

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

restype

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

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

argtypes

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

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

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

errcheck

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

callable(result, func, arguments)

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

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

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

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

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

15.15.2.4. Прототипы функцийFunction prototypes

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

ctypes.CFUNCTYPE(restype, *argtypes, use_errno=False, use_last_error=False)
Возвращаемый прототип функции создаёт функции, использующие стандартное соглашение о вызовах C. Функция освобождает GIL во время вызова. Если use_errno установлен в True, частная копия системной переменной errno из ctypes обменивается с реальным значением 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.
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 MessageBoxA так, чтобы она поддерживала параметры по умолчанию и именованные аргументы. Объявление C из заголовочного файла Windows выглядит так:

WINUSERAPI int WINAPI
MessageBoxA(
    HWND hWnd ,
    LPCSTR lpText,
    LPCSTR lpCaption,
    UINT uType);

Вот обёртка с помощью ctypes:

>>> from ctypes import c_int, WINFUNCTYPE, windll
>>> from ctypes.wintypes import HWND, LPCSTR, UINT
>>> prototype = WINFUNCTYPE(c_int, HWND, LPCSTR, LPCSTR, UINT)
>>> paramflags = (1, "hwnd", 0), (1, "text", "Hi"), (1, "caption", None), (1, "flags", 0)
>>> MessageBox = prototype(("MessageBoxA", 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)
>>>

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

Выходные параметры можно комбинировать с протоколом errcheck для дальнейшей обработки вывода и проверки ошибок. Функция win32 API GetWindowRect возвращает 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
>>>

15.15.2.5. Вспомогательные функцииUtility functions

ctypes.addressof(obj)
Возвращает адрес буфера памяти в виде целого числа. obj должен быть экземпляром типа ctypes.
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)
Эта функция аналогична оператору приведения типов в C. Она возвращает новый экземпляр type, указывающий на тот же блок памяти, что и obj. type должен быть типом-указателем, а obj – объектом, который можно интерпретировать как указатель.
ctypes.create_string_buffer(init_or_size, size=None)

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

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

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

Если первый параметр является строкой, она преобразуется в объект bytes в соответствии с правилами преобразования ctypes.

ctypes.create_unicode_buffer(init_or_size, size=None)

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

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

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

Если первый параметр является объектом bytes, он преобразуется в строку unicode в соответствии с правилами преобразования ctypes.

ctypes.DllCanUnloadNow()
Только Windows: эта функция является хуком, который позволяет реализовать внутрипроцессные COM-серверы с помощью ctypes. Она вызывается из функции DllCanUnloadNow, которую экспортирует DLL расширения _ctypes.
ctypes.DllGetClassObject()
Только Windows: эта функция является хуком, который позволяет реализовать внутрипроцессные COM-серверы с помощью ctypes. Она вызывается из функции DllGetClassObject, которую экспортирует DLL расширения _ctypes.
ctypes.util.find_library(name)

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

Точное поведение зависит от системы.

ctypes.util.find_msvcrt()

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

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

ctypes.FormatError([code])
Только для Windows: возвращает текстовое описание кода ошибки code. Если код ошибки не указан, используется последний код ошибки, полученный вызовом функции Windows API GetLastError.
ctypes.GetLastError()
Только для Windows: Возвращает последний код ошибки, установленный Windows в вызывающем потоке. Эта функция вызывает непосредственно функцию Windows GetLastError(); она не возвращает внутреннюю для ctypes копию кода ошибки.
ctypes.get_errno()
Возвращает текущее значение принадлежащей ctypes копии системной переменной errno в вызывающем потоке.
ctypes.get_last_error()
Только Windows: возвращает текущее значение переменной LastError (приватной копии ctypes системной переменной LastError) в вызывающем потоке.
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_conversion_mode(encoding, errors)

Эта функция задаёт правила, которые объекты ctypes используют при преобразовании между объектами bytes и (unicode) строками. encoding должна быть строкой, задающей кодировку, например 'utf-8' или 'mbcs', errors должна быть строкой, задающей обработку ошибок при кодировании/декодировании. Примеры возможных значений: 'strict', 'replace' или 'ignore'.

set_conversion_mode() возвращает кортеж из двух элементов, содержащий предыдущие правила преобразования. В Windows начальные правила преобразования: ('mbcs', 'ignore'), в других системах – ('ascii', 'strict').

Можно установить encoding в 'undefined', чтобы полностью отключить автоматическое преобразование.

ctypes.set_errno(value)
Устанавливает текущее значение принадлежащей ctypes копии системной переменной errno в вызывающем потоке равным value и возвращает предыдущее значение.
ctypes.set_last_error(value)
Только для Windows: устанавливает текущее значение приватной копии системной переменной LastError в вызывающем потоке в value и возвращает предыдущее значение.
ctypes.sizeof(obj_or_type)
Возвращает размер в байтах буфера памяти ctypes-типа или экземпляра. Делает tо же самое, что и функция C sizeof().
ctypes.string_at(address, size=-1)
Эта функция возвращает C-строку, начинающуюся с адреса памяти address, в виде объекта bytes. Если указан size, он используется как размер, в противном случае строка считается завершающейся нулём.
ctypes.WinError(code=None, descr=None)
Только для Windows: эта функция, вероятно, самая неудачно названная вещь в ctypes. Она создаёт экземпляр WindowsError. Если code не указан, вызывается GetLastError для определения кода ошибки. Если descr не указан, вызывается FormatError() для получения текстового описания ошибки.
ctypes.wstring_at(address, size=-1)
Эта функция возвращает строку широких символов, начинающуюся по адресу памяти address, в виде строки. Если указан size, он используется как количество символов строки, в противном случае строка считается завершающейся нулевым символом.

15.15.2.6. Типы данныхData types

class ctypes._CData

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

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

from_buffer(source[, offset])
Этот метод возвращает экземпляр ctypes, который использует тот же буфер, что и объект source. Объект source должен поддерживать интерфейс буфера с возможностью записи. Необязательный параметр offset задаёт смещение в байтах внутри буфера источника; по умолчанию – ноль. Если буфер источника недостаточно велик, возникает исключение ValueError.
from_buffer_copy(source[, offset])
Этот метод создаёт экземпляр ctypes, копируя буфер из объекта source, который должен быть доступен для чтения. Необязательный параметр offset задаёт смещение в байтах внутри буфера источника; по умолчанию – ноль. Если буфер источника недостаточно велик, возникает исключение ValueError.
from_address(address)
Этот метод возвращает экземпляр типа ctypes, использующий память, указанную address, который должен быть целым числом.
from_param(obj)

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

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

in_dll(library, name)
Этот метод возвращает экземпляр типа ctypes, экспортируемый общей библиотекой. name – это имя символа, экспортирующего данные; library – загруженная общая библиотека.

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

_b_base_
Иногда экземпляры данных ctypes не владеют блоком памяти, который содержат, а вместо этого разделяют часть блока памяти базового объекта. Член только для чтения _b_base_ является корневым объектом ctypes, которому принадлежит блок памяти.
_b_needsfree_
Эта переменная только для чтения равна true, когда экземпляр данных ctypes сам выделил блок памяти, и false в противном случае.
_objects
Этот член может быть либо None, либо словарём, содержащим объекты Python, которые необходимо сохранять в памяти, чтобы содержимое блока памяти оставалось корректным. Этот объект предоставляется только для отладки; никогда не изменяйте содержимое этого словаря.

15.15.2.7. Фундаментальные типы данныхFundamental data types

class ctypes._SimpleCData

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

Экземпляры имеют один атрибут:

value

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

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

Базовые типы данных при возврате из вызовов внешних функций или, например, при получении полей структур или элементов массивов прозрачно преобразуются в нативные типы Python. Иными словами, если внешняя функция имеет restype c_char_p, вы всегда получите объект Python bytes, а не экземпляр 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). Конструктор принимает целочисленный адрес или объект bytes.
class ctypes.c_double
Представляет тип данных C double. Конструктор принимает необязательный инициализатор с плавающей запятой.
class ctypes.c_longdouble
Представляет тип данных C long double. Конструктор принимает необязательный инициализатор с плавающей запятой. На платформах, где sizeof(long double) == sizeof(double), он является псевдонимом c_double.
class ctypes.c_float
Представляет тип данных C float. Конструктор принимает необязательный инициализатор с плавающей запятой.
class ctypes.c_int
Представляет тип данных C signed int. Конструктор принимает необязательный инициализатор целого типа; проверка переполнения не выполняется. На платформах, где sizeof(int) == sizeof(long), он является псевдонимом c_long.
class ctypes.c_int8
Представляет 8-битный тип данных C signed int. Обычно является псевдонимом c_byte.
class ctypes.c_int16
Представляет 16-битный тип данных C signed int. Обычно является псевдонимом c_short.
class ctypes.c_int32
Представляет 32-битный тип данных C signed int. Обычно является псевдонимом c_int.
class ctypes.c_int64
Представляет 64-битный тип данных C signed int. Обычно является псевдонимом 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_ubyte
Представляет тип данных C unsigned char, интерпретирует значение как целое число небольшого размера. Конструктор принимает необязательный целочисленный инициализатор; проверка переполнения не выполняется.
class ctypes.c_uint
Представляет тип данных C unsigned int. Конструктор принимает необязательный целочисленный инициализатор; проверка переполнения не выполняется. На платформах, где sizeof(int) == sizeof(long), это псевдоним для c_ulong.
class ctypes.c_uint8
Представляет 8-битный тип данных C unsigned int. Обычно это псевдоним для c_ubyte.
class ctypes.c_uint16
Представляет 16-битный тип данных C unsigned int. Обычно это псевдоним для c_ushort.
class ctypes.c_uint32
Представляет 32-битный тип данных C unsigned int. Обычно это псевдоним для c_uint.
class ctypes.c_uint64
Представляет 64-битный тип данных C unsigned int. Обычно это псевдоним для 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, интерпретирует значение как строку unicode из одного символа. Конструктор принимает необязательный строковый инициализатор; длина строки должна быть ровно один символ.
class ctypes.c_wchar_p
Представляет тип данных C wchar_t *, который должен быть указателем на завершающуюся нулём строку широких символов. Конструктор принимает целочисленный адрес или строку.
class ctypes.c_bool
Представляет тип данных C bool (точнее, _Bool из C99). Его значением может быть True или False, а конструктор принимает любой объект, имеющий логическое значение.
class ctypes.HRESULT
Только Windows: представляет значение HRESULT, содержащее информацию об успехе или ошибке вызова функции или метода.
class ctypes.py_object
Представляет тип данных C PyObject *. Вызов без аргумента создаёт указатель NULL PyObject *.

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

15.15.2.8. Структурированные типы данныхStructured data types

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.

Конструкторы подклассов Structure и Union принимают как позиционные, так и именованные аргументы. Позиционные аргументы используются для инициализации полей в том же порядке, в котором они указаны в определении _fields_, а именованные аргументы используются для инициализации полей с соответствующим именем.

Можно определять подподклассы типов структур; они наследуют поля базового класса плюс поля из _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_.

15.15.2.9. Массивы и указателиArrays and pointers

Пока не написано – пожалуйста, обратитесь к разделам Pointers и Arrays в руководстве.