Spec-Zone.ru › Python 3.12

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

Исходный код: Lib/ctypes

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

Практическое руководство по ctypes

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

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

Загрузка динамических библиотек

ctypes экспортирует объекты cdll, а в Windows — windll и oledll для загрузки динамических библиотек.

Библиотеки загружаются, обращаясь к ним как к атрибутам этих объектов. cdll загружает библиотеки, которые экспортируют функции, используя стандартную cdecl конвенцию вызова, в то время как windll библиотеки вызывают функции, используя stdcall конвенцию вызова. oledll также использует stdcall конвенцию вызова и предполагает, что функции возвращают код ошибки Windows HRESULT. Код ошибки используется для автоматического повышения исключения OSError при неудачном вызове функции.

Изменено в версии 3.3: Ошибки Windows раньше вызывали WindowsError, который сейчас является алиасом OSError.

Ниже приведены примеры для Windows. Обратите внимание, что msvcrt — это стандартная библиотека MS C, содержащая большинство стандартных функций C, и использует конвенцию вызова cdecl:

>>> from ctypes import *
>>> print(windll.kernel32)  
<WinDLL 'kernel32', handle ... at ...>
>>> print(cdll.msvcrt)      
<CDLL 'msvcrt', handle ... at ...>
>>> libc = cdll.msvcrt      
>>>

Windows автоматически добавляет обычное .dll расширение файла.

Примечание

Обращение к стандартной библиотеке C через cdll.msvcrt будет использовать устаревшую версию библиотеки, которая может быть несовместима с той, которая используется Python. В тех случаях, где это возможно, используйте встроенные возможности Python, а в иных случаях импортируйте и используйте модуль msvcrt.

В Linux необходимо указать имя файла включая расширение для загрузки библиотеки, поэтому доступ к атрибутам для загрузки библиотек невозможен. Должен использоваться метод LoadLibrary() загрузчиков dll, или вы должны загрузить библиотеку, создав экземпляр CDLL, вызвав конструктор:

>>> cdll.LoadLibrary("libc.so.6")  
<CDLL 'libc.so.6', handle ... at ...>
>>> libc = CDLL("libc.so.6")       
>>> libc                           
<CDLL 'libc.so.6', handle ... at ...>
>>>

Доступ к функциям из загруженных 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
>>>

Обратите внимание, что win32 системные dll, такие как 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).

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

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

Основные типы данных

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

Тип ctypes

Тип C

Тип Python

c_bool

_Bool

bool (1)

c_char

char

объект типа bytes длиной в 1 символ

c_wchar

wchar_t

строка длиной в 1 символ

c_byte

char

целое число

c_ubyte

unsigned char

целое число

c_short

short

целое число

c_ushort

unsigned short

целое число

c_int

int

целое число

c_uint

unsigned int

целое число

c_long

long

целое число

c_ulong

unsigned long

целое число

c_longlong

__int64 или long long

целое число

c_ulonglong

unsigned __int64 или unsigned long long

целое число

c_size_t

size_t

целое число

c_ssize_t

ssize_t или Py_ssize_t

целое число

c_time_t

time_t

целое число

c_float

float

число с плавающей точкой

c_double

double

число с плавающей точкой

c_longdouble

long double

число с плавающей точкой

c_char_p

char* (с завершающим нулём)

объект типа bytes или None

c_wchar_p

wchar_t* (с завершающим нулём)

строка или None

c_void_p

void*

целое число или 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 bytes неизменяемы):

>>> 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; если вы хотите получить его как строку с завершающим нулём, используйте свойство 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>
ArgumentError: argument 2: 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
>>>

Вызов функций с переменным числом аргументов

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

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

libc.printf.argtypes = [ctypes.c_char_p]

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

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

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

>>> class Bottles:
...     def __init__(self, number):
...         self._as_parameter_ = number
...
>>> bottles = Bottles(42)
>>> printf(b"%d bottles of beer\n", bottles)
42 bottles of beer
19
>>>

