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:
Here is some documentation on SWIG interface syntax.
First, here is my interface file (http.i):
Finally, I just have to write a shell script to build each languages with the same http.i:
#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.%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.#!/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 ;)

