ctypes._Pointer
:Конкретные типы указателей создаются путем вызова функции ctypes.POINTER()
с типом, на который они будут указывать. Это делается автоматически с помощью функции ctypes.pointer()
.
Если указатель указывает на массив, то его элементы могут быть прочитаны и записаны с использованием стандартного доступа по индексу и срезу.
Объекты-указатели не имеют размера, поэтому функция len()
вызовет исключение TypeError
.
Отрицательные индексы будут считываться из памяти перед указателем (как в C), а индексы вне диапазона, сломаются при нарушении прав доступа.
Атрибуты класса ctypes._Pointer
Pointer._type_
:Атрибут Pointer._type_
возвращает тип, на который указывает указатель.
Pointer.contents
:Атрибут Pointer.contents
возвращает объект, на который указывает указатель.
Присвоение этому атрибуту - изменяет указатель на вновь назначенный объект.
ctypes
.Экземпляры указателей создаются путем вызова функции ctypes.pointer()
для типа модуля ctypes
:
>>> from ctypes import * >>> i = c_int(42) >>> pi = pointer(i)
Экземпляры указателей имеют атрибут Pointer.contents
, который возвращает объект, на который указывает указатель. Смотрим на что указывает созданный указатель pi
в коде выше:
>>> pi.contents # c_long(42)
Обратите внимание, что модуль ctypes
не имеет OOR (возврат исходного объекта), он создает новый эквивалентный объект каждый раз, когда извлекается атрибут:
>>> pi.contents is i # False >>> pi.contents is pi.contents # False
Назначение другого экземпляра ctypes.c_int
атрибуту указателя .contents
приведет к тому, что он будет указывать на то место памяти, где он хранится:
>>> i = c_int(99) >>> pi.contents = i >>> pi.contents # c_long(99)
Экземпляры указателя также индексируются целыми числами:
>>> pi[0] # 99
Присвоение целочисленному индексу - изменяет указанное значение:
>>> print(i) c_long(99) >>> pi[0] = 22 >>> print(i) c_long(22)
Также можно использовать индексы, отличные от 0, но при этом необходимо понимать что вы делаете. Как и в C: можно получить доступ к произвольным ячейкам памяти или изменить их. Обычно эту функциональность используют только в том случае, если получают указатель от C-функции, который фактически указывает на массив, а не на отдельный элемент.
За кулисами функция ctypes.pointer()
делает больше, чем просто создает экземпляры указателя, она должна сначала создать типы указателей. Это делается с помощью функции ctypes.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_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
Иногда функция api языка C ожидает указатель на тип данных в качестве параметра, для записи в соответствующее место или, если данные слишком велики, для передачи их по значению. Это также известно как передача параметров по ссылке.
Модуль ctypes
экспортирует функцию ctypes.byref()
, которая используется для передачи параметров по ссылке. Тот же эффект может быть достигнут с помощью функции ctypes.pointer()
, хотя ctypes.pointer()
выполняет гораздо больше работы, т.к. создает реальный объект-указатель, поэтому функция ctypes.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'