SWIG Object ownership problem
Hi!
I have a little problem with SWIG when it comes to GC and object ownership.
I decided to post this question here first rather than in SWIG mailing list, mainly because the help here is more constructive ;)
Ok so the problem at hand:
I have a director class A which I will inherit in my scripting language side(python), and a singleton container class B in c++ side which takes the instances of inherited A's in. Ok, this is not a problem, but when it comes to point where As instances go out-of-scope they'll get deleted.
I have in python this :
insertAintoB(derivedFromA())
goThroughB()
At goThroughB() program crashes, but IF I put this :
insertAintoB(derivedFromA().__disown__())
goThroughB()
this will work. So my question is how can I say in the swig interface file that swig discards the reference counting for instances of A? This way I don't have to force the fellow developers to use .__disown__() on every instances of A.
And I know there's a possibility to change thisown flag to zero on wrapper file but this is a bad way to do this (every build changes it back and so on...).
I have tested features noref, nounref, norefbase, nounrefbase (may contain typos, I dont remember correctly) and I have tested the swig's DISOWN typemap, but none of these methods seem to work.
Is there any way to do this?
I can post my code if necessary...
(and regular "sorry-for-my-bad-english-and-confusing-text" -disclamer ;)
edit : some typos
The DISOWN typemap is only useful with a pointer returned from a function.
So it won't do any good. The only thing you can do really is call disown in python or recreate disown in C++.
To do this you add a flag in your class in C++ when you don't want the class to be deleted.
The function to set the flag would not be visible to python, but can be called by C++ objects to take ownership.
Then modified the the _wrap_delete function created by swig and wrap the delete args1 with a check of the flag. That way the python object will be destroyed, but not the internal pointer in the python object. You can then do what you please with the pointer.
I prefer calling __disown__. It makes everything clear about the object and C++ never has to know about anything related to python.
So it won't do any good. The only thing you can do really is call disown in python or recreate disown in C++.
To do this you add a flag in your class in C++ when you don't want the class to be deleted.
The function to set the flag would not be visible to python, but can be called by C++ objects to take ownership.
Then modified the the _wrap_delete function created by swig and wrap the delete args1 with a check of the flag. That way the python object will be destroyed, but not the internal pointer in the python object. You can then do what you please with the pointer.
I prefer calling __disown__. It makes everything clear about the object and C++ never has to know about anything related to python.
Quote:Original post by Gorg
To do this you add a flag in your class in C++ when you don't want the class to be deleted.
Can you be more specific how to do this. I presume making int thisown = 0 on c++ side won't do the trick?
Quote:
I prefer calling __disown__. It makes everything clear about the object and C++ never has to know about anything related to python.
Yep, it's style issue. I don't like to remember to use or to say people to use disown whenever they use specific classes.
Something like this would do it.
If you don't want to modify the file generated by swig(because maybe you change it a lot), instead of exposing A to python, you can expose a smart pointer to A instead.
This requires a bit more explanations than I can type at this point, so I will point you to the Swig documentation for details and explanation.
http://www.swig.org/Doc1.3/SWIGDocumentation.html
class A{public: void DisownFromPython() { ownedByCpp = true; } bool OwnedByCpp() { return ownedByCpp; }private: bool ownedByCpp = false; //not valid code, but I don't want to write everything};class B{public: void InsertA( A* a ) { a.DisownFromPython; } }//In the generated Swig filePyObject ...._delete_A(...){ //some swig code if ( !arg1->OwnedByCpp() ) { delete arg1; } // more swig code}
If you don't want to modify the file generated by swig(because maybe you change it a lot), instead of exposing A to python, you can expose a smart pointer to A instead.
This requires a bit more explanations than I can type at this point, so I will point you to the Swig documentation for details and explanation.
http://www.swig.org/Doc1.3/SWIGDocumentation.html
Ok, now I got to somewhere BUT... ;)
Maybe my explanation was a bit confusing, now the python side gets deleted still, and the C++ side doesn't so I'm half way there :)
If I inherit A in python and I want to make sure it doesn't delete as well, how do I do that? I'm guessing smartpointers don't work in that.
disowning still works, but how do I do that in the c++ side, meaning that python doesn't delete even the python inherited side of A...
Maybe my explanation was a bit confusing, now the python side gets deleted still, and the C++ side doesn't so I'm half way there :)
If I inherit A in python and I want to make sure it doesn't delete as well, how do I do that? I'm guessing smartpointers don't work in that.
disowning still works, but how do I do that in the c++ side, meaning that python doesn't delete even the python inherited side of A...
Oh! I see what you mean. Then you don't have much choices, you need to either always keep a reference of the object somewhere in python or pass the PyObject to C++ and increase its ref count when you get it and decrease its ref count when you don't need it anymore.
If you had written your own wrappers function and manually setup the module, you could use the Python C api. But because you use swig, you will have to use the SWIG_ConvertPtr which is defined in the generated wrapper file.
Here's the code for a demo. If you recreate a library from it and regenate the .cxx file, it should compile as is.
classes.h
classes.cpp
demo_glue.cpp (includes the generate cxx file for compilation)
demo.i //swig interface file
test.py //test that show that object stays alive after being release in Python
If you had written your own wrappers function and manually setup the module, you could use the Python C api. But because you use swig, you will have to use the SWIG_ConvertPtr which is defined in the generated wrapper file.
Here's the code for a demo. If you recreate a library from it and regenate the .cxx file, it should compile as is.
classes.h
#ifndef CLASSES_H#define CLASSES_H#include <Python.h>namespace Demo{ class A { public: A(){}; }; // // This function is used to return a reference to A from a pyobject // using the swig functions // A* GetAPointerFromSwigInterface(PyObject* pyObject); // // This the class that receives A. // It was written so it could be used in both // C++ and Python at the same time. // If it is not required you can cut some of the functions // class B { public: B(); ~B(); // // Insert an A from C++ // void InsertA( A* ptr ); // // C++ version to release the ownership of A // A* ReleaseA(); // // This function will be exposed through swig // void InsertAFromPython( PyObject* pyObject ); // // Python version of release A // PyObject* ReleaseAFromPython(); private: // // Detach all the pointers from the class // void ClearPointers(); A* aPtr; PyObject* pyObjectPtr; //when called from python };}#endif //CLASSES_H
classes.cpp
#include <stdexcept>#include "classes.h"using namespace Demo;B::B():aPtr(0), pyObjectPtr(0){ }B::~B(){ ClearPointers();}//// Insert an A from C++//void B::InsertA( A* ptr ){ ClearPointers(); aPtr = ptr;} //// C++ version to release A//A* B::ReleaseA(){ if ( pyObjectPtr != 0 ) { throw std::runtime_error( "Cannot release A to C++ when it comes from python" ); } A* ret = aPtr; aPtr = 0; return ret;} //// This function will be exposed through swig//void B::InsertAFromPython( PyObject* pyObject ){ ClearPointers(); Py_INCREF(pyObject); pyObjectPtr = pyObject; aPtr = GetAPointerFromSwigInterface(pyObject);}//// delete all the pointers from the class//void B::ClearPointers(){ if ( pyObjectPtr != 0 ) { Py_DECREF(pyObjectPtr); pyObjectPtr = 0; } else { if ( aPtr != 0 ) { delete aPtr; } }}//// Python version of release A//PyObject* B::ReleaseAFromPython(){ if ( pyObjectPtr == 0 ) { throw std::runtime_error( "Cannot release A because it was never inserted from python" ); } aPtr = 0; PyObject* ret = pyObjectPtr; pyObjectPtr = 0; //We do not de DECREF the object, because we give away the owership return ret;}
demo_glue.cpp (includes the generate cxx file for compilation)
#include <stdexcept>#include "classes.h"//Insert the demo wrap in a cpp file so we can add more functions after#include "demo_wrap.cxx"//// This function is used to return a reference to A from a pyobject// You will need to first generate the wrapping code to get the SWIGTYPE_p_Demo__A string, which the type descriptor// generated by swig// for your class//Demo::A* Demo::GetAPointerFromSwigInterface(PyObject* pyObject){ Demo::A* aPtr = 0; if ( SWIG_Python_ConvertPtr(pyObject, (void **)&aPtr, SWIGTYPE_p_Demo__A, SWIG_POINTER_EXCEPTION | 0) != 0 ) { throw std::runtime_error( "Error converting to SWIGTYPE_p_Demo__A" ); } return aPtr;}
demo.i //swig interface file
%{ #include "classes.h"%}%module demonamespace Demo{ class A { public: A(); }; class B { public: B(); ~B(); //rename the functions to have a cleaner interface %rename(insertA) InsertAFromPython( PyObject* pyObject ); void InsertAFromPython( PyObject* pyObject ); %rename(releaseA) ReleaseAFromPython(); PyObject* ReleaseAFromPython(); };}
test.py //test that show that object stays alive after being release in Python
import democlass G(demo.A): def __init__(self, value): demo.A.__init__(self) self.value = value obj = G(14)print objprint obj.valueb = demo.B()b.insertA(obj)obj = None #release the object from pythonobj2 = b.releaseA()print obj2print obj2.value
You might already know that but since I forgot to mention it.
If you want to implement a getFunction that returns the pointer in python, but B still keeps the ownership, you need to increment the REFCount.
When python gets a value from a C function, it expects to keep the ownership. So if you want B to keep the ownsership as well, you need to increment the refcount.
If you want to implement a getFunction that returns the pointer in python, but B still keeps the ownership, you need to increment the REFCount.
When python gets a value from a C function, it expects to keep the ownership. So if you want B to keep the ownsership as well, you need to increment the refcount.
PyObject* getAButKeepOwnership(){ if ( pyObjectPtr == 0) { throw std::runtime_error( "Was never inserted in from python" ) } Py_INCREF(pyObjectPtr); //keep ownership. return pyObjectPtr;}
Now it works!
Thank you for your answers :)
Only now seeing that this cannot be implemented through interface, it makes swig less important for our project because now if you want to change the language to which to generate wrappers, you have to change source code.
We are now thinking of moving either boost, do the librarys manually or use disown. Though this is a shame, because swig is so pain-free to use.
Thank you for your answers :)
Only now seeing that this cannot be implemented through interface, it makes swig less important for our project because now if you want to change the language to which to generate wrappers, you have to change source code.
We are now thinking of moving either boost, do the librarys manually or use disown. Though this is a shame, because swig is so pain-free to use.
Actually, I just though of something much better. It moves the wrapping code in the interface file instead of C++.
new demo.i
new demo.i
%{ #include "classes.h"%}%module demonamespace Demo{ class A { public: A(); }; class B { public: B(); ~B(); //rename the C++ insert and release function //to be used only internaly. %rename(_insertAInternal) InsertA( A* aPtr ); void InsertA( A* aPtr ); %rename(_releaseAInternal) ReleaseA(); A* ReleaseA(); //python wrapping functions for insert and release //the object is cached at this level instead //of in the C++ code %pythoncode %{ def insertA(self, a): self.cacheA = a #cache the python object that wraps a until we release it self._insertAInternal(a) def releaseA(self): dummy = self._releaseAInternal() ret = self.cacheA self.cacheA = None return ret %} };}
This topic is closed to new replies.
Advertisement
Popular Topics
Advertisement