【问题标题】:How to apply SWIG OUTPUT typemaps for class types in Python?如何在 Python 中为类类型应用 SWIG OUTPUT 类型映射?
【发布时间】:2015-09-09 11:43:35
【问题描述】:

我在使用 SWIG(版本 3.0.6)围绕 C++ 库生成 Python 包装器时遇到了一些问题。

我的问题与应用 OUTPUT 类型映射有关,特别是在指针/引用类类型的情况下。

为了说明,这就是我想要的标准类型,并且它有效:

// .h
int add(const long arg1,const long arg2,long& resultLong);

// interface.i
%apply long& OUTPUT { long& resultLong };
int add(const long arg1,const long arg2,long& resultLong);

// projectWrapper.py
def add(arg1, arg2):
    return _projectWrapper.add(arg1, arg2)
addTerm = _projectWrapper.add

// usage
>>> result = projectWrapper.add(2, 4)
>>> print result
[0, 6L]

您不必传入“resultLong”,但它会自动附加到结果中。太好了!

但是,当输出类型是指向类类型的指针时,这似乎不像我预期的那样工作:

// .h
int GetClassType(const char* name, exportedClassType*& resultPointer);

class exportedClassType
{...}

// interface.i
%apply exportedClassType*& OUTPUT { exportedClassType*& resultPointer };    
int GetClassType(const char* name, exportedClassType*& resultPointer);

// projectWrapper.py
def GetClassType(name, resultPointer):
    return _projectWrapper.GetClassType(name, resultPointer)
GetClassType = _projectWrapper.GetClassType

问题似乎是 SWIG 没有以与简单类型相同的方式处理它。它仍然作为包装函数签名中的“输入”参数出现。

// attempted usage
>>> classType = projectWrapper.GetClassType("name")
TypeError: GetClassType() takes exactly 2 arguments (1 given)

>>> result = 0
>>> projectWrapper.GetClassType("name", result)
Traceback (most recent call last):
File "<input>", line 1, in <module>
TypeError: in method 'GetClassType', argument 2 of type 'exportedClassType *&'

有人可以告诉我我做错了什么或指出我正确的方向吗?任何帮助都感激不尽!谢谢

【问题讨论】:

  • 你试过使用双指针吗?我在使用 SWIG 2.0.7 时遇到了双指针和生成代码的问题,但这在 3.X.X 中得到了解决
  • 感谢您的回复。抱歉,interface.i 文件中的内容是什么样的?
  • 我应该提到我不拥有 C++ 并且不能在那里更改方法签名。我肯定需要处理采用exportedClassType*&amp; 参数的方法。
  • 好的,所以没有纯C接口。您可以做一个简单的包装器,它改为公开一个双指针。最好,你可以在你的接口文件中制作这个包装器。
  • 这里给出了这个问题的解决方案stackoverflow.com/questions/12739331/…

标签: python c++ swig


【解决方案1】:

这个问题已经有很长一段时间没有解决了,所以我认为我最好为这个问题提供一个解决方案。 OUTPUT 类型映射仅适用于简单类型,因此通过组合inargout 类型映射给出了解决方案。

考虑这种情况,我们有一个 C++ 类 SampleImpl 实现了一个 C++ 接口 SampleBase,这在技术上不是一个接口,因为它涉及到一个虚拟析构函数的实现。假设我们有一个静态函数,它返回错误代码和接口的实现。后者作为对指针的引用,也就是上面的情况。

接口头:

// Sample.hpp
#pragma once
namespace Module {
  class SampleBase {
  public:
#ifndef SWIG
    // Hint to the programmer to implement this function
    static int SampleCreate(SampleBase *&obj);
#endif
    virtual ~SampleBase() = default;
  };
}

实现标头:

// Sample_impl.hpp
#pragma once
#include "Sample.hpp"

namespace Module {
  class SampleImpl : public SampleBase {
  public:
    static int SampleCreate(Module::SampleBase *&obj);

    SampleImpl();
    virtual ~SampleImpl();
  private:
    float a;
  };
}

实施:

// Sample_impl.cpp
#include "Sample_impl.hpp"
#include <cstdio>

namespace Module {
  int SampleImpl::SampleCreate(Module::SampleBase*& obj) {
    obj = (SampleBase*) new SampleImpl();
    return 0;
  }
  SampleImpl::SampleImpl() {
    printf("SampleImpl::SampleImpl()\n");
  }

