Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to apply SWIG OUTPUT typemaps for class types in Python?

Tags:

c++

python

swig

I am having some trouble generating a Python wrapper around a C++ library using SWIG (version 3.0.6).

My issue relates to applying the OUTPUT typemap, specifically in the case of pointers/references to class types.

To illustrate, this is what I want for standard types, and it works:

// .h
int add(const long arg1,const long arg2,long& resultLong);

// interface.i
%apply long& OUTPUT { long& resultLong };
int add(const long arg1,const long arg2,long& resultLong);

// projectWrapper.py
def add(arg1, arg2):
    return _projectWrapper.add(arg1, arg2)
addTerm = _projectWrapper.add

// usage
>>> result = projectWrapper.add(2, 4)
>>> print result
[0, 6L]

You don't have to pass in "resultLong", but it is appended to the result automatically. Great!

However, this doesn't seem to be working as I expect when the output type is some pointer to a class type:

// .h
int GetClassType(const char* name, exportedClassType*& resultPointer);

class exportedClassType
{...}

// interface.i
%apply exportedClassType*& OUTPUT { exportedClassType*& resultPointer };    
int GetClassType(const char* name, exportedClassType*& resultPointer);

// projectWrapper.py
def GetClassType(name, resultPointer):
    return _projectWrapper.GetClassType(name, resultPointer)
GetClassType = _projectWrapper.GetClassType

The problem seems to be that SWIG has not processed it in the same way as the simple type. It still appears as an "input" parameter in the wrapped function signature.

// attempted usage
>>> classType = projectWrapper.GetClassType("name")
TypeError: GetClassType() takes exactly 2 arguments (1 given)

>>> result = 0
>>> projectWrapper.GetClassType("name", result)
Traceback (most recent call last):
File "<input>", line 1, in <module>
TypeError: in method 'GetClassType', argument 2 of type 'exportedClassType *&'

Can somebody please tell me what I'm doing wrong or point me in the right direction? Any help gratefully received! Thanks

like image 659
SWilliams Avatar asked Sep 09 '15 11:09

SWilliams


People also ask

How do I use SWIG in Python?

The SWIG %module directive specifies the name of the Python module. If you specify `%module example', then everything is wrapped into a Python 'example' module. Underneath the covers, this module consists of a Python source file example.py and a low-level extension module _example.so.

What is SWIG object Python?

The Simplified Wrapper and Interface Generator (SWIG) is an open-source software tool used to connect computer programs or libraries written in C or C++ with scripting languages such as Lua, Perl, PHP, Python, R, Ruby, Tcl, and other languages like C#, Java, JavaScript, Go, D, OCaml, Octave, Scilab and Scheme.

Does Numpy use SWIG?

We use the SWIG %apply directive to apply the typemap for one-dimensional input arrays of type double to the actual prototype used by rms . Using numpy. i effectively, therefore, requires knowing what typemaps are available and what they do.

What is SWIG EXE?

SWIG is a software development tool that connects programs written in C and C++ with a variety of high-level programming languages. SWIG is used with different types of target languages including common scripting languages such as Javascript, Perl, PHP, Python, Tcl and Ruby.


2 Answers

This question has appeared as unresolved for quite some time, so I thought that I better provide a solution to the question. The OUTPUT typemap only applies to simple types, so a solution is given by combining an in and an argout typemap.

Consider the situation, where we have a C++ class SampleImpl implementing a C++ interface SampleBase, which is technically not an interface, since it involves the implementation of a virtual destructor. Suppose we have a static function, which returns an error code and an implementation of the interface. The latter as a reference to a pointer, which is the situation above.

Interface header:

// Sample.hpp
#pragma once
namespace Module {
  class SampleBase {
  public:
#ifndef SWIG
    // Hint to the programmer to implement this function
    static int SampleCreate(SampleBase *&obj);
#endif
    virtual ~SampleBase() = default;
  };
}

Implementation header:

// Sample_impl.hpp
#pragma once
#include "Sample.hpp"

namespace Module {
  class SampleImpl : public SampleBase {
  public:
    static int SampleCreate(Module::SampleBase *&obj);

    SampleImpl();
    virtual ~SampleImpl();
  private:
    float a;
  };
}

Implementation:

// Sample_impl.cpp
#include "Sample_impl.hpp"
#include <cstdio>

namespace Module {
  int SampleImpl::SampleCreate(Module::SampleBase*& obj) {
    obj = (SampleBase*) new SampleImpl();
    return 0;
  }
  SampleImpl::SampleImpl() {
    printf("SampleImpl::SampleImpl()\n");
  }

  SampleImpl::~SampleImpl() {
    printf("SampleImpl::~SampleImpl()\n");
  }
}

SWIG interface (using argout typemap)

// example.i
%module example
%{
  #define SWIG_FILE_WITH_INIT
  #include "Sample.hpp"
  #include "Sample_impl.hpp"
%}

%include "typemaps.i"

%typemap(in, numinputs=0) Module::SampleBase *&obj (Module::SampleBase *temp) {
  $1 = &temp;
}

%typemap(argout) Module::SampleBase *& {
  PyObject* temp = NULL;
  if (!PyList_Check($result)) {
    temp = $result;
    $result = PyList_New(1);
    PyList_SetItem($result, 0, temp);

    // Create shadow object (do not use SWIG_POINTER_NEW)
    temp = SWIG_NewPointerObj(SWIG_as_voidptr(*$1),
             $descriptor(Module::SampleBase*),
             SWIG_POINTER_OWN | 0);

    PyList_Append($result, temp);
    Py_DECREF(temp);
  }
}

Usage in Python

import example

// Creating specialization
obj = example.SampleImpl()
del obj

// Creation of object using output typemap
errorCode, obj = example.SampleImpl_SampleCreate()
del obj
like image 107
Jens Munk Avatar answered Sep 18 '22 07:09

Jens Munk


It is not an answer, just not enough of reputaiton for a comment :(

Cause you need to use pointer in C++ and Python doesn't have pointers (so you could not do anything with your current 'result' in Python anyway).

Could you add wrappers to hide pointers into .h as was suggested by @Jens Munk:

class exportedClassType_ptr {
public:
    exportedClassType* ptr;
    exportedClassType_ptr( exportedClassType& input ) {
        this->ptr = &input;
    }
};

int GetClassType( const char* name, exportedClassType_ptr& resultPointer ) {
    return GetClassType( name, resultPointer.ptr );
}

modify .i file to call new method:

%apply exportedClassType_ptr& OUTPUT { exportedClassType_ptr& resultPointer };    
int GetClassType( const char* name, exportedClassType_ptr& resultPointer );

in Python write something like this:

>>> realResult = projectWrapper.exportedClassType()
>>> result = projectWrapper.exportedClassType_ptr(realResult)
>>> projectWrapper.GetClassType("name", result)

and use 'realResult' for future work.

like image 20
embrio Avatar answered Sep 22 '22 07:09

embrio