Ctypes возвращает строку из c функции

Я ветеран Python, но не стал много набирать в C. После полудня, когда я ничего не нашел в Интернете, который работает для меня, я думал, что попрошу здесь и получить помощь, в которой я нуждаюсь.

Что я хочу сделать, это написать простую функцию C, которая принимает строку и возвращает другую строку. Я планирую связать эту функцию на нескольких языках (Java, Obj-C, Python и т.д.), Поэтому я думаю, что она должна быть чистой C?

Вот что я до сих пор. Заметьте, что я получаю segfault при попытке получить значение в Python.

hello.c

#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

Я прочитал, что segfault вызвано тем, что C освобождает память, которая была первоначально выделена для возвращаемой строки. Может быть, я просто лаю неправильное дерево?

Какой правильный способ выполнить то, что я хочу?

Ответ 1

n hello.c вы возвращаете локальный массив. Вы должны вернуть указатель на массив, который должен быть динамически объявлен с помощью 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;
}

Ответ 2

Ваша проблема в том, что приветствие было размещено в стеке, но стек уничтожается, когда функция возвращается. Вы можете выделить память динамически:

#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")

Ответ 3

Вот что происходит. И почему это ломается. Когда вызывается hello(), указатель стека C перемещается вверх, освобождая место для любой памяти, необходимой вашей функции. Наряду с некоторыми служебными вызовами функций все ваши локальные функции функций управляются там. Таким образом, static char greeting[100] означает, что для этой строки используется 100 байт увеличенного стека. Вы, чем использовать некоторые функции, которые управляют этой памятью. Поместите указатель на стек в приветственную память. И затем вы возвращаетесь от вызова, после чего указатель стека возвращается назад к нему оригинал до позиции вызова. Таким образом, эти 100 байт, которые были в стеке на время вашего вызова, по существу, снова используются для захвата, так как стека еще больше манипулируют. Включая поле адреса, указывающее на это значение и возвращаемое вами. В этот момент, кто знает, что с ним происходит, но он, вероятно, установлен на ноль или какое-то другое значение. И когда вы пытаетесь получить к нему доступ, как будто это все еще жизнеспособная память, вы получаете segfault.

Чтобы обойти, вам нужно каким-то образом управлять этой памятью. Вы можете использовать вашу функцию alloc в памяти кучи, но вам нужно будет убедиться, что она получит free() 'ed позже, по вашей привязке. ИЛИ, вы можете написать свою функцию, чтобы язык привязки передавал ей глагол памяти, который будет использоваться.

Ответ 4

Я столкнулся с этой же проблемой сегодня и обнаружил, что вы должны переопределить тип возвращаемого значения по умолчанию (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