Search code examples
pythonc++swigcythoncpython

Extracting SWIG wrapped C++ instance/pointer for use in Cython


I have an instance of a class from SWIG-wrapped C++ library from which I would like to extract its reference, in order to be able to use it inside a Cython file, in which I am directly linking to the same C++ library by using a more lightweight self-made Cython wrapper of the same class.

I know it would not be as easy as accessing some hidden attribute, but I imagine there might be some function within SWIG or CPython that could potentially do that if linked to from within Cython (some PyObject_*, perhaps?).

Unfortunately, I don't know enough about SWIG or CPython internals to know how to do this, or whether this is really possible without modifying the source code of the SWIG binding.


Solution

  • After doing further research, I have figured out how to do what I wanted.

    According to the documentation, SWIG-wrapped classes in Python consist of three layers: a) a pure Python instance, b) a custom SwigPyObject built-in type contained in its .this attribute and c) a normally inaccessible pointer void *ptr to the actual C++ instance contained therein, which is what Cython would require.

    In order to gain access to that pointer, I had to create a Cython wrapper of the SwigPyObject struct in order to access the internal void *ptr of the SWIG instance. The declaration of this struct is normally included directly into the SWIG-generated C++ source, and not as a separate header, so I created one to contain it:

    #include <Python.h>
    
    typedef struct {
      PyObject_HEAD
      void *ptr; // This is the pointer to the actual C++ instance
      void *ty;  // swig_type_info originally, but shouldn't matter
      int own;
      PyObject *next;
    } SwigPyObject;
    

    This include file is then referenced in the Cython .pxd file, to enable access to the internal ptr variable:

    cdef extern from "swigpyobject.h":
        ctypedef struct SwigPyObject:
            void *ptr
    

    The required pointer can now be referenced from inside the .pyx Cython source code:

     cdef SwigPyObject *swig_obj = <SwigPyObject*>pythonswig.this
     cdef MyCppClass *mycpp_ptr = <MyCppClass*?>swig_obj.ptr
     // And if you want a proper instance instead of a pointer:
     cdef MyCppClass my_instance = deref(mycpp_ptr)
    

    Caveat: Since SWIG-wrapped classes exist in both Python and C++ space, SWIG implements mechanisms to deal with the memory allocation, including information about the "ownership" of an instance for garbage collection. This code doesn't try to deal with any of this, so there might be a danger of running into allocation issues, but as long as a reference to the original SWIG instance is kept in Python, I believe it should be safe to manipulate it under Cython.