如何在不访问SWIG模板的情况下向下转换SWIG对象?

时间:2017-01-09 02:48:08

标签: python swig

有没有办法在运行中向下转换swig对象的swig代理?

这样做的原因是模仿C ++向下转换,但纯粹来自python。例如,典型的C ++用法是

MyBase* obj = new MyBase();    // eg might come via network
if (obj.is_child())            // runtime info lets us know what we are dealing with
{
    MyChild* child = (MyChild*) obj;
    obj->some_child_function();
}

在Python方面,存在MyBase和MyChild的代理对象,但所有对象都以MyBase类型的形式输入python。我希望能写下来:

obj = MyBase(); # eg might come from network
print(obj)
if obj.is_child():
    child_obj = MyChild.from_raw_ptr(obj.get_raw_ptr())  # Pseudo-code
    child_obj.some_child_function();
    print(obj)

Output like:
<MyBase; proxy of <Swig Object of type 'MyBase *' at 0x00000000069D46C0> >
<MyChild; proxy of <Swig Object of type 'MyChild *' at 0x00000000069D46C0> >

注意在示例输出中,两个输出行都引用相同的内存地址。

理想情况下,我想在纯python中执行此操作,但如果它必须包含在某些C / C ++中,请记住我无法访问SWIG模板或原始C代码。

1 个答案:

答案 0 :(得分:1)

关于让这个强制转换正常工作的棘手问题不仅仅是改变你在答案中所做的代理类型,还改变了代理中this成员的类型。如你所知,你需要在你想要的问题输出中找到。

SWIG确实存储了足够的信息以使其成为可能,我能够相当简单地实现您想要的结果,而无需修改/重新编译/反向工程/依赖原始模块的.i文件, 提供你可以匹配用于构建它的SWIG版本和编译器足够接近,以至于我们可以依赖于内存中的struct布局是相同的。我应该告诉我,感觉就像我必须做的那样解决它应该比必要的更复杂,但是我无法看到任何其他方式来公开来自swig_type_info&的类型层次结构信息#39;模块内部没有像这样做。

为了验证和开发这个,我把以下的test.i文件放在一起,这是你遇到的问题的最小例子:

%module test

%inline %{

struct BaseClass {
    virtual bool is_derived() const { return false; }
    virtual ~BaseClass() {} 
};

struct DerivedClass : BaseClass {
    virtual bool is_derived() const { return true; }
    void do_something_special() {}
};

BaseClass *get_one() {
    static DerivedClass b;
    return &b;
};
%}

我编译了这个:

swig3.0 -Wall -c++ -python test.i
g++ -Wall -Wextra -o _cast.so -shared -fPIC cast_wrap.cxx -I/usr/include/python2.7

但是除了在我的代码中使用import test之外,没有触及它。

如果我们查看生成的test_wrap.cxx,我们可以看到swig_type_info的以下定义,每个SWIG知道的类型会发出一次:

/* Structure to store information on one type */
typedef struct swig_type_info {
  const char             *name;                 /* mangled name of this type */
  const char             *str;                  /* human readable name of this type */
  swig_dycast_func        dcast;                /* dynamic cast function down a hierarchy */
  struct swig_cast_info  *cast;                 /* linked list of types that can cast into this type */
  void                   *clientdata;           /* language specific type data */
  int                    owndata;               /* flag if the structure owns the clientdata */
} swig_type_info;

dcast成员似乎没有被填充(即我的测试中它总是为空),但幸运的是cast成员。 cast是指向有关类型层次结构的信息的链接列表中第一个节点的指针,如下所示:

/* Structure to store a type and conversion function used for casting */
typedef struct swig_cast_info {
  swig_type_info         *type;                 /* pointer to type that is equivalent to this type */
  swig_converter_func     converter;            /* function to cast the void pointers */
  struct swig_cast_info  *next;                 /* pointer to next cast in linked list */
  struct swig_cast_info  *prev;                 /* pointer to the previous cast */
} swig_cast_info;

其中包含类型名称和转换器函数。基本上我们需要做的就是遍历链表以搜索执行我们之后转换然后调用它的转换器函数。你可能在这一点上完全使用ctypes,但是我选择使用完全相同的编译器和SWIG版本将节省我必须以ctypes表示法表达那些结构并且总是正确的事实,所以我只是写了另一个带有更多C ++的SWIG模块。

(我应该补充说,有一些内部函数在模块中使用这些信息,但默认情况下它们具有静态链接,因此在这里很难找到并使用它们。)

无论如何,我的cast.i文件将这个整齐地暴露回Python,最终看起来像这样:

%module cast

%{
#include <iostream>
%}

%inline %{
    PyObject *dyn_cast(PyObject *obj) {
        assert(SwigPyObject_Check(obj));
        SwigPyObject *s = (SwigPyObject*)obj;
        void *ptr = s->ptr;
        swig_cast_info *cast = s->ty->cast;
        while (cast) {
            std::cerr << "Cast @" << cast << ", converter: " << (void*)cast->converter << ", type: " << cast->type->str << "\n";
            if (0==strcmp(cast->type->name, "_p_DerivedClass")) break; 
            cast = cast->next;
        }
        assert(cast->converter);
        int newmem;
        s->ptr = cast->converter(ptr, &newmem);
        s->ty = cast->type;
        Py_INCREF(obj);
        return obj;
    }
%}

%pythoncode %{
import test

def base_to_derived(o):
    if not isinstance(o, test.BaseClass): raise TypeError()
    if not o.is_derived(): raise TypeError()
    c = test.DerivedClass.__new__(test.DerivedClass)
    c.this = dyn_cast(o.this)
    return c
%}

所有这一切都是在转换链接列表中寻找有关派生类的信息。还有一些额外的Python来安全地包装它并处理创建一个新的代理对象,但实质上就是它。

有了这个,我就可以运行以下Python代码了:

import test

o=test.get_one()
print(o)

import cast

o=cast.base_to_derived(o)
print(o)
o.do_something_special()

编译完演员模块后:

swig3.0 -Wall -c++ -python cast.i
g++ -Wall -Wextra -o _cast.so -shared -fPIC cast_wrap.cxx -I/usr/include/python2.7
python run.py 
<test.BaseClass; proxy of <Swig Object of type 'BaseClass *' at 0xb6cd9428> >
Cast @0xb6ccf5ec, converter: 0xb6cbcdf1, type: DerivedClass *
<test.DerivedClass; proxy of <Swig Object of type 'DerivedClass *' at 0xb6cd9428> >

就个人而言,我只是维持原始模块的一个分支,向上游推送补丁,甚至只使用%import编写另一个SWIG模块,该模块将原始模块扩展为未经修改的类似此