Если вы не хотите хранить данные экземпляра в переменной экземпляра _as_parameter_, вы можете определить свойство property, которое делает этот атрибут доступным по запросу.

Указание требуемых типов аргументов (прототипы функций)

Можно указать требуемые типы аргументов функций, экспортируемых из DLL, установив атрибут argtypes.

argtypes должен быть последовательностью C-типов данных (функция printf() — вероятно, не лучший пример здесь, потому что она принимает переменное количество и разные типы параметров в зависимости от строки формата, с другой стороны, это довольно удобно для экспериментов с этой функцией):

>>> printf.argtypes = [c_char_p, c_char_p, c_int, c_double]
>>> printf(b"String '%s', Int %d, Double %f\n", b"Hi", 10, 2.2)
String 'Hi', Int 10, Double 2.200000
37
>>>

Указание формата защищает от несовместимых типов аргументов (точно так же, как прототип C-функции), и пытается преобразовать аргументы в допустимые типы:

>>> printf(b"%d %d %d", 1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ArgumentError: argument 2: TypeError: wrong type
>>> printf(b"%s %d %f\n", b"X", 2, 3)
X 2 3.000000
13
>>>

Если вы определили собственные классы, которые передаёте в вызовы функций, вам нужно реализовать метод класса from_param(), чтобы использовать их в последовательности argtypes. Метод класса from_param() получает объект Python, переданный в вызов функции; он должен выполнить проверку типа или любые другие необходимые действия, чтобы убедиться, что этот объект приемлем, а затем вернуть сам объект, его атрибут _as_parameter_, или что-то ещё, что вы хотите передать как аргумент C-функции в этом случае. Результат должен быть целым числом, строкой, байтами, экземпляром ctypes или объектом с атрибутом _as_parameter_.

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

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

C-прототип time() — 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(), которая ожидает указатель на строку и символ, а возвращает указатель на строку:

>>> 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 типа «один символ» в объект типа 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 API FormatMessage() для получения строкового представления кода ошибки и возвращать исключение. WinError принимает необязательный параметр кода ошибки; если он не используется, он вызывает GetLastError() для его получения.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Массивы

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

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

TenPointsArrayType = POINT * 10

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

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

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

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

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

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

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

Указатели

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

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

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

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

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

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

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

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

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

>>> pi[0]
99
>>>

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

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

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

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

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

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

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

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

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

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

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

Обычно ctypes выполняет строгую проверку типов. Это означает, что если у вас POINTER(c_int) в списке аргументов функции или в качестве типа поля структуры, принимаются только экземпляры ровно того же типа. Существуют исключения из этого правила, когда 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_ позже, после оператора класса:

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

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

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

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

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

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

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

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

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

Результат:

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

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

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

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

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

Функции-фабрики могут быть использованы как фабрики декораторов, поэтому мы можем написать так:

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

Примечание

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

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

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

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

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

>>> 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 является указателем на массив записей 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 не имеет ожидаемого эффекта.

Помните, что извлечение подобъектов из структур, союзов и массивов не копирует подобъект, а возвращает оберточный объект, имеющий доступ к основному буферу корневого объекта.

Еще один пример, который может вести себя иначе, чем ожидается, это:

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

Примечание

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

Почему он печатает False? Экземпляры ctypes — это объекты, содержащие блок памяти плюс некоторые описатели, которые обеспечивают доступ к содержимому памяти. Сохранение объекта Python в блоке памяти не сохраняет сам объект, вместо этого сохраняется 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 и (пере)определении типа данных после того, как необходимый размер уже известен, в каждом конкретном случае.

Справочник ctypes

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

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

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

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

ctypes.util.find_library(name)

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

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

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

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

Вот несколько примеров:

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

В macOS 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, так как API Windows не возвращает эту информацию, что затрудняет диагностику. Для устранения этой ошибки и определения отсутствующей DLL необходимо найти список зависимых DLL и определить, какая из них отсутствует, используя инструменты отладки и трассировки Windows.

Изменено в версии 3.12: Параметр name теперь может быть объектом пути.

См. также

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

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

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

Изменено в версии 3.3: WindowsError использовался ранее, теперь он является алиасом OSError.

Изменено в версии 3.12: Параметр name теперь может быть объектом пути.

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

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

Изменено в версии 3.12: Параметр name теперь может быть объектом пути.

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

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

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

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

Изменено в версии 3.12: Параметр name теперь может быть объектом пути.

Все эти классы можно создать, вызвав их с, по крайней мере, одним аргументом — именем пути к динамической библиотеке. Если у вас уже есть дескриптор уже загруженной динамической библиотеки, его можно передать в качестве параметра 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, управляемого функциями API Windows GetLastError() и SetLastError(); для запроса и изменения внутренней копии кода ошибки Windows используются ctypes.get_last_error() и ctypes.set_last_error().

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

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

ctypes.RTLD_GLOBAL

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

ctypes.RTLD_LOCAL

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

ctypes.DEFAULT_MODE

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

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

>>> from ctypes import CDLL
>>> libc = CDLL("libc.so.6")  # 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.

Для прямого доступа к API C Python доступен готовый объект Python общей библиотеки:

ctypes.pythonapi

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

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

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

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

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

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

class ctypes._FuncPtr

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

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

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

restype

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

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

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

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

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

Функции прототипы

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

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

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

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

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

ctypes.PYFUNCTYPE(restype, *argtypes)

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

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

prototype(address)

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

prototype(callable)

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

prototype(func_spec[, paramflags])

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

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

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

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

Функции-утилиты

ctypes.addressof(obj)

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

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

ctypes.alignment(obj_or_type)

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

ctypes.byref(obj[, offset])

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

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

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

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

ctypes.cast(obj, type)

Эта функция похожа на оператор приведения типов в C. Она возвращает новый экземпляр type, указывающий на тот же блок памяти, что и obj. type должен быть типом указателя, а obj — объектом, который может быть интерпретирован как указатель.

ctypes.create_string_buffer(init_or_size, size=None)

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

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

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

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

ctypes.create_unicode_buffer(init_or_size, size=None)

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

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

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

Вызывает событие аудита 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, или номера версии (это формат, используемый для параметра позиксной компоновки -l). Если библиотека не найдена, возвращает None.

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

ctypes.util.find_msvcrt()

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

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

ctypes.FormatError([code])

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

ctypes.GetLastError()

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

ctypes.get_errno()

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

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

ctypes.get_last_error()

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

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

ctypes.memmove(dst, src, count)

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

ctypes.memset(dst, c, count)

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

ctypes.POINTER(type, /)

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

ctypes.pointer(obj, /)

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

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

ctypes.resize(obj, size)

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

ctypes.set_errno(value)

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

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

ctypes.set_last_error(value)

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

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

ctypes.sizeof(obj_or_type)

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

ctypes.string_at(ptr, size=-1)

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

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

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

Типы данных

class ctypes._CData

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

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

from_buffer(source[, offset])

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

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

from_buffer_copy(source[, offset])

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

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

from_address(address)

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

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

from_param(obj)

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

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

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 типа bytes или строка.

Когда из экземпляра ctypes извлекается атрибут value, обычно каждый раз возвращается новый объект. 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_ssize_t

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

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

class ctypes.c_time_t

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

Добавлен в версии 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

Представляет тип данных C 64-битного 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.

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

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

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

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

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_ после оператора класса, определяющего подкласс Structure, это позволяет создавать типы данных, которые прямо или косвенно ссылаются на себя:

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

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

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

_pack_

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

_anonymous_

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

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

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

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

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

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

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

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

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

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

class ctypes.Array(*args)

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

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

_length_

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

_type_

Определяет тип каждого элемента в массиве.

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

class ctypes._Pointer

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

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

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

_type_

Определяет тип, на который указывает указатель.

contents

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

© 2001–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.12/library/ctypes.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API