ctypes возвращают строку из функции c
Я ветеран Python, но в C не особо баловался. После полдня, когда я не нашел в интернете ничего подходящего, я подумал, что попрошу здесь и получу необходимую помощь.
Я хочу написать простую функцию C, которая принимает строку и возвращает другую строку. Я планирую связать эту функцию на нескольких языках (Java, Obj-C, Python и т. Д.), Поэтому я думаю, что это должен быть чистый C?
Вот что у меня так далеко. Заметьте, что я получаю segfault при попытке получить значение в Python.
Привет
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
const char* hello(char* name) {
static char greeting[100] = "Hello, ";
strcat(greeting, name);
strcat(greeting, "!\n");
printf("%s\n", greeting);
return greeting;
}
main.py
import ctypes
hello = ctypes.cdll.LoadLibrary('./hello.so')
name = "Frank"
c_name = ctypes.c_char_p(name)
foo = hello.hello(c_name)
print c_name.value # this comes back fine
print ctypes.c_char_p(foo).value # segfault
Я читал, что ошибка вызвана тем, что C освобождает память, которая была первоначально выделена для возвращаемой строки. Может я просто лаю не на то дерево?
Как правильно выполнить то, что я хочу?
3 ответа
Привет. Вы возвращаете локальный массив. Вы должны вернуть указатель на массив, который должен быть динамически объявлен с использованием malloc.
char* hello(char* name)
{
char hello[] = "Hello ";
char excla[] = "!\n";
char *greeting = malloc ( sizeof(char) * ( strlen(name) + strlen(hello) + strlen(excla) + 1 ) );
if( greeting == NULL) exit(1);
strcpy( greeting , hello);
strcat(greeting, name);
strcat(greeting, excla);
return greeting;
}
Ваша проблема в том, что приветствие было размещено в стеке, но стек уничтожается, когда функция возвращается. Вы можете выделить память динамически:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
const char* hello(char* name) {
char* greeting = malloc(100);
snprintf("Hello, %s!\n", 100, name)
printf("%s\n", greeting);
return greeting;
}
Но это только часть битвы, потому что теперь у вас утечка памяти. Вы можете подключить это с помощью другого вызова ctypes для free ().
... или гораздо лучший подход - прочитать официальную привязку C к python (python 2.x на http://docs.python.org/2/c-api/ и python 3.x на http://docs.python.org/3/c-api/). Сделайте так, чтобы ваша функция C создала строковый объект python и передала его обратно. Это будет мусор, собираемый Python автоматически. Поскольку вы пишете на стороне C, вам не нужно играть в игру ctypes.
...редактировать..
Я не компилировал и не тестировал, но я думаю, что.py будет работать:
import ctypes
# define the interface
hello = ctypes.cdll.LoadLibrary('./hello.so')
# find lib on linux or windows
libc = ctypes.CDLL(ctypes.util.find_library('c'))
# declare the functions we use
hello.hello.argtypes = (ctypes.c_char_p,)
hello.hello.restype = ctypes.c_char_p
libc.free.argtypes = (ctypes.c_void_p,)
# wrap hello to make sure the free is done
def hello(name):
_result = hello.hello(name)
result = _result.value
libc.free(_result)
return result
# do the deed
print hello("Frank")
Сегодня я столкнулся с той же проблемой и обнаружил, что вы должны переопределить тип возвращаемого значения по умолчанию (int
) установив restype
по методу. См. Типы возвращаемых данных в документации ctype здесь.
import ctypes
hello = ctypes.cdll.LoadLibrary('./hello.so')
name = "Frank"
c_name = ctypes.c_char_p(name)
hello.hello.restype = ctypes.c_char_p # override the default return type (int)
foo = hello.hello(c_name)
print c_name.value
print ctypes.c_char_p(foo).value
Вот что происходит. И почему это ломается. Когда вызывается hello(), указатель стека C перемещается вверх, освобождая место для любой памяти, необходимой вашей функции. Наряду с некоторыми накладными расходами вызова функций, все ваши локальные функции управляются там. Чтобы static char greeting[100]
, означает, что 100 байтов увеличенного стека предназначены для этой строки. Вы чем используете некоторые функции, которые манипулируют этой памятью. В это место вы помещаете указатель в стек на память приветствия. И затем вы возвращаетесь из вызова, после чего указатель стека возвращается обратно в исходное положение до вызова. Таким образом, те 100 байтов, которые находились в стеке на время вашего вызова, по сути, снова пригодятся для захвата, так как стеком в дальнейшем манипулируют. Включая поле адреса, которое указывало на это значение и которое вы вернули. В тот момент, кто знает, что с ним происходит, но он, вероятно, установлен на ноль или какое-то другое значение. И когда вы пытаетесь получить к нему доступ, как будто это все еще жизнеспособная память, вы получаете segfault.
Чтобы обойти, нужно как-то по-другому управлять этой памятью. Вы можете иметь свою функцию alloc
съел память в куче, но вам нужно убедиться, что она получает free()
под редакцией позже, по вашей привязке. ИЛИ, вы можете написать свою функцию так, чтобы язык связывания передавал ей кусочек памяти для использования.
Я тоже столкнулся с той же проблемой, но использовал другой подход. Я должен был найти строку в списке строк, соответствующих определенному значению.
В основном я инициализировал массив символов с размером самой длинной строки в моем списке. Затем передал это как аргумент моей функции для хранения соответствующего значения.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void find_gline(char **ganal_lines, /*line array*/
size_t size, /*array size*/
char *idnb, /* id number for check */
char *resline) {
/*Iterates over lines and finds the one that contains idnb
then affects the result to the resline*/
for (size_t i = 0; i < size; i++) {
char *line = ganal_lines[i];
if (strstr(line, idnb) != NULL) {
size_t llen = strlen(line);
for (size_t k = 0; k < llen; k++) {
resline[k] = line[k];
}
return;
}
}
return;
}
Эта функция была обернута соответствующей функцией python:
def find_gline_wrap(lines: list, arg: str, cdll):
""
# set arg types
mlen = maxlen(lines) # gives the length of the longest string in string list
linelen = len(lines)
line_array = ctypes.c_char_p * linelen
cdll.find_gline.argtypes = [
line_array,
ctypes.c_size_t,
ctypes.c_char_p,
ctypes.c_char_p,
]
#
argbyte = bytes(arg, "utf-8")
resbyte = bytes("", "utf-8")
ganal_lines = line_array(*lines)
size = ctypes.c_size_t(linelen)
idnb = ctypes.c_char_p(argbyte)
resline = ctypes.c_char_p(resbyte * mlen)
pdb.set_trace()
result = cdll.find_gline(ganal_lines, size, idnb, resline)
# getting rid of null char at the end
result = resline.value[:-1].decode("utf-8")
return result