ctypes — Библиотека иностранных функций для Python

Источник: Lib/ctypes


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

учебник по ctypes

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

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

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

Доступ к функциям осуществляется как к атрибутам объектов dll:

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

Обратите внимание, что системные 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, а затем вызвать ее с помощью байтов или строковых объектов соответственно.

Иногда 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. В этом примере используется функция rand(), которая не принимает аргументов и возвращает псевдослучайное целое число:

>>> print(libc.rand())  
1804289383

В Windows можно вызвать функцию GetModuleHandleA(), которая возвращает хэндл модуля win32 (передав None в качестве единственного аргумента, можно вызвать ее с указателем NULL):

>>> 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, целые числа, байтовые объекты и (unicode) строки - единственные родные объекты Python, которые можно напрямую использовать в качестве параметров в этих вызовах функций. None передается как указатель на C NULL, объекты bytes и строки передаются как указатель на блок памяти, содержащий их данные (char* или 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 или long long

int

c_ulonglong

unsigned __int64 или unsigned long long

int

c_size_t

size_t

int

c_ssize_t

ssize_t или Py_ssize_t

int

c_time_t

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

строка или 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)              # the memory location has changed
c_wchar_p(139966783348904)
>>> print(c_s.value)
Hi, there
>>> print(s)                # first object is unchanged
Hello, World
>>>

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

>>> from ctypes import *
>>> p = create_string_buffer(3)            # create a 3 byte buffer, initialized to NUL bytes
>>> print(sizeof(p), repr(p.raw))
3 b'\x00\x00\x00'
>>> p = create_string_buffer(b"Hello")     # create a buffer containing a NUL terminated string
>>> print(sizeof(p), repr(p.raw))
6 b'Hello\x00'
>>> print(repr(p.value))
b'Hello'
>>> p = create_string_buffer(b"Hello", 10) # create a 10 byte buffer
>>> 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 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>
ctypes.ArgumentError: argument 2: 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 точно такой же, как и вызов функций с фиксированным числом параметров. На некоторых платформах, в частности ARM64 для платформ Apple, соглашение о вызове переменных функций отличается от соглашения о вызове обычных функций.

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

libc.printf.argtypes = [ctypes.c_char_p]

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

Вызов функций с собственными пользовательскими типами данных

Вы также можете настроить преобразование аргументов ctypes так, чтобы в качестве аргументов функции использовались экземпляры ваших собственных классов. ctypes ищет атрибут _as_parameter_ и использует его в качестве аргумента функции. Атрибут должен быть целым числом, строкой, байтом, экземпляром 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>
ctypes.ArgumentError: argument 2: TypeError: 'int' object cannot be interpreted as ctypes.c_char_p
>>> 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 объекта функции.

Прототипом time() на языке C является time_t time(time_t *). Поскольку time_t может иметь тип, отличный от возвращаемого по умолчанию типа int, следует указать атрибут restype:

>>> libc.time.restype = c_time_t

Типы аргументов могут быть указаны с помощью argtypes:

>>> libc.time.argtypes = (POINTER(c_time_t),)

Чтобы вызвать функцию с указателем NULL в качестве первого аргумента, используйте None:

>>> print(libc.time(None))  
1150640792

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

>>> strchr = libc.strchr
>>> strchr(b"abcdef", ord("d"))  
8059983
>>> strchr.restype = c_char_p    # c_char_p is a pointer to a string
>>> 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")
b'def'
>>> strchr(b"abcdef", b"def")
Traceback (most recent call last):
ctypes.ArgumentError: argument 2: TypeError: one character bytes, bytearray or integer expected
>>> print(strchr(b"abcdef", b"x"))
None
>>> strchr(b"abcdef", b"d")
b'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 - это функция, которая вызывает Windows FormatMessage() api, чтобы получить строковое представление кода ошибки, и возвращает исключение. 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, содержащая две точки с именами верхний левый и нижний правый:

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

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

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

