如何从Python SWIG封装中向下转换C++对象?

5 投票
3 回答
4113 浏览
提问于 2025-04-16 05:24

问题是:我用SWIG把一些C++代码封装成了Python代码。在Python这边,我想把一个封装好的C++指针转换成指向子类的指针。我在SWIG的.i文件里添加了一个新的C++函数来进行这个转换,但当我从Python调用它时,出现了类型错误(TypeError)。

具体情况是这样的:

我有两个C++类,分别是Base和Derived。Derived是Base的子类。我还有一个第三个类Container,它包含了一个Derived对象,并提供了一个访问器来获取这个对象。这个访问器返回的是一个常量的Base引用,如下所示:

class Container {
  public:
    const Base& GetBase() const {
      return derived_;
    }

  private:
    Derived derived_;
};

我用SWIG把这些类封装成了Python代码。在我的Python代码中,我想把Base的引用再转换回Derived。为此,我在SWIG的.i文件里写了一个C++的辅助函数来进行这个转换:

%inline %{
  Derived* CastToDerived(Base* base) {
    return static_cast<Derived*>(base);
  }
%}

在我的Python代码中,我调用了这个转换函数:

base = container.GetBase()
derived = CastToDerived(base)

但是当我这样做时,出现了以下错误:

TypeError: in method 'CastToDerived', argument 1 of type 'Base *'

这可能是什么原因呢?

为了参考,这里是SWIG生成的.cxx文件中相关的部分,也就是原始函数和它在Python中的对应版本:

  Derived* CastToDerived(Base* base) {
    return static_cast<Derived*>(base);
  }

//  (lots of other generated code omitted)

SWIGINTERN PyObject *_wrap_CastToDerived(PyObject *SWIGUNUSEDPARM(self), PyObject *args) {
  PyObject *resultobj = 0;
  Base *arg1 = (Base *) 0 ;
  void *argp1 = 0 ;
  int res1 = 0 ;
  PyObject * obj0 = 0 ;
  Derived *result = 0 ;

  if (!PyArg_ParseTuple(args,(char *)"O:CastToDerived",&obj0)) SWIG_fail;
  res1 = SWIG_ConvertPtr(obj0, &argp1,SWIGTYPE_p_Base, 0 |  0 );
  if (!SWIG_IsOK(res1)) {
    SWIG_exception_fail(SWIG_ArgError(res1), "in method '" "CastToDerived" "', argument " "1"" of type '" "Base *""'"); 
  }
  arg1 = reinterpret_cast< Base * >(argp1);
  result = (Derived *)CastToDerived(arg1);
  resultobj = SWIG_NewPointerObj(SWIG_as_voidptr(result), SWIGTYPE_p_Derived, 0 |  0 );
  return resultobj;
fail:
  return NULL;
}

任何帮助都将非常感谢。

-- Matt

3 个回答

0

我注意到你代码中的两个问题。第一个是GetBase返回的是一个常量的引用,第二个是CastToDerived期望的是一个指向非常量Base的指针。

即使在C++中,你也会遇到不少麻烦来让这个代码正常工作。我不太确定还有什么地方出错,但我建议你先解决这两个问题。

0

你有没有可能多次定义了基类?我之前也遇到过类似的问题,使用ctypes的时候,不小心在两个不同的模块里定义了同样的结构类。在纯Python中也发生过类似的事情,我用imp.load_module加载了一个插件类,创建了这个类的对象,然后又重新加载了模块——结果!之前创建的对象就不再通过isinstance测试了,因为重新加载的类虽然名字一样,但实际上是一个不同的类,ID也不同。(更详细的描述可以在这篇博客里找到。)

3

正如我在上面评论的,这在swig 1.3.40版本下似乎运行得不错。

以下是我的文件:

c.h:

#include <iostream>
class Base {};
class Derived : public Base
{
    public:
        void f() const { std::cout << "In Derived::f()" << std::endl; }
};
class Container {
  public:
    const Base& GetBase() const {
      return derived_;
    }
  private:
    Derived derived_;
};

c.i

%module c

%{
#define SWIG_FILE_WITH_INIT
#include "c.h"
%}

%inline %{
  Derived* CastToDerived(Base* base) {
    return static_cast<Derived*>(base);
  }
%}
class Base
{
};

class Derived : public Base
{
    public:
        void f() const;
};

class Container {
  public:
    const Base& GetBase() const;
};

ctest.py

import c

container = c.Container()
b = container.GetBase()
d = c.CastToDerived(b)
d.f()
print "ok"

一次运行的结果:

$ swig -c++ -python c.i
$ g++ -fPIC -I/usr/include/python2.6 -c -g c_wrap.cxx
$ g++ -shared -o _c.so c_wrap.o
$ python ctest.py 
In Derived::f()
ok

撰写回答