Recently I started to use SWIG to develop a solution that passes (possibly) several callback functions that should be called in the C++ layer. Besides, I spent some time reading the introduction callback chapter and the director feature.
I've managed to achieve my goal by coding different classes that inherit from the base class in the C++ layer from the documentation example, but I'm trying to learn in how to pass std::function
instances instead of pointer to classes.
I believe that the (incorrect) example below summarizes my objective:
File example.h:
#include <functional>
#include <map>
#include <vector>
class Controller {
private:
std::vector<std::function<void()>> functions;
public:
virtual void AddFunction(const std::function<void(void)>& func) {
functions.push_back(func);
}
virtual void Execute() {
for (int m = 0; m < functions.size(); ++m) functions[m]();
}
Controller() {}
virtual ~Controller() {}
};
File example.i:
%module example
%{
#include "example.h"
%}
%include "example.h"
File example.py:
import example
def foo():
print("yey")
def bar():
print("dog")
def cat():
print("miaw")
e = example.Controller()
e.AddFunction(foo)
e.AddFunction(bar)
e.AddFunction(cat)
# Prints yey, dog and miaw sequentially
e.Execute()
So, my question is:
To use directors a class with a virtual function to override is needed.
Given your Controller
definition as test.h
:
#include <functional>
#include <map>
#include <vector>
class Controller {
private:
std::vector<std::function<void()>> functions;
public:
virtual void AddFunction(const std::function<void(void)>& func) {
functions.push_back(func);
}
virtual void Execute() {
for (int m = 0; m < functions.size(); ++m) functions[m]();
}
Controller() {}
virtual ~Controller() {}
};
Then the SWIG interface file (test.i
) could declare a director-enabled Callback
class and extend Controller
to take that class as a parameter and call the overridden virtual function:
%module(directors="1") test
%feature("director") Callback;
%inline %{
class Callback {
public:
virtual void function() = 0;
virtual ~Callback() {}
};
%}
%{
#include "test.h"
%}
%include "test.h"
%extend Controller {
void AddFunction(Callback* cb) {
$self->AddFunction(std::bind(&Callback::function, cb));
}
}
The Python code would look like:
import test
class Foo(test.Callback):
def function(self):
print('yey')
class Bar(test.Callback):
def function(self):
print('dog')
class Cat(test.Callback):
def function(self):
print('miaw')
# Note that AddFunction needs to be provided an object instance that persists
# until after Execute is called, so "e.AddFunction(Foo())" wouldn't work
# because Foo() would be freed after the AddFunction call.
foo = Foo()
bar = Bar()
cat = Cat()
e = test.Controller()
e.AddFunction(foo)
e.AddFunction(bar)
e.AddFunction(cat)
e.Execute()
Output:
yey
dog
miaw
This is the .i
file to use to directly add functions and not use directors. This works with the example.py
in the question and is based on this answer:
%module example
%inline %{
class PyCallback
{
PyObject *func;
PyCallback& operator=(const PyCallback&); // Not allowed
public:
PyCallback(const PyCallback& o) : func(o.func) {
Py_XINCREF(func);
}
PyCallback(PyObject *func) : func(func) {
Py_XINCREF(this->func);
assert(PyCallable_Check(this->func));
}
~PyCallback() {
Py_XDECREF(func);
}
void operator()(void) {
if (!func || Py_None == func || !PyCallable_Check(func))
return;
PyObject *result = PyObject_CallNoArgs(func);
Py_XDECREF(result);
}
};
%}
%{
#include "test.h"
%}
%include "test.h"
%extend Controller {
void AddFunction(PyObject* cb) {
$self->AddFunction(PyCallback(cb));
}
}