Python SWIG wrapper for C++ rvalue std::string && - python

I'm trying to build a python wrapper for gnucash c++ parts. In QofBackend I encountered the method const std::string && get_message (). In python this message returns <Swig Object of type 'std::string *' at 0x7f4a20f5c9f0> instead of a string as in my setting as there is no swig typemap for std::string rvalue reference.
I didn't really find a simple explanation so I rebuilt an example setting and dug into c++ which I barely know. I managed to get the string into python but I'd like to know
if this typemap(out) approach is correct (also in respect of memory and error handling).
The conversion in set_s_workaround() is also just a workaround. I don't think that for gnucash the python code ever needs to set this value but for completeness sake it would be nice to also have a typemap(in) std::string&& and
get rid of get_s_workaround
get init_s_2 working.
/* example.hpp */
#include <string>
using namespace std;
struct struct1{
string s;
const std::string&& get_s();
void set_s(string&&);
void set_s_workaround(string);
void init_s();
void init_s_2();
void print_s();
};
string conv_rvalue_string(string);
/* example.cpp */
#include<iostream>
#include"example.hpp"
using namespace std;
void
struct1::set_s (std::string&& msg)
{
s = msg;
}
std::string
conv_rvalue_string (std::string msg)
{
return msg;
}
void
struct1::set_s_workaround(std::string msg)
{
set_s ( conv_rvalue_string(msg) );
}
void
struct1::init_s ()
{
set_s("Some content");
}
void
struct1::init_s_2()
{
std::string msg {"Couldn't find "};
/* set_s( msg ); */
}
void
struct1::print_s ()
{
cout<<get_s()<<endl;
}
const std::string&&
struct1::get_s ()
{
return std::move(s);
}
/* example.i */
%module example
%include "std_string.i"
%typemap(out) std::string&& {
std::string s = *$1;
$result = SWIG_From_std_string(s);
}
%{
#include <string>
#include "example.hpp"
%}
%include "example.hpp"
#!/bin/bash
swig3.0 -c++ -shadow -python example.i
g++ -fpic -c example.hpp example.cpp example_wrap.cxx -I/usr/include/python3.7
g++ -shared example_wrap.o example.o -o _example.so
# pyexample.py
import example
s1 = example.struct1()
s1.set_s_workaround('TEST')
s1.print_s()
print(s1.get_s())
Thanks for the help!

This std::string && typemap uses a temporary std::string that is passed to the method that takes a std::string rvalue reference.
The fragment dependecies in the %typemap ensure that the used fragments are ready in the wrapper file. We simply use the fragments that are already provided by "std_string.i".
/* example.i */
%module example
%include "std_string.i"
%typemap(out, fragment="SWIG_From_std_string") std::string&& {
$result = SWIG_From_std_string(*$1);
}
%typemap(in, fragment="SWIG_AsVal_std_string") std::string&& (std::string temp) {
int res = SWIG_AsVal_std_string($input, &temp);
$1 = &temp;
}
%{
#include <string>
#include "example.hpp"
%}
%include "example.hpp"

init_s2() would work like this:
void
struct1::init_s_2()
{
std::string msg {"Couldn't find "};
set_s( msg + "");
}
credits to S. Holtermann!
%typemap(out) std::string&& {
std::string s = *$1;
$result = SWIG_From_std_string(s);
}
can be shortened to
%typemap(out) std::string&& {
$result = SWIG_From_std_string(*$1);
}
without creating a local var (credits to S. Holtermann!)
A possible typemap(in) could be
%typemap(in) std::string && (std::string *temp){
int res = SWIG_AsPtr_std_string($input, &temp);
$1 = temp;
}
works
hasn't been tested extensively
does it cause memory leaks?

Related

vector of enum not correctly handled by SWIG