Структура/союзная схема, выравнивание и порядок байтов

По умолчанию поля Structure и Union размещаются так же, как это делает компилятор языка C. Это поведение можно полностью отменить, указав в определении подкласса атрибут класса _layout_; подробности см. в документации к атрибуту.

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

Также можно задать минимальное выравнивание для упаковки самого подкласса, подобно тому, как работает #pragma align(n) в MSVC. Этого можно добиться, указав атрибут класса :_align_ в определении подкласса.

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

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

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

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

>>> 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 при разыменовании указателей (но разыменование недействительных указателей non-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 выполняет строгую проверку типов. Это означает, что если в списке argtypes функции или в определении структуры в качестве типа поля-члена указан POINTER(c_int), то принимаются только экземпляры точно такого же типа. Из этого правила есть некоторые исключения, когда 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; /* forward declaration */

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 недоступен в самом утверждении класса. В 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 = b"foo"
>>> c2 = cell()
>>> c2.name = b"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.

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

Я приведу пример, в котором используется стандартная функция qsort() библиотеки C, предназначенная для сортировки элементов с помощью функции обратного вызова. 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() до тех пор, пока они используются из кода на Си. ctypes не сохраняются, и если вы этого не сделаете, они могут быть собраны в мусор, что приведет к аварийному завершению программы при выполнении обратного вызова.

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

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

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

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

>>> version = ctypes.c_int.in_dll(ctypes.pythonapi, "Py_Version")
>>> print(hex(version.value))
0x30c00a0

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

Цитирую документацию по этому значению:

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

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

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

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

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

Поскольку table является pointer в массиве записей 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
zipimport 12345
>>>

Тот факт, что в стандартном 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
>>> # now swap the two points
>>> 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 - это объекты, содержащие блок памяти и несколько descriptors, обращающихся к содержимому памяти. При хранении объекта Python в блоке памяти сам объект не хранится, вместо этого хранится contents объекта. При повторном обращении к содержимому каждый раз создается новый объект 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 и (пере)определять тип данных после того, как требуемый размер уже известен, в каждом конкретном случае.

ссылка на типсы

Поиск общих библиотек

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

Цель функции 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'
>>>

В macOS и Android 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=None)

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

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

Изменено в версии 3.12: Параметр name теперь может быть path-like object.

См.также

Microsoft DUMPBIN tool – Инструмент для поиска зависимых DLL.

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

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

Изменено в версии 3.3: Раньше поднимался WindowsError, который теперь является псевдонимом OSError.

Изменено в версии 3.12: Параметр name теперь может быть path-like object.

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

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

Изменено в версии 3.12: Параметр name теперь может быть path-like object.

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

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

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

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

Изменено в версии 3.12: Параметр name теперь может быть path-like object.

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

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

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

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

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

Параметр winmode используется в Windows для указания способа загрузки библиотеки (поскольку mode игнорируется). Он принимает любое значение, допустимое для параметра Win32 API LoadLibraryEx flags. Если значение опущено, по умолчанию используются флаги, которые приводят к наиболее безопасной загрузке 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")  # On 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 Python api доступен готовый объект общей библиотеки Python:

ctypes.pythonapi

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

Поднимает auditing event ctypes.dlopen с аргументом name.

Поднимает auditing event ctypes.dlsym с аргументами library, name.

Поднимает auditing event ctypes.dlsym/handle с аргументами handle, name.

Иностранные функции

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

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

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

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

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

exception ctypes.ArgumentError

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

Поднимает auditing event ctypes.set_exception с аргументом code.

Поднимает auditing event ctypes.call_function с аргументами func_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. Во время вызова функция освобождает GIL. use_errno и use_last_error имеют то же значение, что и выше.

ctypes.PYFUNCTYPE(restype, *argtypes)

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

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

prototype(address)

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

prototype(callable)

Создайте вызываемую функцию C (функцию обратного вызова) из вызываемой функции Python.

prototype(func_spec[, paramflags])

