0

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

Mihai Galos
  • 1,707
  • 1
  • 19
  • 38
  • I suspect that your problem is in the use of shared_ptr. Does it work when you use a normal pointer instead as type of data? – Klamer Schutte Aug 28 '16 at 09:21
  • 1
    please make a *minimal* and complete example we can try out for ourselves that illustrates the problem. Without the header files (which are presumably large) I can't debug this myself. – Flexo Aug 28 '16 at 09:30
  • Where do you define EMediaSampleMeta ? I would expect a %include "emediasamplemeta.h" line but did you forget that? – Klamer Schutte Aug 28 '16 at 09:34
  • @MihaiGALOS Tried your example on Linux. I get a segmentation fault when I try to query for types, `SWIG_TypeQuery`. Question. Just curious, how will the interpreter know that you have loaded the library? – Jens Munk Aug 28 '16 at 15:48
  • I found my problem. Current directory is not contained in the python path. – Jens Munk Aug 28 '16 at 18:13
  • @Jens: I'm using VS2010 for debugging. In the Loaded Modules, I can inspect the libraries that are loaded. Granted, the full code does a null check on the handle to the library, the minimal complete example skips it.. – Mihai Galos Aug 28 '16 at 20:14
  • So as far as I can understand, in the run() method on the Python side, I need an actual wrapped object, and not a Swig object of type 'shared_ptr*'.. – Mihai Galos Aug 29 '16 at 15:55
  • Hmm. You create a `shared_ptr` of `Foo` but `Foo` does not implement `iBase` Is this really what you want? – Jens Munk Aug 29 '16 at 22:34
  • Hello Jens. Indeed there was a problem in the original code. I updated the example, I hope it's clearer now. In Python, I need to get the value of the second parameter enumCode, so I expect print(enumCode) to print a EMyEnumeration_Two which mapps to a 2. Also, I want to call print(data.getA()) from the instantiated python class. This should also print a 2. It currently doesn't print anything. – Mihai Galos Aug 30 '16 at 05:54
  • @MihaiGalos you need a virtual destructor to establish the vtable. – Jens Munk Aug 30 '16 at 14:26

2 Answers2

1

This is not really an answer, but I read the discussion Discussion from 2005 and it makes sense, that it shouldn't be possible. If you on the Python side, do the following, you get your enumeration 'dereferenced' to a simple integer.

import ExampleSmartPtr

instance = ExampleSmartPtr.ExampleSmartPtr()
swigData1 = ExampleSmartPtr.DllWrapper.Foo()
swigData2 = ExampleSmartPtr.DllWrapper.EMyEnumeration_Two
instance.run(swigData1,swigData2)

This will print

__init__!!
run
-> data: <DllWrapper.Foo; proxy of <Swig Object of type 'std::shared_ptr< Foo > *' at 0x7f8825c0b7e0> >
-> enumCode: 2

I think that the issue is that two different vtables are into play. The original C++ vtable and that of the Swig Object. Just curious, in what scenario is it of interest to use a Python descendant of a C++ class from within C++?

Jens Munk
  • 4,627
  • 1
  • 25
  • 40
  • I would like to extend a base class in python. This base class has methods which take pointers to other abstract types, specialized back in c++. So in python, I would like to call these abstract methods back over to c++. It's kind of a Python plugin architecture for a c++ application, if you will. I'm still studying its feasability. – Mihai Galos Aug 30 '16 at 14:56
  • Would it be possible to make use the strategy explained here, http://stackoverflow.com/questions/34445045/passing-python-functions-to-swig-wrapped-c-code Then you cannot use inheritance, but you have to work with function pointers. Your plugin written in Python implements a specific set of functions with known prototypes and they are called from C++. I have made use of this to enable the possibility experiment with a variety of functions (as plugins) to a large program written in C++ – Jens Munk Aug 30 '16 at 15:06
  • Okay, you can actually do oop without inheritance, but that is another story :-) I hope that you find a way to achieve what you try to accomplish. – Jens Munk Aug 30 '16 at 15:17
0

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!

Community
  • 1
  • 1
Mihai Galos
  • 1,707
  • 1
  • 19
  • 38