Dears,
I use SWIG to generate Python bindings to a C++ API (and it works great!) but I have serious difficulty wrapping a function that takes a vector of enum as argument. I have built a minimal example to simplify the debugging, which I put as an attachment to this issue. It seems to me that the example should work, at least it works well for a vector of integer argument.
The need is pretty simple : we have a C++ method with the following signature:
void Run(const std::vector<double> & in, std::vector<int> & out, std::vector<testing::Status> & status)
where testing::Status is an enum
and we will like to obtain a Python method like:
out, status = Run(in)
Using the attached example, the swig executable does not raise any error, and the Python Run method can be ran, but the output value status cannot be used, an error is raised:
status: (<Swig Object of type 'testing::Status *' at 0x7fa441156450>, <Swig Object of type 'testing::Status *' at 0x7fa441156660>) swig/python detected a memory leak of type 'testing::Status *', no destructor found. swig/python detected a memory leak of type 'testing::Status *', no destructor found.
Here are the different files that can be used to reproduce the error:
mylib.h, the C++ to wrap in Python
#include <vector>
namespace testing
{
typedef enum
{
Ok = 0,
Error = 1,
} Status;
class Algo
{
public:
void Run(const std::vector<double> & in, std::vector<int> & out, std::vector<testing::Status> & status)
{
status.resize(in.size());
out.resize(in.size());
for (int i=0; i<in.size(); ++i) {
out[i] = i;
status[i] = Status::Ok;
}
}
};
}
mymodule.i, the SWIG interface file
%module mymodule
%{
#include "mylib.h"
%}
%include "std_vector.i"
%include "typemaps.i"
%define STD_TEMPLATE(TYPE...)
%template() TYPE;
%apply TYPE& OUTPUT {TYPE&}
%typemap(argout) const TYPE& {
// do nothing for const references
}
%typemap(out) (TYPE&) = (const TYPE&);
%enddef
STD_TEMPLATE (std::vector <int>);
STD_TEMPLATE (std::vector <double>);
STD_TEMPLATE (std::vector < testing::Status >);
%include "mylib.h"
build.sh, the build command line used to compile binaries
${swig_install}/bin/swig \
-I. \
-I${swig_install}/share/swig/${swig_version}/python \
-I${swig_install}/share/swig/${swig_version} \
-c++ -python \
-outdir . \
-o "mymodule.cxx" \
"mymodule.i"
g++ -L${python_install}/lib -lpython3 \
-I${python_install}/include/python \
-I. \
-std=c++11 -shared -fPIC \
mymodule.cxx -o _mymodule.so
run.py, the example in Python that raises the error
import mymodule as mm
algo = mm.Algo()
out, status = algo.Run([1.1, 2.2])
print("out:", out)
print("status:", status)
SWIG doesn't know what to do with the vector of enum outputs. One way is to handle the typemaps yourself:
mylib.i
%module mylib
%{
#include "mylib.h"
%}
%include "std_vector.i"
%include "typemaps.i"
%define STD_TEMPLATE(TYPE...)
%template() TYPE;
%apply TYPE& OUTPUT {TYPE&}
%typemap(argout) const TYPE& {
// do nothing for const references
}
%typemap(out) (TYPE&) = (const TYPE&);
%enddef
STD_TEMPLATE (std::vector <int>);
STD_TEMPLATE (std::vector <double>);
// Don't require an input parameter in Python.
// Create a temporary vector to hold the output result.
%typemap(in,numinputs=0) std::vector<testing::Status>& (std::vector<testing::Status> tmp) %{
$1 = &tmp;
%}
// Create a Python list object the same size as the vector
// and copy and convert the vector contents into it.
%typemap(argout) std::vector<testing::Status>& (PyObject* list) %{
list = PyList_New($1->size());
for(int x = 0; x < $1->size(); ++x)
PyList_SET_ITEM(list, x, PyLong_FromLong($1->at(x)));
$result = SWIG_Python_AppendOutput($result, list);
%}
%include "mylib.h"
Output (same mylib.h and run.py):
out: (0, 1)
status: [0, 0]

Wrapp C++ library with SWIG with overloading called functions

I have compiled SWIG python wrapper for the following function:
mylib.h:
#pragma once
#include <string>
myEnum {foo=0, bar};
myEnum getResult(const std::string& path, std::string& result);
And interface:
%module mylib
%include <std_string.i>
%{
#include "mylib.h"
%}
%apply const std::string & {std::string &};
%apply std::string & {std::string &};
int getResult(const std::string& path, std::string& result);
But I have a problem with function getResult. Because in Python strings are immutable . So I interested can I overload my function in interface file?
For example something like this:
%module mylib
%include <std_string.i>
%{
#include "mylib.h"
%}
%apply const std::string & {std::string &};
%apply std::string & {std::string &};
std::string getResult(std::string& path)
{
std::string result;
getResult(path, result);
return result;
}

