Введение
Примеры
Основное использование
Допустим, мы хотим использовать libc
«s ntohl
функцию.
Во- первых, мы должны загрузить libc.so
:
>>> from ctypes import *
>>> libc = cdll.LoadLibrary('libc.so.6')
>>> libc
<CDLL 'libc.so.6', handle baadf00d at 0xdeadbeef>
Затем мы получаем объект функции:
>>> ntohl = libc.ntohl
>>> ntohl
<_FuncPtr object at 0xbaadf00d>
И теперь мы можем просто вызвать функцию:
>>> ntohl(0x6C)
1811939328
>>> hex(_)
'0x6c000000'
Что делает именно то, что мы ожидаем.
Общие подводные камни
Не удалось загрузить файл
Первая возможная ошибка - не удается загрузить библиотеку. В этом случае обычно возникает OSError.
Это либо потому, что файл не существует (или не может быть найден в ОС):
>>> cdll.LoadLibrary("foobar.so")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.5/ctypes/__init__.py", line 425, in LoadLibrary
return self._dlltype(name)
File "/usr/lib/python3.5/ctypes/__init__.py", line 347, in __init__
self._handle = _dlopen(self._name, mode)
OSError: foobar.so: cannot open shared object file: No such file or directory
Как видите, ошибка ясна и довольно показательна.
Вторая причина в том, что файл найден, но имеет неправильный формат.
>>> cdll.LoadLibrary("libc.so")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.5/ctypes/__init__.py", line 425, in LoadLibrary
return self._dlltype(name)
File "/usr/lib/python3.5/ctypes/__init__.py", line 347, in __init__
self._handle = _dlopen(self._name, mode)
OSError: /usr/lib/i386-linux-gnu/libc.so: invalid ELF header
В этом случае файл представляет собой файл сценария , а не .so
файл. Это может также произойти при попытке открыть .dll
- файл на компьютере Linux или 64 - битный файл на переводчике 32bit питона. Как вы можете видеть, в этом случае ошибка немного более расплывчата и требует некоторого изучения.
Неспособность получить доступ к функции
Предположим , что мы успешно загрузили .so
файл, затем мы должны получить доступ к нашей функции , как мы сделали на первом примере.
Когда несуществующая функция используются, AttributeError
поднимаются:
>>> libc.foo
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.5/ctypes/__init__.py", line 360, in __getattr__
func = self.__getitem__(name)
File "/usr/lib/python3.5/ctypes/__init__.py", line 365, in __getitem__
func = self._FuncPtr((name_or_ordinal, self))
AttributeError: /lib/i386-linux-gnu/libc.so.6: undefined symbol: foo
Базовый объект ctypes
Самый основной объект - это int:
>>> obj = ctypes.c_int(12)
>>> obj
c_long(12)
Теперь, obj
относится к кусок памяти , содержащей значение 12.
К этому значению можно получить прямой доступ и даже изменить его:
>>> obj.value
12
>>> obj.value = 13
>>> obj
c_long(13)
Поскольку obj
относится к кусок памяти, мы также можем узнать это размер и расположение:
>>> sizeof(obj)
4
>>> hex(addressof(obj))
'0xdeadbeef'
массивы типов
Как знает любой хороший программист на Си, одно значение не поможет вам так далеко. Что действительно заставит нас работать, так это массивы!
>>> c_int * 16
<class '__main__.c_long_Array_16'>
Это не фактический массив, но это чертовски близко! Мы создали класс , который обозначает массив 16 int
с.
Теперь все, что нам нужно сделать, это инициализировать его:
>>> arr = (c_int * 16)(*range(16))
>>> arr
<__main__.c_long_Array_16 object at 0xbaddcafe>
Теперь arr
является актуальной массив, содержащий числа от 0 до 15.
К ним можно получить доступ, как и к любому списку:
>>> arr[5]
5
>>> arr[5] = 20
>>> arr[5]
20
И точно так же , как любые другие ctypes
объекта, он также имеет размер и расположение:
>>> sizeof(arr)
64 # sizeof(c_int) * 16
>>> hex(addressof(arr))
'0xc000l0ff'
Функции обтекания для ctypes
В некоторых случаях функция C принимает указатель на функцию. Как алчный ctypes
пользователи, мы хотели бы использовать эти функции, и даже передать функции питона в качестве аргументов.
Давайте определим функцию:
>>> def max(x, y):
return x if x >= y else y
Теперь эта функция принимает два аргумента и возвращает результат того же типа. Для примера давайте предположим, что тип - это int.
Как и в примере с массивом, мы можем определить объект, обозначающий этот прототип:
>>> CFUNCTYPE(c_int, c_int, c_int)
<CFunctionType object at 0xdeadbeef>
Это прототип обозначает функцию , которая возвращает c_int
(первый аргумент), и принимает два c_int
аргумента (другие аргументы).
Теперь давайте обернем функцию:
>>> CFUNCTYPE(c_int, c_int, c_int)(max)
<CFunctionType object at 0xdeadbeef>
Функциональные прототипы имеют на более частом использовании: они могут обернуть ctypes
функции (например , libc.ntohl
) и убедитесь , что используется правильные аргументы при вызове функции.
>>> libc.ntohl() # garbage in - garbage out
>>> CFUNCTYPE(c_int, c_int)(libc.ntohl)()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: this function takes at least 1 argument (0 given)
Комплексное использование
Давайте объединить все из примеров выше , в один сложный сценарий: с помощью libc
«ы lfind
функции.
Для получения более подробной информации о функции, прочитать страницу человека.Я призываю вас прочитать это, прежде чем продолжить.
Сначала мы определим правильные прототипы:
>>> compar_proto = CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))
>>> lfind_proto = CFUNCTYPE(c_void_p, c_void_p, c_void_p, POINTER(c_uint), c_uint, compar_proto)
Затем давайте создадим переменные:
>>> key = c_int(12)
>>> arr = (c_int * 16)(*range(16))
>>> nmemb = c_uint(16)
И теперь мы определяем функцию сравнения:
>>> def compar(x, y):
return x.contents.value - y.contents.value
Обратите внимание на то, что x
и y
являются POINTER(c_int)
, так что мы должны разыменовать их и принимать их значения для того , чтобы реально сравнить значение , хранящееся в памяти.
Теперь мы можем объединить все вместе:
>>> lfind = lfind_proto(libc.lfind)
>>> ptr = lfind(byref(key), byref(arr), byref(nmemb), sizeof(c_int), compar_proto(compar))
ptr
является возвращаемым недействительным указателем. Если key
не был найден в arr
, значение не будет None
, но в данном случае мы получили действительное значение.
Теперь мы можем преобразовать его и получить доступ к значению:
>>> cast(ptr, POINTER(c_int)).contents
c_long(12)
Кроме того , мы можем видеть , что ptr
указывает на правильное значение внутри arr
:
>>> addressof(arr) + 12 * sizeof(c_int) == ptr
True