Модуль ctypes

Введение

Примеры

Основное использование

Допустим, мы хотим использовать 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 

Синтаксис

Параметры

Примечания