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
Другие вопросы по тегам