We have an untemplated C++ class with a templated constructor. We were able to use SWIG 2 to make a Python wrapper, but the same code fails in SWIG 3: the wrapper class's constructor raises AttributeError("No constructor defined"). I'm hoping someone can suggest a clean fix or workaround.
Here an extract of the C++ header:
class FootprintSet {
public:
template <typename ImagePixelT>
FootprintSet(image::Image<ImagePixelT> const& img,
Threshold const& threshold,
int const npixMin=1, bool const setPeaks=true);
FootprintSet(geom::Box2I region);
...
and the main part of the SWIG interface file:
%shared_ptr(lsst::afw::detection::FootprintSet);
%include "lsst/afw/detection/FootprintSet.h"
%define %footprintSetOperations(PIXEL)
%template(FootprintSet) FootprintSet<PIXEL>;
%enddef
%extend lsst::afw::detection::FootprintSet {
%footprintSetOperations(boost::uint16_t)
%footprintSetOperations(int)
%footprintSetOperations(float)
%footprintSetOperations(double)
}
One crude workaround I've considered is to replace the templated constructor in the header with explicit versions for each specialization, e.g.:
class FootprintSet {
public:
#ifndef SWIG
template <typename ImagePixelT>
FootprintSet(image::Image<ImagePixelT> const& img,
Threshold const& threshold,
int const npixMin=1, bool const setPeaks=true);
#else
FootprintSet(image::Image<boost::unit16> const& img,
Threshold const& threshold,
int const npixMin=1, bool const setPeaks=true);
FootprintSet(image::Image<int> const& img,
Threshold const& threshold,
int const npixMin=1, bool const setPeaks=true);
FootprintSet(image::Image<float> const& img,
Threshold const& threshold,
int const npixMin=1, bool const setPeaks=true);
FootprintSet(image::Image<double> const& img,
Threshold const& threshold,
int const npixMin=1, bool const setPeaks=true);
#endif
FootprintSet(geom::Box2I region);
...
Or (probably better) put something similar in the SWIG interface, instead of the C++ header.
Still, I'm hoping there is a simpler solution. We would like to update to SWIG 3 for the C++11 support, but this is a significant blocker.
It does appear to be a regression in SWIG. Here's a reduced example:
price#price-laptop:~/test $ cat test.h
#ifndef TEST_H
#define TEST_H
#include <iostream>
#include <typeinfo>
namespace test {
class Foo
{
public:
template<typename T>
Foo(T bar);
~Foo() {}
void working() const {
std::cout << "WORKING" << std::endl;
}
};
}
#endif
price#price-laptop:~/test $ cat test.cc
#include "test.h"
namespace test {
template <typename T>
Foo::Foo(T) {
std::cout << typeid(T).name() << std::endl;
}
template Foo::Foo(int);
}
price#price-laptop:~/test $ cat test.i
%feature("autodoc", "1");
%module(package="test", docstring="test") testLib
%{
#include "test.h"
%}
%include "test.h"
%extend test::Foo {
%template(Foo) Foo<int>;
}
price#price-laptop:~/test $ clang++ -shared -undefined dynamic_lookup -o libtest.dylib test.cc
With SWIG 2.0.12:
price#price-laptop:~/test $ swig -python -c++ test.i
price#price-laptop:~/test $ clang++ -shared -undefined dynamic_lookup -o _testLib.so test_wrap.cxx -L. -ltest -I/usr/include/python2.7
price#price-laptop:~/test $ python -c "import testLib; testLib.Foo(1)"
i
With SWIG 3.0.2, it seems to treat Foo::Foo as an ordinary function, warns about it not having a return type, and ignores it:
price#price-laptop:~/test $ swig -python -c++ test.i
test.i:11: Warning 504: Function test::Foo::Foo(int) must have a return type. Ignored.
price#price-laptop:~/test $ clang++ -shared -undefined dynamic_lookup -o _testLib.so test_wrap.cxx -L. -ltest -I/usr/include/python2.7
price#price-laptop:~/test $ python -c "import testLib; testLib.Foo(1)"Traceback (most recent call last):
File "<string>", line 1, in <module>
File "testLib.py", line 82, in __init__
def __init__(self, *args, **kwargs): raise AttributeError("No constructor defined")
AttributeError: No constructor defined
Suggest you kick this upstream.
EDIT: This has been fixed upstream.
The compiler might need some help figuring out that Image is a templated type. Try putting typename:
FootprintSet(typename image::Image<ImagePixelT> const& img, ...
Related
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]
This question builds on the question: How to instantiate a template method of a template class with swig? .
However, compared to that question, the code I'm trying to wrap is a little different:
class MyClass {
public:
template <class T>
void f1(const string& firstArg, const T& value);
};
The MyClass is a regular C++ class, with one template function f1.
Attempt to wrap MyClass::f1:, i.e. the Swig .i file
%template(f1String) MyClass::f1<std::string>;
With the above, a Python client can do
o = MyClass
str1 = "A String"
o.f1String("", str1)
This interface require the Python client to learn about all different f1 function names, each one different depending on the type. Not so clean.
A cleaner interface can be obtained by overloading, extending in the interface file, e.g.
%extend MyClass {
void f1(const string& s, const string& s1){
$self->f1(s, s1);
}
void f1(const string& s, const int& anInt){
$self->f1(s, anInt);
}
}
This allow client code like this:
o = MyClass
str1 = "A String"
anInt = 34
o.f1("", str1)
o.f1("", anInt)
Question is, is there any way to obtain the interface above (by extending), without extending, using Swig?
Luckily the Python wrapper supports overloading, so you can simply instantiate the two methods with the same name and SWIG will do its magic to resolve the overloads at runtime. See 6.18 Templates in the chapter “SWIG and C++” of the documentation for more details.
test.i
%module example
%{
#include<iostream>
class MyClass {
public:
template <class T>
void f1(const std::string& firstArg, const T& value) {
std::cout << firstArg << ',' << value << '\n';
}
};
%}
%include <std_string.i>
class MyClass {
public:
template <class T>
void f1(const std::string& firstArg, const T& value);
};
%extend MyClass {
%template(f1) f1<std::string>;
%template(f1) f1<int>;
}
test.py
from example import *
o = MyClass()
str1 = "A String"
anInt = 34
o.f1("X", str1)
o.f1("Y", anInt)
Example workflow to compile and run:
$ swig -python -c++ test.i
$ g++ -Wall -Wextra -Wpedantic -I /usr/include/python2.7/ -fPIC -shared test_wrap.cxx -o _example.so -lpython2.7
$ python2.7 test.py
X,A String
Y,34
After reading an excellent answer on Dynamically rethrowing self-defined C++ exceptions as Python exceptions using SWIG, I am trying to throw a custom Exception from C++ and catch it in Python. I have tried to build the simplest example, so that I can see what I'm doing wrong. I would appreciate some help here.
I've started by creating a class called Example which has one method. In this method, I always throw my Exception which extends the Exception base class in C++. This exception is declared in the header.
I'm trying to catch this Exception in Python but so far I am unable to get this to compile. I tried some of the other solutions in the thread mentioned aboved, and while they compiled, I did not seem to be able to catch the Exception without Python aborting.
Here's what I have so far (a very basic project)
example.h
#ifndef EXAMPLE_H
#define EXAMPLE_H
#include <string>
#include <exception>
class Example
{
public:
std::string getName();
};
class ExampleException
{
public:
ExampleException(const std::string &what) { this->eMsg == (std::string(what)); }
const char * what() const throw() { return eMsg.c_str(); }
private:
std::string eMsg;
};
#endif // EXAMPLE_H
example.cpp
#include "example.h"
#include "exception"
using namespace std;
std::string Example::getName()
{
/* Always throw an exception just so we can test this */
throw ExampleException("Couldn't find a name");
return("John");
}
example.i
%module example
%include "example.i"
%include exception.i
%init %{
m_ExampleException = PyErr_NewException("_example.ExampleException", NULL, NULL);
Py_INCREF(m_ExampleException);
PyModule_AddObject(m, "ExampleException", m_ExampleException);
%}
%exception {
try {
$action
} catch (ExampleException &e) {
PyErr_SetString(ExampleException, const_cast<char*>(e.what()));
SWIG_fail;
}
}
%{
#include "example.h"
#define SWIG_FILE_WITH_INIT
static PyObject* m_ExampleException;
%}
test.py
import example
try:
print example.Example_getName()
except ExampleException as e:
print e.what()
And I run the following commands to build the project:
swig -verbose -python -c++ example.i
g++ -fPIC -c example_wrap.cxx -I/usr/include/python2.7 -o example_wrap.o -fPIC
g++ -c example.cpp -I/usr/include/python2.7 -fPIC
g++ -shared example_wrap.o example.o -o _example.so
Unfortunately, I am given the following error message as well as a warning:
example_wrap.cxx: In function ‘void init_example()’:
example_wrap.cxx:3839:82: warning: deprecated conversion from string constant to ‘char*’ [-Wwrite-strings]
m_ExampleException = PyErr_NewException("_example.ExampleException", NULL, NULL);
^
In file included from /usr/include/python2.7/Python.h:80:0,
from example_wrap.cxx:171:
/usr/include/python2.7/object.h:769:20: error: expected primary-expression before ‘)’ token
((PyObject*)(op))->ob_refcnt++)
I feel like I'm missing something obvious and would appreciate an extra pair of eyes. I have not used SWIG and C++ together extensively before.
Thanks for your time
This program in C runs and compiles well :
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <getopt.h>
#include <atasmart.h>
int main(){
const char *device = "/dev/sda";
int ret;
uint64_t ms;
SkDisk *d;
if ((ret = sk_disk_open(device, &d)) < 0) {
printf("Failed to open disk\n");
return 1;
}
if ((ret = sk_disk_smart_read_data(d)) < 0) {
printf("Failed to read SMART data: \n");
}
if ((ret = sk_disk_smart_get_power_on(d, &ms)) < 0) {
printf("Failed to get power on time:\n");
}
printf("%llu\n", (unsigned long long) ms);
return 0;
}
using:
gcc atatest.c `pkg-config --cflags --libs libatasmart`
However while trying to create python bindings based on that program:
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <getopt.h>
#include <atasmart.h>
#include <Python.h>
static PyObject *pySmart_powerOn(PyObject *self, PyObject *args)
{
const char *device = "/dev/sda";
int ret;
uint64_t ms;
SkDisk *d;
if (!PyArg_ParseTuple(args, "s", &device))
{
return NULL;
}
if ((ret = sk_disk_smart_get_power_on(d, &ms)) < 0) {
return Py_BuildValue("s", "Failed to get power on time");
}
return Py_BuildValue("K", (unsigned long long) ms);
}
static PyMethodDef pySmart_methods[] = {
{ "powerOn", (PyCFunction)pySmart_powerOn, METH_VARARGS, NULL },
{ NULL, NULL, 0, NULL }
};
PyMODINIT_FUNC initpySmart()
{
Py_InitModule3("pySmart", pySmart_methods, "Trial module");
}
I create a shared library using
gcc -shared -I/usr/include/python2.7 `pkg-config --cflags --libs libatasmart` atabind.c -o pySmart.so -fPIC
then I get a warning as follows :, but the file compiles
In file included from /usr/include/python2.7/Python.h:8:0,
from atabind.c:12:
/usr/include/python2.7/pyconfig.h:1158:0: warning: "_POSIX_C_SOURCE" redefined [enabled by default]
/usr/include/features.h:214:0: note: this is the location of the previous definition
when in Python i run
import pySmart
I get
ImportError: ./pySmart.so: undefined symbol: sk_disk_smart_get_power_on
My guess is that the error is caused because I have compiled the pySmart.so shared library with incorrect flags/options.. but I'm unable to figure it out!
You need to specify linker flags (-lfoo) after your source files. That's because of the way how linker works: when you specify a library to it, it checks the library for symbols needed so far. If no symbols needed (as if you didn't get to any source objects yet), it just skips the library.
Try the following commandline:
gcc -shared -I/usr/include/python2.7 \
`pkg-config --cflags libatasmart` \
atabind.c \
`pkg-config --libs libatasmart` \
-o pySmart.so -fPIC
You should include your Python.h first then any std header.
All function, type and macro definitions needed to use the Python/C API are included in your code by
#include "Python.h"
This implies inclusion of the following standard headers:
<stdio.h>, <string.h>, <errno.h>, <limits.h>, <assert.h> and <stdlib.h> (if available).
Since Python may define some pre-processor definitions which affect the standard headers on some systems, you must include Python.h before any standard headers are included.
Alternatively:
just _GNU_SOURCE , and it will be ignored by GNU libc's /usr/include/features.h
I am new to the Python C binding swig and have been trying to solve this problem for a while now. I have an external C library (Example.c) that I would like to call from Python. I read Swig tutorial and able to generate the wrapper in no time. The problem now is that when I invoke the API and I got this:
>>> import Example
>>> dir(Example)
['Example_CreateConnection', 'trimmed to fit the screen']
>>> Example.Example_CreateConnection("")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: in method 'Example_CreateConnection', argument 1 of type 'ExampleChar const *'
It seemed like it cannot find the type ExampleChar. The following is my swig file:
%module Example
%{
#include "ExampleSDK.h"
%}
%include "ExampleTypes.h"
%include "ExampleSDK.h"
ExampleTypes.h looks like this:
#ifndef ExampleTypes_H
#define ExampleTypes_H
typedef wchar_t ExampleChar;
#endif /* ExampleTypes_H */
ExampleSDK.h looks like this:
#ifndef ExampleSDK_H
#define ExampleSDK_H
#include "ExampleTypes.h"
void Example_CreateConnection(const ExampleChar *temp);
#endif /* ExampleSDK_H */
The following are the command lines being invoked to generate the wrapper:
swig -python -I. Example.i
gcc -c Example.c -I/Developer/SDKs/MacOSX10.6.sdk/usr/include/
gcc -c Example_wrap.c -I/usr/include/python2.6 -I.
gcc -bundle -flat_namespace -undefined suppress -o _Example.so Example_wrap.o Example.o -L/usr/lib/python2.6/config/ -lpython2.6
Here is how the Example.c looks like:
#include "runetype.h" // for Mac wchar_t definition
#include "ExampleSDK.h"
void Example_CreateConnection(const ExampleChar *temp)
{
//do nothing
}
I am not sure what is wrong with it. I hope someone will be able to point out the mistake(s) I have done over here. Thank you.
Regards,
Chuan Lim
Last time I used wchat_t with SWIG+Python I ended up needing to add something like:
%include "pywstrings.swg"
%include "pystrings.swg"
%include "std_string.i"
%include "typemaps.i"
%fragment("SWIG_AsVal_wchar_t", "header", fragment="<wchar.h>") {
SWIGINTERN int SWIG_AsVal_wchar_t(PyObject* p, wchar_t* c) {
return SWIG_OK;
}
}
%fragment("SWIG_From_wchar_t", "header", fragment="<wchar.h>") {
SWIGINTERNINLINE PyObject* SWIG_From_wchar_t(wchar_t c) {
return SWIG_Py_Void();
}
}
// Python -> C
%typemap(in) wchar_t const * {
$1 = PyString_to_wchar_t($input);
}
// C -> Python
%typemap(out) wchar_t * {
$result = wchar_t_to_PyObject($1);
}
in my Swig interface file.