ctypes - просмотр поля c_char_p возвращаемой структуры - PullRequest
0 голосов
/ 15 декабря 2018

Я определил простую структуру C под названием TestStruct и функцию init_struct для создания экземпляра и возврата на него указателя

#include <stdlib.h>
#include <stdio.h>

typedef struct {
    int x;
    int y;
    char* msg;
} TestStruct;

TestStruct* init_struct(int x, int y, char* msg) {
    TestStruct* p;
    TestStruct initial = {x, y, msg};
    p = malloc(sizeof(TestStruct));
    *p = initial;
    return p;
}

Я компилирую код C в .soфайл с использованием gcc.Затем в Python я хочу создать привязку, используя ctypes, которая может получить доступ ко всем членам структуры struct

import ctypes
import os

class PyStruct(ctypes.Structure):
    _fields_ = [('x', ctypes.c_int), 
                ('y', ctypes.c_int),         
                ('msg', ctypes.c_char_p)]

lib = ctypes.cdll.LoadLibrary(os.path.abspath('/path/to/libstruct.so'))
_init_struct = lib.init_struct
_init_struct.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_char_p]
_init_struct.restype = ctypes.POINTER(PyStruct)

myStruct = _init_struct(1, 4, ctypes.c_char_p(b'hello world'))
print(myStruct.contents.x, myStruct.contents.y, myStruct.contents.msg)

Целочисленные члены структуры (x и y) printв порядке, но я не могу понять, как напечатать строку, на которую указывает msg.Вместо ожидаемого hello world я вижу строку байтов b'\x01.Из других прочтений я догадываюсь, что я усекаю истинную, более длинную строку и показываю только первый байт.

1 Ответ

0 голосов
/ 17 декабря 2018

Вы передаете ctypes.c_char_p(b'hello world') в init_struct и копируете указатель на блок c_char_p в присваиваниях initial и p.Однако этот указатель на блок c_char_p является действительным только на время вызова init_struct, т. Е. После возврата init_struct указатель c_char_p больше не будет действительным и доступ к немуэто будет неопределенное поведение.Другими словами, копия этого указателя, которую вы взяли в myStruct.msg, свисает и никогда не должна быть доступна за пределами init_struct.

Помните, что ctypes действительно НЕ нарушает сборку мусора в Python(GC) правила.В этой строке myStruct = _init_struct(1, 4, ctypes.c_char_p(b'hello world')) ctypes выделит некоторый объект c_char_p, скопирует в строку b hello world, завершит его нулевым значением и передаст необработанный указатель на эту память стороне C.Затем выполняется сторона C, и ваш код получает копию этого указателя.Когда сторона C возвращается, ctypes освобождает свою ссылку на объект c_char_p.Затем Python GC находит, что на c_char_p больше нет ссылок, и поэтому он собирает мусор.Следовательно, вы получите висячий указатель в myStruct.msg.

. Правильное решение - клонировать msg содержимое внутри init_struct и предоставить функцию fini_struct для освобождения этогоклонировать память, когда вы закончите, что-то вроде:

#include <stdlib.h>
#include <stdio.h>

typedef struct {
    int x;
    int y;
    char* msg;
} TestStruct;

TestStruct* init_struct(int x, int y, char* msg) {
    TestStruct* p = malloc(sizeof(TestStruct));
    p->x = x;
    p->y = y;
    p->msg = strdup(msg);
    return p;
}

void fini_struct(TestStruct* p) {
    free(p->msg);
    free(p);
}

Тогда сторона Python:

import ctypes
import os

class PyStruct(ctypes.Structure):
    _fields_ = [('x', ctypes.c_int), 
                ('y', ctypes.c_int),         
                ('msg', ctypes.c_char_p)]

lib = ctypes.cdll.LoadLibrary(os.path.abspath('/path/to/libstruct.so'))
_init_struct = lib.init_struct
_init_struct.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_char_p]
_init_struct.restype = ctypes.POINTER(PyStruct)

_fini_struct = lib.fini_struct
_fini_struct.argtypes = [ctypes.POINTER(PyStruct)]

myStruct = _init_struct(1, 4, ctypes.c_char_p(b'hello world'))
print(myStruct.contents.x, myStruct.contents.y, myStruct.contents.msg)

# when you are done with myStruct
_fini_struct(myStruct)
...