如果映射不是一对一的,ctypes如何将C结构成员映射到Python类_fields _?

时间:2018-10-21 16:37:50

标签: python c ctypes

在阅读thisthis之后,我尝试用C编写自己的“共享库”,并尝试使用Python包装器来尝试理解ctypes。

请注意,我故意在Python包装器中注释了成员a,以探讨当我在C结构的成员与Python类_fields_之间的映射不完全一对一时会发生什么情况:

testlib.c

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

void myprint();

void myprint() {
    printf("Hello world\n");
}

typedef struct object_t {
    uint8_t a;
    uint8_t b;
    uint8_t c;
    uint8_t d;
    uint8_t e;
    uint32_t f;
    uint8_t* g;
} object_t;

static object_t object  = {'a', 'b', 'c', 'd', 'e', 12345, NULL};

object_t* func1() {
    return &object; 
}

void func2(void(*callback)(object_t*), object_t* this_object) {
    callback(this_object);
}

我将testlib.c编译到共享库testlib.so的同一文件夹中,如下所示:

gcc -shared -o testlib.so -fPIC testlib.c

wrapper.py

from ctypes import *

testlib = CDLL('./testlib.so')
testlib.myprint()

class object_t(Structure):
    _fields_ = [
    # ('a', c_uint8),
    ('b', c_uint8),
    ('c', c_uint8),
    ('d', c_uint8),
    ('e', c_uint8),
    ('f', c_uint32),
    ('g', POINTER(c_uint8)),
    ]

callback_t = CFUNCTYPE(None, POINTER(object_t)) 

func1 = testlib.func1
func1.argtypes = None
func1.restype = POINTER(object_t) 

func2 = testlib.func2
func2.argtypes = [callback_t] 
func2.restype = None

ret = func1()

# a = ret.contents.a
b = ret.contents.b
c = ret.contents.c
d = ret.contents.d
e = ret.contents.e
f = ret.contents.f
g = ret.contents.g

print("{} {} {} {} {} {}".format(
    # chr(a),
    chr(b),
    chr(c),
    chr(d),
    chr(e),
    chr(f),
    g,
    ))

def mycallback(obj):
    # a = obj.contents.a
    b = obj.contents.b
    c = obj.contents.c
    d = obj.contents.d
    e = obj.contents.e
    f = obj.contents.f
    g = obj.contents.g
    print("{} {} {} {} {} {}".format(
    # chr(a),
    chr(b),
    chr(c),
    chr(d),
    chr(e),
    chr(f),
    g,
    ))

func2(callback_t(mycallback), ret)

当我运行python wrapper.py时,得到以下输出:

Hello world
a b c d e <__main__.LP_c_ubyte object at 0x7f503901cd08>
a b c d e <__main__.LP_c_ubyte object at 0x7f503902c048>

我看到这并没有失败;相反,我仍然得到前五个字母,然后是指针对象。

该映射是否仅对应于每个成员类型应该占用的内存,然后将该内存推入我在Python字段中指定的类型,而不管它实际上是否有意义? (即我在C端的“ 12345”被放到Python端的POINTER(c_uint8)中)

我问的原因是因为我正在查看Kazam(https://bazaar.launchpad.net/~kazam-team/kazam/stable/view/head:/kazam/pulseaudio/ctypes_pulseaudio.py)中PulseAudio的ctypes绑定,并且发现pa_source_info._fields_中的许多成员都已被注释掉(第119-135行) )。

我的猜测是,如果我想取消注释其他项,除非连续地取消注释列表中的ctypes,否则ctypes将无法正确处理该映射,然后为当前未使用的类型映射添加类/类型,例如{{1 }}或pa_usec_t。有人可以确认吗?谢谢。

2 个答案:

答案 0 :(得分:2)

  

我的猜测是,如果我想取消注释其他项目,除非连续地取消注释列表中的ctypes,否则ctypes将无法正确处理该映射,然后为当前未使用的类型映射添加类/类型,例如{{1 }}或pa_usec_t

是的,确实如此。 pa_source_port_info试图完全遵循C编译器的行为(但存在一些位域错误)。存储器中的ctypes仅是struct s个连续字节。为了使ctype产生正确的布局,所有成员都必须至少具有相同的大小,并且与C结构中的相应成员具有相同的对齐要求。

this question中讨论了C结构的布局和内部填充。

答案 1 :(得分:0)

这甚至比您想象的还要糟糕:如果字节不完全重叠,则值的 part 可能会显示为下一个成员。并且填充可以很容易地预测出未对齐的内容:这里,e之后可能有三个字节,很有可能为0。删除a也删除了填充,所以Python中的f与C语言中的e对齐。