Mihai Galos Mihai Galos - 3 months ago 18
C++ Question

Inheriting Base from C++ in Python, call abstract method using SWIG

I'm having some trouble coupling C++ (98) with python 3. I have some base classes in C++ which I'd like to extend in Python. Some methods in question are pure virtual on the C++ side and will thus be implemented on the Python side.

Currently, I can call the abstract methods from C++ and, over swig, the specialization gets called in Python. Cool. I'm having trouble handing over parameters to Python..

Minimal complete example to simplify my problem:

// iBase.h
#pragma once
#include <memory>

typedef enum EMyEnumeration{
EMyEnumeration_Zero,
EMyEnumeration_One,
EMyEnumeration_Two

}TEMyEnumeration;


class FooBase{
protected:
int a;
public:
virtual int getA() = 0 ;
};

class Foo : public FooBase{
public:
Foo() {a = 2;}
int getA(){return a;}
};

class iBase{
public:

virtual void start() =0;
virtual void run(std::shared_ptr<FooBase> p, TEMyEnumeration enumCode) = 0;
};


On the swig side:

// myif.i
%module(directors="1") DllWrapper

%{
#include <iostream>
#include "iBase.h"
%}

%include <std_shared_ptr.i>
%shared_ptr(FooBase)
%shared_ptr(Foo)
%feature("director") FooBase;
%feature("director") iBase;
%include "iBase.h"


Run swig as:

swig -c++ -python myif.i
swig -Wall -c++ -python -external-runtime runtime.h


Compile myif_wrap.cxx -> _DllWrapper.pyd

Create an *.exe with the following code, it will load up the _DllWrapper.pyd library (make sure it's in the same directory!). Also, copy DllWrapper.py generated by swig to the exe directory.

//Main_SmartPtr.cpp
#include "stdafx.h"
#include <Python.h>
#include <windows.h>
#include <string>
#include <memory>
#include "iBase.h"
#include "runtime.h"
using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
string moduleName = "ExampleSmartPtr";

// load *.pyd (actually a dll file which implements PyInit__<swigWrapperName>)
auto handle =LoadLibrary("_DllWrapper.pyd");

// getting an instance handle..
Py_Initialize();
PyObject *main = PyImport_AddModule("__main__");
PyObject *dict = PyModule_GetDict(main);

PyObject *module = PyImport_Import(PyString_FromString(moduleName.c_str()));
PyModule_AddObject(main, moduleName.c_str(), module);
PyObject *instance = PyRun_String(string(moduleName+string(".")+moduleName+string("()")).c_str(), Py_eval_input, dict, dict);


//calling start() in the Python derived class..
//PyObject *result = PyObject_CallMethod(instance, "start", (char *)"()");

// trying to call run in the Python derived class..

shared_ptr<Foo> foo = make_shared<Foo>();
EMyEnumeration enumCode = EMyEnumeration_Two;


string typeName1 = "std::shared_ptr <FooBase> *";
swig_type_info* info1 = SWIG_TypeQuery(typeName1.c_str());
auto swigData1 = SWIG_NewPointerObj((void*)(&foo), info1, SWIG_POINTER_OWN);

string typeName2 = "TEMyEnumeration *";
swig_type_info* info2 = SWIG_TypeQuery(typeName2.c_str());
auto swigData2 = SWIG_NewPointerObj((void*)(&enumCode), info2, SWIG_POINTER_OWN);

auto result = PyObject_CallMethod(instance, "run", (char *)"(O)(O)", swigData1, swigData2);

return 0;
}


Create a new Python file and put it in the exe's directory:

#ExampleSmartPtr.py
import DllWrapper

class ExampleSmartPtr(DllWrapper.iBase):
def __init__(self): # constructor
print("__init__!!")
DllWrapper.iBase.__init__(self)

def start(self):
print("start")
return 0

def run(self, data, enumCode):
print("run")
print("-> data: "+str(data))
print("-> enumCode: "+str(enumCode))

print (data.getA())
return 1


The output of running the exe is :

__init__!!
run
-> data: (<DllWrapper.FooBase; proxy of <Swig Object of type 'std::shared_ptr< FooBase > *' at 0x00000000014F8B70> >,)
-> enumCode: (<Swig Object of type 'TEMyEnumeration *' at 0x00000000014F89F0>,)


How can one 'dereference' the enumCode to a simple int? How does one call print (data.getA()) in python class run()? In its current form it doesn't print anything..

Answer

It seems somebody else tried the exact same thing !

What I did was compile the *.pyd with -DSWIG_TYPE_TABLE=iBase.

Then I added this to the main application on the cpp side:

iBase *python2interface(PyObject *obj) {
  void *argp1 = 0;
  swig_type_info * pTypeInfo = SWIG_TypeQuery("iBase *");

  const int res = SWIG_ConvertPtr(obj, &argp1,pTypeInfo, 0);
  if (!SWIG_IsOK(res)) {
    abort();
  }
  return reinterpret_cast<iBase*>(argp1);
}

and called the implementation form python like this:

auto foo = make_shared<Foo>();
TEMyEnumeration enumCode = EMyEnumeration_Two;
python2interface(instance)->run(foo, enumCode);

To finish off, I compiled the C++ implementation again with -DSWIG_TYPE_TABLE=iBase.

Works like a charm!