Sign in to follow this  

SWIG Object ownership problem

This topic is 4483 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
Something like this would do it.





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 file

PyObject ...._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

Share this post


Link to post
Share on other sites
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...

Share this post


Link to post
Share on other sites
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



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


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


class G(demo.A):
def __init__(self, value):
demo.A.__init__(self)
self.value = value


obj = G(14)
print obj
print obj.value

b = demo.B()

b.insertA(obj)
obj = None #release the object from python

obj2 = b.releaseA()

print obj2
print obj2.value



Share this post


Link to post
Share on other sites
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.




PyObject* getAButKeepOwnership()
{
if ( pyObjectPtr == 0)
{
throw std::runtime_error( "Was never inserted in from python" )
}

Py_INCREF(pyObjectPtr); //keep ownership.
return pyObjectPtr;

}



Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
Actually, I just though of something much better. It moves the wrapping code in the interface file instead of C++.


new demo.i

%{
#include "classes.h"
%}

%module demo


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

}







Share this post


Link to post
Share on other sites
Ok, now it seems much better.

Thanks again, I'll work implementing that solution and see how that works.

You've been great help, I get back to this post if something comes up ;)

Share this post


Link to post
Share on other sites

This topic is 4483 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this