使用swig包装的typedef结构和python中的枚举来转换字符串/缓冲区数据

时间:2015-02-19 22:08:37

标签: python c struct casting swig

我有一些在嵌入式系统上运行的C代码,生成一个数据流,我的python代码将在蓝牙/ usb线的另一端读取。流的协议仍在大量开发中,并且经常更改,但是在单个.h文件中定义。我想使用SWIG来保持python方面的最新,特别是通过提供对流数据布局(结构)的访问

这是一个示例.h文件,描述了许多结构和一些常量(作为#defines),显然是整个协议的一个非常小的子集,为简洁起见。

//datalayouts.h

#ifdef SWIG
#define __attribute__(x)
#endif

#define TOKEN_TYPE_SYNC_VALUE 1
#define TOKEN_TYPE_DELTA 2

typedef struct __attribute__((packed))
{
    uint8_t token_type;
    uint32_t timestamp;
    uint32_t value;
} struct_token_type_sync_value;

typedef struct __attribute__((packed))
{
    uint8_t token_type;
    int16_t delta;
} struct_token_type_delta;

加上这是基本的接口文件

%module datalayouts
%{
#include "datalayouts.h"
%}
%include "datalayouts.h"

这一切都很好地编译和导入。在python中,我可以创建一个token_type_sync_value类型的变量,但我想要做的是投射一部分我从流中读取的数据(作为字符串),在其上施加正确的结构

例如:

>>> from datalayouts token_type_sync_value
>>> data = stream.read() #returns 100+ bytes
>>> if ord(data[0]) == TOKEN_TYPE_SYNC_VALUE:
...     #here I want to access data[0:9] as a token_type_sync_value

这是可能的,如果是这样的话?

1 个答案:

答案 0 :(得分:1)

您可以使用SWIG执行此操作,最简单的解决方案是使用%extend从Python中提供额外的构造函数,将PyObect用作缓冲区:

%module test

%include <stdint.i>

%inline %{
#ifdef SWIG
#define __attribute__(x)
#endif

#define TOKEN_TYPE_SYNC_VALUE 1
#define TOKEN_TYPE_DELTA 2

typedef struct __attribute__((packed))
{
    uint8_t token_type;
    int16_t delta;
} struct_token_type_delta;
%}

%extend struct_token_type_delta {
  struct_token_type_delta(PyObject *in) {
    assert(PyObject_CheckBuffer(in));
    Py_buffer view;
    const int ret = PyObject_GetBuffer(in, &view, PyBUF_SIMPLE);
    assert(0==ret);
    assert(view.len >= sizeof(struct_token_type_delta));
    struct_token_type_delta *result = new struct_token_type_delta(*static_cast<const struct_token_type_delta*>(view.buf));
    PyBuffer_Release(&view); // Note you could/should retain view.obj for the life of this object to prevent use after free
    return result;
  }
}

您需要为要从缓冲区构造的每个类型执行此操作,但每个类型的构造函数的实际代码保持不变,因此可以包装为宏(使用%define)非常简单。您还希望通过保留对底层缓冲区的引用更长时间来防止在空闲错误之后使用。


就个人而言,如果是我这样做,虽然我会寻找一个不同的解决方案,因为有更好的方法来获得相同的结果,编写创建和维护薄的POD / bean类对象的代码是乏味和沉闷的任何语言,更不用说2个或更多。假设protbuf太重了,无法在嵌入式系统中使用,我希望反过来解决这个问题,使用Python的ctypes,然后让你的Python代码也为你的C构建工具生成标题。如下所示:

import ctypes

class ProtocolStructure(type(ctypes.Structure)):
  def __str__(self):
    s='''
typedef struct __attribute__((packed)) {
\t%s
}'''
    return s % '\n\t'.join(('%s %s;' % (ty.__name__[2:], name) for name,ty in self._fields_))

class struct_token_type_delta(ctypes.Structure, metaclass=ProtocolStructure):
  _fields_ = (('token_type', ctypes.c_uint8),
              ('delta', ctypes.c_int16))

if __name__ == '__main__':
  # when this file is run instead of imported print the header file to stdout

  h='''
#ifndef PROTO_H
#define PROTO_H
%s
#endif
'''

  print(h % ';\n'.join('%s %s;\n' % (ty, name)  for name,ty in globals().items() if issubclass(type(ty), ProtocolStructure)))

然后让你写:

import proto
proto.struct_token_type_delta.from_buffer(bytearray(b'\xff\x11\x22'))