python pointer to C structure in SWIG -- accessing the struct members

I'm trying to get a very basic Python-to-C interface working with SWIG where I can pass a pointer to a structure to a C function and the members get populated, and I can then access the members in python.
In the below example, everything works except when I try to print the structure members:
print swig_test.diags_mem0_get(var)
Results in:
$ ./runpython.py
Traceback (most recent call last):
File "./runpython.py", line 11, in <module>
print swig_test.diags_mem0_get(var)
AttributeError: 'module' object has no attribute 'diags_mem0_get'
Whereas this:
print var.mem0
Results in:
$ ./runpython.py
<Swig Object of type 'uint16_t *' at 0x7f8261e15b40>swig/python detected a memory leak of type 'uint16_t *', no destructor found.
I am following the SWIG 3.0 Documentation, specifically section "5.5 Structures and unions" here: http://swig.org/Doc3.0/SWIGDocumentation.html#SWIG_nn31
What am I doing wrong?
I have distilled the example down to bare bones:
swig_test.h
typedef struct diags_t {
uint16_t mem0;
uint16_t mem1;
} diags;
diags *malloc_diags(void);
void free_diags(diags *pdiag);
int get_diags(diags *pdiags);
swig_test.c
#include <stdlib.h> // malloc()
#include <stdint.h> // uint16_t
#include "swig_test.h"
int main(int argc, char **argv) {
return 0;
}
int get_diags(diags *pdiags) {
pdiags->mem0 = 0xdead;
pdiags->mem1 = 0xbeef;
return 0;
}
diags *malloc_diags(void) {
diags *dptr = malloc(sizeof(diags));
return dptr;
}
void free_diags(diags *pdiag) {
if (pdiag != NULL)
free(pdiag);
}
swig_test.i
%module swig_test
%{
#include "swig_test.h"
%}
%include "swig_test.h"
Makefile
CXX = gcc
INCLUDES = -I./
COMPFLAGS = -c -Wall -fPIC
PYINC = /usr/include/python2.7
SWIG = /usr/bin/swig
all: swig_test _swig_test.so
swig_test: swig_test.o
$(CXX) -Wall $^ -o $#
swig_test.o: swig_test.c
$(CXX) $(COMPFLAGS) $(INCLUDES) $^
_swig_test.so: swig_test_wrap.o swig_test.o
$(CXX) -shared $^ -L$(PYLIB) -lpython2.7 -o $#
swig_test_wrap.o: swig_test_wrap.c
$(CXX) $(COMPFLAGS) $(INCLUDES) -I$(PYINC) $^
swig_test_wrap.c: swig_test.i
$(SWIG) -python $(INCLUDES) $^
And finally the simple python example:
runpython.py
#!/usr/bin/python2
import swig_test
var = swig_test.malloc_diags()
if var == 'NULL':
print "Error, no memory left"
else:
ret = swig_test.get_diags(var)
if ret == 0:
print swig_test.diags_mem0_get(var)
print var.mem0
swig_test.free_diags(var)
The functionality you're looking for comes in typemaps. The documentation freely admits that "At first glance, this code will look a little confusing." Here's what worked for me.
In essence, a typemap is a few lines of code that SWIG swaps in when it needs to convert between Python and C. They're separately defined for Python to C (%typemap(in)) and C to Python (%typemap(out)). SWIG's documentation also defines a few magic variables:
$input refers to an input object that needs to be converted to C/C++.
$result refers to an object that is going to be returned by a wrapper function.
$1 refers to a C/C++ variable that has the same type as specified in the typemap declaration (an int in this example).
For unsigned integer support, you just need in and out maps for uint8_t, uint16_t, and uint32_t
The lines below provide that functionality. They can go into SWIG's .i file, or the main header (with an ifdef SWIG guard around them).
/* uintXX_t mapping: Python -> C */
%typemap(in) uint8_t {
$1 = (uint8_t) PyInt_AsLong($input);
}
%typemap(in) uint16_t {
$1 = (uint16_t) PyInt_AsLong($input);
}
%typemap(in) uint32_t {
$1 = (uint32_t) PyInt_AsLong($input);
}
/* uintXX_t mapping: C -> Python */
%typemap(out) uint8_t {
$result = PyInt_FromLong((long) $1);
}
%typemap(out) uint16_t {
$result = PyInt_FromLong((long) $1);
}
%typemap(out) uint32_t {
$result = PyInt_FromLong((long) $1);
}
Resources I found useful:
http://www.swig.org/Doc3.0/Typemaps.html#Typemaps_nn3
https://docs.python.org/2/c-api/int.html?highlight=pyint_aslong
One "fix" is to change the uint16_t members to int types. Then this syntax works:
print var.mem0
Clearly SWIG has some problems with non-integer types.
I'd love it if somebody proposed an alternative solution that lets me keep my uint16_t types.