Возвращает внешнюю функцию, экспортируемую общей библиотекой. func_spec должен представлять собой 2-кортеж (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)
>>>

Функции с выходными параметрами автоматически возвращают значение выходного параметра, если он один, или кортеж, содержащий значения выходных параметров, если их несколько. Так, функция GetWindowRect теперь возвращает при вызове экземпляр 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.

Поднимает auditing event 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 должно быть целым числом, задающим размер массива, или объектом bytes, который будет использоваться для инициализации элементов массива.

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

Поднимает auditing event ctypes.create_string_buffer с аргументами init, size.

ctypes.create_unicode_buffer(init_or_size, size=None)

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

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

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

Поднимает auditing event ctypes.create_unicode_buffer с аргументами init, size.

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-private копии системной errno переменной в вызывающем потоке.

Поднимает auditing event ctypes.get_errno без аргументов.

ctypes.get_last_error()

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

Поднимает auditing event 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-private копии системной errno переменной в вызывающем потоке в значение и верните предыдущее значение.

Поднимает auditing event ctypes.set_errno с аргументом errno.

ctypes.set_last_error(value)

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

Поднимает auditing event ctypes.set_last_error с аргументом error.

ctypes.sizeof(obj_or_type)

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

ctypes.string_at(ptr, size=-1)

Возвращает байтовую строку по адресу void *ptr. Если указан size, то он используется в качестве размера, в противном случае строка принимается с нулевым окончанием.

Поднимает auditing event ctypes.string_at с аргументами ptr, size.

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

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

Изменено в версии 3.3: Раньше создавался экземпляр WindowsError, который теперь является псевдонимом OSError.

ctypes.wstring_at(ptr, size=-1)

Возвращает широкосимвольную строку в void *ptr. Если указан size, то он используется как количество символов в строке, в противном случае предполагается, что строка завершается нулем.

Поднимает auditing event ctypes.wstring_at с аргументами ptr, size.

Типы данных

class ctypes._CData

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

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

from_buffer(source[, offset])

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

Поднимает auditing event ctypes.cdata/buffer с аргументами pointer, size, offset.

from_buffer_copy(source[, offset])

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

Поднимает auditing event ctypes.cdata/buffer с аргументами pointer, size, offset.

from_address(address)

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

Поднимает auditing event ctypes.cdata с аргументом 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, которые необходимо поддерживать в живом состоянии, чтобы содержимое блока памяти оставалось актуальным. Этот объект используется только для отладки; никогда не изменяйте содержимое этого словаря.

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

class ctypes._SimpleCData

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

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

value

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

Когда атрибут 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. Конструктор принимает необязательный инициализатор 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-битный тип данных 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_ssize_t

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

Added in version 3.2.

class ctypes.c_time_t

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

Added in version 3.12.

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

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.

Структурированные типы данных

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

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

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

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

Added in version 3.11.

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

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

Added in version 3.11.

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

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

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

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

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

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

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

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

_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_, иначе он не будет иметь никакого эффекта. Установка этого атрибута в 0 равносильна его отсутствию.

_align_

Необязательное маленькое целое число, позволяющее переопределить выравнивание структуры при упаковке или распаковке в/из памяти. Установка этого атрибута в 0 равносильна его отсутствию.

_layout_

Необязательная строка с именем макета структуры/объединения. В настоящее время может иметь значение:

  • "ms": раскладка, используемая компилятором Microsoft (MSVC). В GCC и Clang эту раскладку можно выбрать с помощью __attribute__((ms_struct)).

  • "gcc-sysv": раскладка, используемая GCC с моделью данных System V или «SysV-like», применяемой в Linux и macOS. При такой раскладке _pack_ должен быть не установлен или равен нулю.

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

  • В Windows: "ms"

  • Если указано _pack_: "ms"

  • Иначе: "gcc-sysv"

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

_anonymous_

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

Поля, перечисленные в этой переменной, должны быть полями типа structure или union. 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

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