简体   繁体   English

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

[英]How do I downcast a SWIG object without access to the SWIG template?

Is there a way to downcast the swig proxy of a swig object on the fly?有没有办法即时降低 swig 对象的 swig 代理?

The reason for doing this is to emulate C++ downcasting, but purely from python.这样做的原因是为了模拟 C++ 向下转换,但纯粹来自 python。 For example, typical C++ usage would be例如,典型的 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();
}

On the Python side, the proxy objects for MyBase and MyChild exist, but all objects enter python as a MyBase type.在 Python 端,存在 MyBase 和 MyChild 的代理对象,但所有对象都作为 MyBase 类型输入 python。 I'd like to be able to write:我希望能够写:

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> >

Note in the sample output, both output lines reference the same memory address.请注意,在示例输出中,两条输出行都引用了相同的内存地址。

Ideally I'd like to do this in pure python, but if it must be wrapper around some C/C++ bear in mind I don't have access to the SWIG template or original C code.理想情况下,我想在纯 python 中执行此操作,但如果它必须围绕某些 C/C++ 进行包装,请记住,我无法访问 SWIG 模板或原始 C 代码。

The tricky bit about getting this cast to work properly is not just changing the type of the proxy, which you've done in your answer, but changing the type of the this member inside the proxy also as you recognised is needed in the desired output of your question.使这个转换正常工作的棘手之处不仅仅是更改代理的类型(您在答案中已完成),而且还更改代理中this成员的类型,正如您在所需的输出中所识别的那样你的问题。

SWIG does store enough information to make this possible and I was able to fairly simply achieve the result you desired without needing to patch/recompile/reverse-engineer/rely upon the original module's .i file in anyway, provided you can match the SWIG version that was used to build it and compiler close enough that we can rely on the struct layouts in memory being the same. SWIG 确实存储了足够的信息使这成为可能,并且我能够相当简单地实现您想要的结果,而无需修补/重新编译/逆向工程/无论如何都依赖原始模块的 .i 文件,前提是您可以匹配 SWIG 版本用于构建它和编译器足够接近我们可以依赖内存中的结构布局是相同的。 I should caveat this by saying that it feels like what I had to do to solve it ought to be more complex than necessary, but I can't see any other way of exposing the type hierarchy information from swig_type_info 's internal to the module without doing it like this.我应该警告说,感觉我必须做的事情来解决它应该比必要的更复杂,但是我看不到任何其他方式将swig_type_info内部的类型层次结构信息暴露给模块而无需这样做。

So to validate and develop this I put together the following, test.i file which is a minimal example of the problem you've got:因此,为了验证和开发这一点,我将以下 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;
};
%}

I compiled this with:我编译了这个:

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

But then didn't touch it other than by using import test inside my code.但是除了在我的代码中使用import test之外没有触及它。

If we look inside the generated test_wrap.cxx we can see the following definition of swig_type_info , which is emitted once per type that SWIG knows of:如果我们看一下生成的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;

The dcast member didn't seem to be populated (ie it was always null in my tests), but fortunately the cast member was. dcast成员似乎没有被填充(即在我的测试中它总是空的),但幸运的是cast是。 cast is a pointer to the first node in a linked-list of information about the type hierarchy, which looks like: 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;

Which has both the name of the type and the converter function populated.其中填充了类型名称和转换器函数。 So essentially what we need to do is walk that linked list to search for the converter function that performs the conversion we're after and then call it.所以本质上我们需要做的是遍历这个链表来搜索执行我们所追求的转换的转换器函数,然后调用它。 You probably could at this point do exactly that using ctypes, however I chose to make use of the fact that the exact same compiler and SWIG versions would save me having to express those structs in ctypes notation and would always be correct so I simply wrote another SWIG module with some more C++.在这一点上,您可能可以使用 ctypes 完全做到这一点,但是我选择利用这样一个事实,即完全相同的编译器和 SWIG 版本将使我不必用 ctypes 表示法表达这些结构,并且总是正确的,所以我只是写了另一个带有更多 C++ 的 SWIG 模块。

(I should add that there are internal functions which do use this information within a module, but they have static linkage by default, so they're hard to find and use here). (我应该补充一点,在模块中确实有使用此信息的内部函数,但默认情况下它们具有静态链接,因此很难在此处找到和使用它们)。

Anyway my cast.i file, which exposes this neatly back to Python ended up looking like this:无论如何,我的 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
%}

All that's doing is walking the cast linked list looking for information about the derived class.所做的只是遍历强制转换链表以查找有关派生类的信息。 There's a little bit of extra Python to wrap that up safely and handle creating a new proxy object, but that's essentially it.有一点额外的 Python 来安全地包装它并处理创建一个新的代理对象,但基本上就是这样。

With this in place it was then possible for me to run the following Python code:有了这个,我就可以运行以下 Python 代码:

import test

o=test.get_one()
print(o)

import cast

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

After having compiled my cast module:编译我的演员模块后:

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> >

Personally though I'd just take the hit on maintaining a branch of the original module, pushing a patch upstream or even just use %import to write another SWIG module which extends the original module unmodified over something like this.就我个人而言,尽管我只是尝试维护原始模块的一个分支,将补丁推送到上游,或者甚至只是使用%import编写另一个 SWIG 模块,该模块将原始模块扩展到这样的事情上,未经修改。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM