SWIG Python C++ 结构体作为输入/输出参数

3
说实话,我在这个网站上阅读了很多关于“struct”主题的文章,但我需要你的帮助。我有C风格的结构体。
   struct Time
   {
      uint16_t  year; //    year with four digits like 2016
      uint8_t   month; // 1 .. 12
      uint8_t   day; // 1 .. 31
      uint8_t   hour; // 0 .. 23, 24 hour representation
      uint8_t   minute; // 0 .. 59
      uint8_t   second; // 0 .. 59
   };

使用类和成员函数,并将其实现在DLL中。

class DeviceInterface {
virtual uint32_t getTime(Time&  time) = 0;
};

uint32_t值是状态码。

以下是自动生成的SWIG C++代码:

SWIGINTERN PyObject *_wrap_getTime(PyObject *SWIGUNUSEDPARM(self), PyObject *args) {
  PyObject *resultobj = 0;
  DeviceInterface *arg1 = (DeviceInterface *) 0 ;
  Time *arg2 = 0 ;
  void *argp1 = 0 ;
  int res1 = 0 ;
  void *argp2 = 0 ;
  int res2 = 0 ;
  PyObject *swig_obj[2] ;
  uint32_t result;
  
  if (!SWIG_Python_UnpackTuple(args, "getTime", 2, 2, swig_obj)) SWIG_fail;
  res1 = SWIG_ConvertPtr(swig_obj[0], &argp1,SWIGTYPE_p_DeviceInterface, 0 |  0 );
  if (!SWIG_IsOK(res1)) {
    SWIG_exception_fail(SWIG_ArgError(res1), "in method '" "getTime" "', argument " "1"" of type '" "DeviceInterface *""'"); 
  }
  arg1 = reinterpret_cast< DeviceInterface * >(argp1);
  res2 = SWIG_ConvertPtr(swig_obj[1], &argp2, SWIGTYPE_p_Time,  0 );
  if (!SWIG_IsOK(res2)) {
    SWIG_exception_fail(SWIG_ArgError(res2), "in method '" "getTime" "', argument " "2"" of type '" "Time &""'"); 
  }
  if (!argp2) {
    SWIG_exception_fail(SWIG_ValueError, "invalid null reference " "in method '" "getTime" "', argument " "2"" of type '" "Time &""'"); 
  }
  arg2 = reinterpret_cast< Time * >(argp2);
  result = (uint32_t)(arg1)->getTime(*arg2);
  resultobj = SWIG_From_unsigned_SS_int(static_cast< unsigned int >(result));
  return resultobj;
fail:
  return NULL;
}

从上面的代码中,我看不到时间值,即arg2变量,是如何返回的。那么我需要在SWIG接口文件中编写什么来获取状态码和时间

1个回答

5
你可以编写一个类型映射器将输出参数Time附加到结构中。 SWIG为该结构生成代理,可以通过SWIG_NewPointerObj()根据需要生成。以下是完整示例:

DeviceInterface.h(最小实现)

#include <stdint.h>

struct Time
{
    uint16_t year;   // year with four digits like 2016
    uint8_t  month;  // 1 .. 12
    uint8_t  day;    // 1 .. 31
    uint8_t  hour;   // 0 .. 23, 24 hour representation
    uint8_t  minute; // 0 .. 59
    uint8_t  second; // 0 .. 59
};

class DeviceInterface {
public:
    virtual uint32_t getTime(Time& time) {
        time.year = 2021;
        time.month = 12;
        time.day = 30;
        time.hour = 9;
        time.minute = 47;
        time.second = 30;
        return 0;
    }
};

test.i

%module test

%{
#include "DeviceInterface.h"
%}

%include <stdint.i>

// Do not require an input parameter for Time.
// Instead, SWIG will allocate one.
%typemap(in,numinputs=0) Time& %{
    $1 = new Time;
%}

// After calling the function, process Time as an output.
// Convert the allocated pointer to a SWIG Python wrapper
// and pass ownership to Python.  Python will free the object
// when it goes out of scope.
%typemap(argout) Time& (PyObject* tmp) %{
    // Convert C pointer to SWIG Python wrapper
    tmp = SWIG_NewPointerObj($1, $1_descriptor, SWIG_POINTER_OWN);
    // Append to existing uint32_t return value
    $result = SWIG_Python_AppendOutput($result, tmp);
%}

%include "DeviceInterface.h"

演示:

>>> import test
>>> d=test.DeviceInterface()
>>> r=d.getTime()
>>> r
[0, <test.Time; proxy of <Swig Object of type 'Time *' at 0x000001ED4F866090> >]
>>> r[1].year
2021
>>> r[1].month
12

如果您想要定制Time的SWIG封装对象的显示,使其更加人性化可读,可以使用以下方法扩展Time对象:
%module test

%{
// Added to support the __repr__ implementation
#include <string>
#include <sstream>

#include "DeviceInterface.h"
%}

%include <stdint.i>
%include <std_string.i> // SWIG support for std::string.

%typemap(in,numinputs=0) Time& %{
    $1 = new Time;
%}

%typemap(argout) Time& (PyObject* tmp) %{
    tmp = SWIG_NewPointerObj($1, $1_descriptor, SWIG_POINTER_OWN);
    $result = SWIG_Python_AppendOutput($result, tmp);
%}

// Extend time to suport Python's __repr__.
// It must return a string representing how to display the object in Python.
%extend Time {
    std::string __repr__()
    {
        std::ostringstream ss;
        ss << "Time(year=" << $self->year << ", month=" << (unsigned)$self->month
           << ", day=" << (unsigned)$self->day << ", hour=" << (unsigned)$self->hour
           << ", minute=" << (unsigned)$self->minute << ", second=" << (unsigned)$self->second << ")";
        return ss.str();
    }
}

%include "DeviceInterface.h"

示例:

>>> import test
>>> d=test.DeviceInterface()
>>> r=d.getTime()
>>> r
[0, Time(year=2021, month=12, day=30, hour=9, minute=47, second=30)]

+100500 很棒,谢谢。我可以返回由Py_BuildValue打包的元组而不是由SWIG_Python_AppendOutput创建的列表吗? - devaskim
1
@Den SWIG_Python_AppendOutput 是智能的。如果正确处理void返回值和多个类型映射的实例,就像您之前提出的问题有两个输出参数一样。如果您考虑构建时间元组而不是代理时间对象,请注意Time代理可以传递给其他需要Time对象的函数,而元组必须再次转换为Time对象。 - Mark Tolonen
我的意思是返回一个元组,以获取以下内容:status_code, time = d.getTime(),而不是返回状态码列表和时间对象。 - devaskim
@Den 你也可以使用列表来实现,不一定非要用元组。 - Mark Tolonen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接