Swig returns 'No module name specified' error when including c++ code in python

I would like to include C++ code in python and decided to use swig.
I followed the example from the tutorial on the swig-site.
/* File: example.i */
%module example
%{
#define SWIG_FILE_WITH_INIT
#include "example.h"
%}
int fact(int n);
.
/* File: example.c */
#include "example.h"
int fact(int n) {
if (n < 0){ /* This should probably return an error, but this is simpler */
return 0;
}
if (n == 0) {
return 1;
}
else {
/* testing for overflow would be a good idea here */
return n * fact(n-1);
}
}
.
/* File: example.h */
int fact(int n);
If if I then run the command to wrap it, I get the following.
swig -c++ -python example.i
No module name specified using %module or -module.
How can I circumvent that?

typedef does not work with SWIG (python wrapping C++ code)

Hello and thanks for your help in advance !
I am writing a python wrapper (SWIG 2.0 + Python 2.7) for a C++ code. The C++ code has typedef which I need to access in python wrapper. Unfortunately, I am getting following error when executing my Python code:
tag = CNInt32(0)
NameError: global name 'CNInt32' is not defined
I looked into SWIG documentation section 5.3.5 which explains size_t as typedef but I could not get that working too.
Following is simpler code to reproduce the error:
C++ header:
#ifndef __EXAMPLE_H__
#define __EXAMPLE_H__
/* File: example.h */
#include <stdio.h>
#if defined(API_EXPORT)
#define APIEXPORT __declspec(dllexport)
#else
#define APIEXPORT __declspec(dllimport)
#endif
typedef int CNInt32;
class APIEXPORT ExampleClass {
public:
ExampleClass();
~ExampleClass();
void printFunction (int value);
void updateInt (CNInt32& var);
};
#endif //__EXAMPLE_H__
C++ Source:
/* File : example.cpp */
#include "example.h"
#include <iostream>
using namespace std;
/* I'm a file containing use of typedef variables */
ExampleClass::ExampleClass() {
}
ExampleClass::~ExampleClass() {
}
void ExampleClass::printFunction (int value) {
cout << "Value = "<< value << endl;
}
void ExampleClass::updateInt(CNInt32& var) {
var = 10;
}
Interface file:
/* File : example.i */
%module example
typedef int CNInt32;
%{
#include "example.h"
%}
%include <windows.i>
%include "example.h"
Python Code:
# file: runme.py
from example import *
# Try to set the values of some typedef variables
exampleObj = ExampleClass()
exampleObj.printFunction (20)
var = CNInt32(5)
exampleObj.updateInt (var)
Thanks again for your help.
Santosh
I got it working. I had to use typemaps in the interface file, see below:
- Thanks a lot to "David Froger" on Swig mailing lists.
- Also, thanks to doctorlove for initial hints.
%include typemaps.i
%apply CNInt32& INOUT { CNInt32& };
And then in python file:
var = 5 # Note: old code problematic line: var = CNInt32(5)
print "Python value = ",var
var = exampleObj.updateInt (var) # Note: 1. updated values returned automatically by wrapper function.
# 2. Multiple pass by reference also work.
# 3. It also works if your c++ function is returning some value.
print "Python Updated value var = ",var
Thanks again !
Santosh

Categories