Функция C, вызываемая из Python через ctypes, возвращает неверное значение
Я написал простую функцию на C, которая возводит заданное число в заданную степень. Функция возвращает правильное значение, когда я вызываю ее на C, но когда я вызываю ее на Python, она возвращает другое, неверное значение.
Я создал общий файл с помощью команды: $ gcc -fPIC -shared -o test.so test.c
Я пробовал разные конфигурации функции C, некоторые из которых возвращают ожидаемое значение, а некоторые - нет. Например, когда моя функция a simple square использует return x*x
, без for
цикла, она возвращает правильное значение в Python.
Я надеюсь, что в конечном итоге смогу вызвать функцию C на python, которая вернет двумерный массив C.
#include <stdio.h>
float power(float x, int exponent)
{
float val = x;
for(int i=1; i<exponent; i++){
val = val*x;
}
return val;
}
from ctypes import *
so_file = '/Users/.../test.so'
functions = CDLL(so_file)
functions.power.argtype = [c_float, c_int]
functions.power.restype = c_float
print(functions.power(5,3))
Я получаю ожидаемый результат 125.0 при вызове функции на C, но когда я вызываю функцию на python, она возвращает значение 0.0.
Я впервые использую ctypes. Допустил ли я очевидную ошибку, которая приводит к неправильному вычислению функции?
Переведено автоматически
Ответ 1
Листинг [Python.Документы]: ctypes - библиотека внешних функций для Python.
Для того, чтобы все было правильно преобразовано (Python <=> C) при вызове функции (находящейся в .dll (.so)), необходимо указать 2 вещи (оставляя в стороне соглашение о вызовах x86 (pc032) (Win)):
Типы аргументов
Возвращаемый тип
В CTypes это достигается путем указания:
argtypes - последовательность (кортеж, список), содержащая тип аргумента каждой функции (CTypes) в том порядке, в каком они указаны в объявлении функции (если функция имеет только один аргумент, должна быть последовательность элементов)
restype - единственный тип CTypes (возвращаемый тип функции)
Примечание: альтернативой вышеописанному является создание прототипов внешних функций (CFUNCTYPE, WINFUNCTYPE, PYFUNCTYPE - проверьте раздел Прототипы функций (в URL в начале)).
В любом случае:
Не удается указать
Орфографическая ошибка (что, по сути, то же самое, что и в предыдущем маркере)
любое из них (при необходимости (1)) приведет к применению значений по умолчанию: все они обрабатываются (в стиле C89) как int значения, которые (в большинстве систем) имеют длину 32 бита.
Это порождает неопределенное поведение (2) (также применяется при их неправильном указании), особенно в 064-битном процессоре / ОС (и процессе Python), где значения более крупных типов (например, указатели) могут быть усечены (проверьте [SO]: Максимальное и минимальное значение целых чисел C-типов из Python (ответ @CristiFati) для получения подробной информации (limits -> size) относительно целых типов C).
Отображаемые ошибки могут быть многочисленными и иногда вводить в заблуждение.
Вы неправильно написали argtype (отсутствует s в конце).
Исправьте это, и все будет в порядке
Пример:
Для функции func, экспортируемой с помощью libdll.dll (libdll.so) со следующим заголовком:
double func(uint32_t ui32,
float f,
long long vll[8],
void *pv,
char *pc,
uint8_t *pui8);
Эквивалентом Python было бы:
import ctypes as cts
func = libdll.func
func.argtypes = (cts.c_uint32,
cts.c_float,
cts.c_longlong * 8,
cts.c_void_p,
cts.c_char_p,
cts.POINTER(cts.c_ubyte))
'''
Last 2 argument types are almost the same (1st is signed while 2nd is unsigned).
- 1st is used for NUL terminated strings (that work with strcpy, strlen, ...).
Python automatically converts them to bytes
- 2nd is used for buffers in general (which may contain NUL (0x00) characters)
'''
func.restype = cts.c_double
Некоторые (более разрушительные) результаты того же (или очень похожего) сценария (есть много других):
Footnotes
#1: Technically, there are situations where it's not required to specify them. But even then, it's best to have them specified in order to eliminate any possible confusion:
Function with no arguments:
function_from_dll.argtypes = ()
Function returning void:
function_from_dll.restype = None
#2: Undefined Behavior ([Wikipedia]: Undefined behavior) as the name suggests, is a situation when the outcome of a piece of code can't be "predicted" (or guaranteed by the C (C++) standard). The main cases:
Works as expected
Doesn't work as expected
Has some funny outputs / side effects
Crashes
The "beauty" of it is that sometimes it seems totally random, sometimes it "only reproduces" under some specific circumstances (different machines, different OSes, different environments, ...). Bottom line is that all of them are purely coincidental! The problem lies in the code (can be the current code (highest chances) or other code it uses (libraries, compilers)).