Mihai Galos Mihai Galos - 1 year ago 80
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 Source

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!