  SampleImpl::~SampleImpl() {
    printf("SampleImpl::~SampleImpl()\n");
  }
}

SWIG 接口(使用 argout 类型映射)

// example.i
%module example
%{
  #define SWIG_FILE_WITH_INIT
  #include "Sample.hpp"
  #include "Sample_impl.hpp"
%}

%include "typemaps.i"

%typemap(in, numinputs=0) Module::SampleBase *&obj (Module::SampleBase *temp) {
  $1 = &temp;
}

%typemap(argout) Module::SampleBase *& {
  PyObject* temp = NULL;
  if (!PyList_Check($result)) {
    temp = $result;
    $result = PyList_New(1);
    PyList_SetItem($result, 0, temp);

    // Create shadow object (do not use SWIG_POINTER_NEW)
    temp = SWIG_NewPointerObj(SWIG_as_voidptr(*$1),
             $descriptor(Module::SampleBase*),
             SWIG_POINTER_OWN | 0);

    PyList_Append($result, temp);
    Py_DECREF(temp);
  }
}

在 Python 中的使用

import example

// Creating specialization
obj = example.SampleImpl()
del obj

// Creation of object using output typemap
errorCode, obj = example.SampleImpl_SampleCreate()
del obj

【讨论】:

  • 不客气。我进行了更改,只有在构造函数或函数返回 0 且没有错误时才将对象添加到输出列表中。
【解决方案2】:

这不是一个答案,只是没有足够的声誉来发表评论:(

因为您需要在 C++ 中使用指针,而 Python 没有指针(因此无论如何您都无法对 Python 中的当前“结果”做任何事情)。

您能否按照@Jens Munk 的建议添加包装器以将指针隐藏到 .h 中:

class exportedClassType_ptr {
public:
    exportedClassType* ptr;
    exportedClassType_ptr( exportedClassType& input ) {
        this->ptr = &input;
    }
};

int GetClassType( const char* name, exportedClassType_ptr& resultPointer ) {
    return GetClassType( name, resultPointer.ptr );
}

修改 .i 文件以调用新方法:

%apply exportedClassType_ptr& OUTPUT { exportedClassType_ptr& resultPointer };    
int GetClassType( const char* name, exportedClassType_ptr& resultPointer );

在 Python 中这样写:

>>> realResult = projectWrapper.exportedClassType()
>>> result = projectWrapper.exportedClassType_ptr(realResult)
>>> projectWrapper.GetClassType("name", result)

并在未来的工作中使用“realResult”。

【讨论】:

  • 感谢您的回答。我现在明白 Jens Munk 的 cmets 到底是什么意思了。我在实现这一点上有一个问题。如果要包装的方法是类方法,比如exportedClassType.GetClassType,该怎么办?如何在需要 exportedClasssType_ptr 的类上创建一个新的覆盖方法?
  • 没有实例很难给出建议。例如,您可以创建自己的派生类class newClassWithoutPointers: public oldClassWithPointers { } 并使用输入指针数据更改所有方法。但如果所有这些方法都调用newdelete 来获取输入指针,它可能会以错误的方式工作。
  • 对于我的用例来说,这并不完全完整(参见上面的 cmets),但它是一个清晰的完整示例,它解释了该方法并让我摆脱了困境。你得到了赏金,再次感谢。
【解决方案3】:

我认为您需要使用指针。我也不确定在混合类型映射和返回语句时会发生什么。一个最小的示例文件tst.i:

%module tst

%{

  // declaration:
  void add(long *resultLong, const long arg1,const long arg2);
  long mul(const long a, const long b);

  // the code:
  void add(long *resultLong, const long arg1,const long arg2) {
    *resultLong = arg1 + arg2;
  }
  long mul(const long a, const long b) {
    return a*b;
  }

%}

// The wrapper:
%apply (long* OUTPUT) { long* resultLong }; 
void add(long* resultLong, const long arg1,const long arg2);
long mul(const long a, const long b);

翻译后(我一直用CMake),在python中的用法是:

import tst
x = tst.add(3, 4)  # results in 7L    
y = tst.mul(3, 4)  # results in 12L

我认为对于标量数据类型,最好使用返回语句而不是类型映射。连接数组时,我建议使用numpy.i 的预定义类型映射。

【讨论】:

  • 对不起,我不明白这如何适用于问题中所问的类类型。
猜你喜欢
  • 1970-01-01
  • 2016-07-25
  • 2019-01-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-18
  • 1970-01-01
相关资源
最近更新 更多