I'm extending my library with Python (2.7) by wrapping interfaces with SWIG 2.0, and have a graph object in which I want to create a visitor. In C++, the interface looks like this:
struct Visitor{virtual void OnStateBegin() = 0;virtual void OnNode(Node* n) = 0;virtual void OnStateEnd() = 0;};
I would like to define a class in Python that does the equivalent, all defined in python, that will allow for the definition of a visitor:
class GraphVisitor:def __init__(self, label):self._label = labelprint("__init__(self,{0})".format(self._label))def OnStateBegin(self):print("OnStateBegin()" + self._label)def OnNode(self, i_node):print("OnNode()" + self._label)def OnStateEnd(self):print("OnStateEnd()" + self._label)
And what I'm trying to do is create an instance of a GraphVisitor in python script, and call the methods OnStateBegin(), OnNode(), and OnStateEnd() for a given instance from C++. Here's what I'd like to do in Python:
#model is a SWIG wrapped class
mvis = GraphVisitor("This is a test")
model.Visit("mvis") # I'm not sure how to pass the instance 'mvis' to C++?
And in my C++ wrapped by Swig, I'm not sure how to get at the instance 'mvis'? I can call functions defined in Python no problem, but instances has me stumped!
In order to solve this problem, I retrieved the class from the module given it's module name and class name (the code below assumes the module hasn't already been loaded):
void Model::Visit(const char* mod_name, const char* class_name)
{PyErr_Clear();PyObject* mod_name_obj = PyString_FromString(mod_name);PyObject* class_name_obj = PyString_FromString(class_name);PyObject* py_module = PyImport_Import(mod_name_obj);PyObject* err_1 = PyErr_Occurred();if(err_1)PyErr_Print();
Once I had the module, I looked up the class from it's dictionary:
if(py_module){PyObject* py_module_dict = PyModule_GetDict(py_module);PyObject* py_class = PyDict_GetItem(py_module_dict, class_name_obj);
I simplified my problem a bit by instantiating the python class in C++, then created my visitor, and finally visited it:
if(py_class && PyClass_Check(py_class) && PyCallable_Check(py_class)){PyObject* inst = PyInstance_New(py_class, 0, 0);if(inst && PyInstance_Check(inst)){IModel::IVisitorPtr py_visitor = new PyModelVisitor(inst);_model->Visit(py_visitor);}}}
}
The visitor had 3 functions OnStateBegin(), OnNode(), and OnStateEnd(). I added to my SWIG python binding generator an option to generate a header file for external access to the SWIG runtime with the -external-runtime option, so I could create a class in C++ (INode* below) and pass it to Python as the argument to the python OnNode() member function as follows (error checking removed for brevity):
VisitorCtrl OnNode(INode* node)
{Node* node_impl = new NodeImpl(node);PyObject* pynode = SWIG_NewPointerObj(node_impl, SWIG_TypeQuery("Node *"), 0);PyObject* result = PyObject_CallMethodObjArgs(_inst, PyString_FromString("OnNode"), pynode, 0);long rivis = PyInt_AsLong(result);return(static_cast<VisitorCtrl>(rivis));
}