【问题标题】:ctypes - view c_char_p field of returned structctypes - 查看返回结构的 c_char_p 字段
【发布时间】:2019-05-16 03:38:54
【问题描述】:

我定义了一个简单的 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;
}

我使用gcc 将C 代码编译成.so 文件。然后,在 Python 中,我想使用 ctypes 创建一个绑定,它可以访问 C 结构的所有成员

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)

结构的整数成员(xy)打印得很好,但我不知道如何打印msg 指向的字符串。我最终看到的不是预期的hello world,而是一个字节字符串b'\x01。我从其他阅读中得到的预感是,我正在截断真实的、更长的字符串,并且只显示第一个字节。

【问题讨论】:

  • 也许您需要从字节流转换为字符串?
  • 仅供参考,您可以直接传递 b'hello world' 而无需 ctypes.c_char_p(b'hello world') 包装。
  • 关于:p = malloc(sizeof(TestStruct)); *p = initial; 1) 应始终检查 (!=NULL) 返回值以确保 mallloc 成功。 2)第二条语句不会复制initial. Suggest using: strncpy(p, &initial, sizeof(TestStruct));`的内容

标签: python c struct binding ctypes


【解决方案1】:

您正在将ctypes.c_char_p(b'hello world') 传递给init_struct,并将指向c_char_p 块的指针复制到initialp 的分配中。但是,指向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 对象,复制到字符串bhello world,null 终止它,并将指向该内存的原始指针传递给C 端。然后 C 端运行,您的代码获取该指针的副本。当 C 端返回时,ctypes 释放其对c_char_p 对象的引用。然后 Python 的 GC 发现 c_char_p 不再被引用,因此它被垃圾收集。因此,您最终会在 myStruct.msg 中得到一个悬空指针。

正确的解决方案是在 init_struct 中克隆 msg contents 并提供一个 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)

【讨论】:

    猜你喜欢
    • 2021-11-06
    • 1970-01-01
    • 2016-12-04
    • 1970-01-01
    • 2010-12-24
    • 2014-07-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多