2017-01-09 5 views
1

Gibt es eine Möglichkeit, den SWIG-Proxy eines SWIG-Objekts im laufenden Betrieb zu verkleinern?Wie kann ich ein SWIG-Objekt ohne Zugriff auf die SWIG-Vorlage downcast?

Der Grund dafür ist, C++ - Downcasting zu emulieren, aber rein von Python. Zum Beispiel würde typische C++ Nutzung

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(); 
} 

Auf der Python Seite sein, die Proxy-Objekte für MyBase und MyChild existieren, aber alle Objekte geben Python als MyBase Typen. Ich möchte in der Lage sein zu schreiben:

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

Hinweis in der Beispielausgabe, beide Ausgangslinien verweisen auf die gleiche Speicheradresse.

Idealerweise möchte ich dies in reinem Python machen, aber wenn es um C/C++ - Wrapper geht, habe ich keinen Zugriff auf die SWIG-Vorlage oder den ursprünglichen C-Code.

+0

Aus Neugier, wie Menschen am Ende wollen, ohne Quelle zu ändern, oder zumindest in der Lage zu bezahlen/fragen? – Flexo

+0

Ist es sicher anzunehmen, dass SWIG atleast über beide Typen Bescheid weiß, auch wenn keine Übertragung unterstützt wird? – Flexo

+0

SWIG kennt beide Typen - dh es gibt SWIG-Objekte vom Typ 'MyBase *' und 'MyChild *'. Ich möchte nicht einen Zweig der Quelle pflegen müssen - es lohnt sich nicht für meinen Anwendungsfall. Auf der Oberfläche scheint Downcasting eine übliche Operation zu sein, die keinen benutzerdefinierten Code auf der C/C++ Seite benötigt. Wenn es in C/C++ getan werden musste, würde ich versuchen, einen Wrapper zu schreiben, der mit den SWIG C/C++ - Typen arbeitete, ohne den ursprünglichen Code zu modifizieren - im Grunde schreibe eine dynamic_cast -Funktion und enthülle das in Python ... aber Ich dachte, ich würde diese Frage stellen, um zu sehen, ob es einen Konsens über die beste Praxis gibt. – Zero

Antwort

1

Das heikle Bit um diese Umwandlung richtig zu funktionieren ist nicht nur die Art des Proxy, die Sie in Ihrer Antwort getan haben, sondern auch die Art der this Mitglied im Proxy ändern, wie Sie erkannt, ist erforderlich in der gewünschten Ausgabe Ihrer Frage.

SWIG Speicher genügend Informationen, tut dies möglich zu machen, und ich konnte das gewünschte Ergebnis, ohne zu wünschen ziemlich einfach erreichen/recompile zu patchen/Reverse-Engineering/stützen sich auf die .i-Datei des ursprünglichen Modul in wie auch immer, zur Verfügung gestellt Sie können die SWIG-Version, die verwendet wurde, um sie zu erstellen, und den Compiler so nah beieinander setzen, dass wir uns darauf verlassen können, dass die Strukturlayouts im Speicher dieselben sind. Ich sollte dies mit der Aussage ablehnen, dass es sich anfühlt, dass das, was ich zu lösen hatte, komplexer sein sollte als notwendig, aber ich kann keine andere Art sehen, die Typhierarchieinformation von swig_type_info 's intern auf dem Modul ohne auszugeben mach es so.

So zu validieren und zu entwickeln, das ich zusammen die folgenden, test.i-Datei, die ein minimales Beispiel für das Problem ist was du hast:

%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; 
}; 
%} 

ich dies mit kompilierte:

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

Aber dann habe ich es nicht anders als mit import test in meinem Code.

Wenn wir innerhalb der generierten test_wrap.cxx schauen wir die folgende Definition von swig_type_info sehen können, die einmal pro Typ emittiert wird, die SWIG kennt:

/* 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; 

Das dcast Mitglied schien nicht aufgefüllt werden (dh es war immer Null in meinen Tests), aber zum Glück war das cast Mitglied. cast ist ein Zeiger auf den ersten Knoten in einer verketteten Liste von Informationen über die Art Hierarchie, die wie folgt aussieht:

/* 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; 

die sowohl den Namen des Typs und die Wandlerfunktion besiedelt. Im Wesentlichen müssen wir also die verkettete Liste durchgehen, um nach der Konverterfunktion zu suchen, die die gewünschte Konvertierung durchführt, und sie dann aufrufen. Sie könnten wahrscheinlich an dieser Stelle genau das mit Ctypes tun, aber ich entschied mich dafür, die Tatsache zu nutzen, dass der exakt gleiche Compiler und die SWIG-Versionen mir das Speichern dieser Strukturen in Ctypes-Notation ersparen würden und immer korrekt wären, also schrieb ich einfach ein anderes SWIG-Modul mit etwas mehr C++.

(Ich sollte hinzufügen, dass es interne Funktionen gibt, die diese Informationen in einem Modul verwenden, aber sie haben standardmäßig statische Verknüpfung, so dass sie hier schwer zu finden und zu verwenden sind).

Auf jeden Fall meine cast.i-Datei, die diese ordentlich zurück zu Python macht am Ende wie folgt aussehen:

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

Alles, was zu tun ist, um die Besetzung verknüpfte Liste für Informationen über die abgeleiteten Klasse suchen zu Fuß. Es gibt ein bisschen zusätzliches Python, um das sicher einzubetten und die Erstellung eines neuen Proxy-Objekts zu handhaben, aber das ist es im Wesentlichen.

In diesem Ort war es dann möglich, dass ich die folgende Python-Code ausführen:

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

persönlich nur das nehmen würde ich aber:

import test 

o=test.get_one() 
print(o) 

import cast 

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

Nach meinem Guss Modul kompiliert haben einen Zweig des ursprünglichen Moduls beibehalten, einen Patch stromaufwärts schieben oder einfach nur verwenden, um ein anderes SWIG-Modul zu schreiben, das das ursprüngliche Modul unverändert über so etwas erweitert.

+0

Mit '% import' schreibe ich ein anderes SWIG-Modul, das die ursprünglichen unmodifizierten Sounds genau nach dem was ich _really_ bin, erweitert. Danke für Ihre Antwort (die beantwortet, wonach ich gefragt habe!) Und es ist auch eine großartige Einführung in die SWIG-interne Implementierung – Zero

Verwandte Themen