1

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:

  1. Am I in the right direction here? In other words, is this really possible to do with SWIG?
  2. Can I achieve my goal with the director feature only?
tadman
  • 208,517
  • 23
  • 234
  • 262
José Joaquim
  • 103
  • 2
  • 9

1 Answers1

1

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));
    }
}
Mark Tolonen
  • 166,664
  • 26
  • 169
  • 251
  • Thanks for your helpful comment. Though I'm strongly accepting your suggestion, I'm trying to get rid of declaring a new class for each callback. For instance, in Python, we should always have a `class Something(test.Callback)`. In C#, something like `public class Something : Callback`. Not to mention if I decide to consider the rule of one class definition per file... Therefore, is there any solution where the C++ layer requires a method instead of a class? – José Joaquim May 02 '23 at 18:05
  • 1
    There is, but it is more complicated without directors. Your C callback needs a `void* context`-like parameter because some context has to be tracked converting a Python callback into a C one. – Mark Tolonen May 02 '23 at 19:51
  • 1
    See [this answer](https://stackoverflow.com/a/11522655/235698) for a C++ example. C needs the context parameter to hold the state that the C++ object maintains, so you don't need the context parameter using C++. I was thinking of a C project I did where context was needed. – Mark Tolonen May 02 '23 at 19:57
  • The link to the other answer seems to be exactly what I was looking for. It seems to me that, while the director feature is a language-agnostic approach, the "`context`" approach is tailored to the target language. Thank you :) – José Joaquim May 02 '23 at 20:35
  • 1
    @JoséJoaquim Added an updated `test.i` that works with your example.py based on the answer I linked. – Mark Tolonen May 02 '23 at 20:53