1 · 07

Generates your C++ bindings easily with SWIG

Today, I have to develop a client library. This client library aims to allow users to use my storage webservice app more easily. Mainly to avoid them to read my HTTP Rest API documentation. I want to target the most used languages, and of course, I don't want to develop the same library for each languages. The main reason is that my client library is a draft for the moment, and I want to make changes on the client API over the time. This library is very high level, so I take only the HTTP core class for the example:
#ifndef __HTTP_H__
#define __HTTP_H__

#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include <vector>
#include <exception>

#include <curl/curl.h>

namespace HTTP
{
class Error : public std::exception
{
private:
std::string _message;

public:
Error(const std::string &msg) : _message("HTTPError: ")
{
_message.append(msg);
}
virtual const char* what() const throw()
{
return _message.c_str();
}
virtual ~Error() throw() {}
};

class Response
{
private:
friend class Request;
std::map<std::string, std::string> _headers;
std::string  _version;
std::string  _code;
std::string  _message;
std::string  _body;

void  _parseHeaderLine(const std::string &headerData);
Response() {}

public:
const std::map<std::string, std::string> &getHeaders() const { return _headers; }
const std::string  &getVersion() const { return _version; }
const std::string  &getCode() const { return _code; }
const std::string  &getMessage() const { return _message; }
const std::string  &getBody() const { return _body; }
friend std::ostream&  operator<<(std::ostream &out, const Response &resp)
{
std::map<std::string, std::string>::const_iterator it, ite = resp._headers.end();
for (it = resp._headers.begin(); it != ite; ++it)
out << it->first << ": " << it->second << std::endl;
return out;
}
};

class Request
{
private:
std::string      _host;
unsigned int    _port;
CURL        *_curl;
struct curl_slist  *_headerList;
Response      _response;
std::ofstream    _outFile;
std::ifstream    _inFile;
std::string      _credentials;
std::vector<char>  _buffer;

void  _request(const std::string &uri);

public:
Request(const std::string &host, const unsigned int port = 80);
virtual ~Request();

virtual void  setHeader(const std::string &name, const std::string &value);
virtual void  setCredentials(const std::string &login, const std::string &passwd);
virtual void  get(const std::string &uri, const std::string &outFile = "");
virtual void  post(const std::string &uri, const std::string &data);
virtual void  postFile(const std::string &uri, const std::string &inFile);
virtual void  setVerbose();
virtual void  setHost(const std::string &host, const unsigned int port = 80);
static size_t  writeHeaderCallback(void *ptr, size_t size, size_t nmemb, void *stream);
static size_t  writeCallback(void *ptr, size_t size, size_t nmemb, void *stream);
static size_t  readCallback(void *ptr, size_t size, size_t nmemb, void *stream);
virtual const Response  &getResponse() const { return _response; }
};
}

#endif /* !__HTTP_H__ */
SWIG uses interface files to parse and to generate bindings. IMHO, a good thing is to have an only one interface file to add language bindings much easier.
Here is some documentation on SWIG interface syntax. First, here is my interface file (http.i):
%module http

%include stl.i
%include exception.i

%exception
{
try
{
$function
}
catch (std::exception &e)
{
SWIG_exception(SWIG_RuntimeError, e.what());
}
catch (...)
{
SWIG_exception(SWIG_RuntimeError, "Unknown exception");
}
}

%include "http.h"
%{
#include "http.h"
%}
All C++ code between %{ ... }% tags will be copied during generation. SWIG provides a lot of interface files. Some of them are language-dependant. So I have written a generic exception handler to use the same interface file for all targetted languages.
Finally, I just have to write a shell script to build each languages with the same http.i:
#!/bin/sh

CFLAGS_python_Darwin=-I/usr/include/python2.5
LDFLAGS_python_Darwin="-dynamiclib -lpython2.5 -o _zws.so"

CFLAGS_java_Darwin=-I/System/Library/Frameworks/JavaVM.framework/Headers
LDFLAGS_java_Darwin="-dynamiclib -o libzws.dylib"

CFLAGS_ruby_Darwin=-I/System/Library/Frameworks/Ruby.framework/Headers
LDFLAGS_ruby_Darwin="-dynamiclib -lruby -o zws.bundle"

CFLAGS_php_Darwin="$(php-config --includes)"
LDFLAGS_php_Darwin="-bundle -o zws.so"

if [ $# -lt 1 ] ; then
echo Usage $0 language
exit 1
fi

LANG=$1
ROOT=$(dirname $0)
OSNAME=$(uname -s)
ARCH=$(uname -p)

# All the source files
SRC="http.cpp"

(
DIR=${LANG}_${OSNAME}_${ARCH}
rm -rf ${DIR}/*
mkdir -p ${ROOT}/${DIR}
cd ${ROOT}/${DIR}

# This options will be always added to the compilation/linking
CFLAGS+="-undefined suppress -flat_namespace"
LDFLAGS+="-lcurl"

CFLAGS+=" $(eval echo \$CFLAGS_${LANG}_${OSNAME})"
LDFLAGS+=" $(eval echo \$LDFLAGS_${LANG}_${OSNAME})"

swig -I./src -${LANG} -c++ -o wrapper.cpp -outdir . ./http.i
g++ ${CFLAGS} ${SRC} wrapper.cpp ${LDFLAGS}
)
Notes:
  • ./script.sh python to generate python bindings.
  • Some of the code below has been modified for the example, the most important thing to understand is the way of use.
  • I work on Mac OS X, so you just have to add (CFLAGS|LDFLAGS)_<lang>_<os> for other configs.
  • It would be cleaner with a Makefile ;)
Cheers !