Search code examples
pythonc++swigfractions

How can I write a function to accept a Fraction object in SWIG?


I'm writing an interface between Python and a our C++ video-processing library using SWIG. In python, I use the Fraction class to represent frame rate (e.g. NTFS24 = 24000/1001 FPS). The functionality in question is video transcoding, i.e. taking a video (or a stream of frames) input and producing a similar output. For that we need to specify the output (and sometimes input) frame rate.

Is there any way I can interface the Fraction class on the C++ (SWIG) side? From what I found around the Internet I should be able to pass a tuple to a std::pair<int,int> parameter, so that's my fallback plan, but is there a better way? Thanks!


Solution

  • I put together the following interface file to illustrate how wrapping a Fraction might work. In the end I decided to create my own fractions struct to hold the fractions on the C++ side, primarily because it's less ambiguous than using std::pair<int, int>. (I figured a pair of integers could also be a 2D coordinate, or a screen resolution or many other types, and stronger typing is a better thing for overload resolution etc.)

    %module test
    
    %{
    #include <iostream> // Just for testing....
    
    static PyObject *fractions_module = NULL;
    %}
    
    %init %{
      // Import the module we want
      fractions_module = PyImport_ImportModule("fractions");
      assert(fractions_module);
      // TODO: we should call Py_DECREF(fractions_module) when our module gets unloaded
    %}
    
    %typemap(in) const Fraction& (Fraction tmp) {
      // Input typemap for fraction: duck-type on attrs numerator, denominator
      PyObject *numerator = PyObject_GetAttrString($input, "numerator");
      PyObject *denominator = PyObject_GetAttrString($input, "denominator");
    
      int err = SWIG_AsVal_int(numerator, &tmp.numerator);
      assert(SWIG_IsOK(err)); // TODO: proper error handling
      err = SWIG_AsVal_int(denominator, &tmp.denominator);
      assert(SWIG_IsOK(err)); // TODO: errors...
    
      Py_DECREF(numerator);
      Py_DECREF(denominator);
    
      $1 = &tmp;  
    }
    
    %typemap(out) Fraction {
      // Output typemap: pass two ints into fractions.Fraction() ctor
      $result = PyObject_CallMethod(fractions_module, "Fraction", "ii", $1.numerator, $1.denominator);
    }
    
    %inline %{
      struct Fraction {
        int numerator, denominator;
      };
    
      void fraction_in(const Fraction& fraction) {
        std::cout << fraction.numerator << "/" << fraction.denominator << "\n";
      }
    
      Fraction fraction_out() {
        Fraction f = {100, 1};
        return f;
      }
    %}
    

    Mostly this is just two typemaps - one for inputs to C++ functions and one for outputs. They construct a temporary C++ Fraction from the numerator and denominator attributes of the input object and construct a fractions.Fraction Python object from our C++ one respectively. Adapting them for other similar fractional types should be fairly straightforward.