Learning SWIG
Warning, I don’t know SWIG. Here’s my notes as a total neophyte.
All posts in the series
- Learning SWIG
- Anatomy of SWIG
- Automatic garbage collection of opaque pointers in SWIG
Here’s a simple one from sql.h
%module sql SQLRETURN SQL_API SQLAllocEnv(SQLHENV *OUTPUT);
The above is clearly not going to work without some defines
%module sql typedef short SQLSMALLINT; typedef SQLSMALLINT SQLRETURN; /* SQL_API is __stdcall on Windows */ #define SQL_API SQLRETURN SQL_API SQLAllocEnv(SQLHENV *OUTPUT);
Taking a look at the generated code, and it is incorrect because we want to call the function without arguments. i.e. in python env = SQLAllocEnv().
if (!PyArg_ParseTuple(args,(char *)"O:SQLAllocEnv",&obj0)) SWIG_fail;
Sounds like we’ll need one of those dreaded typemaps. Here’s the result after a few attempts. The plan is as follows, we’ll define a local SQLHENV called env, and then assign it’s address to arg1, which will be passed to SQLAllocEnv. Observe the absence of semicolons.
%module sql
typedef short SQLSMALLINT;
typedef SQLSMALLINT SQLRETURN;
/* SQL_API is __stdcall on Windows */
#define SQL_API
%typemap(in, numinputs=0) SQLHENV *OUTPUT
(
SQLHENV env
)
{
$1 = &env
}
SQLRETURN SQL_API SQLAllocEnv(SQLHENV *OUTPUT);
The result looks a bit better.
SQLHENV *arg1 = (SQLHENV *) 0 ;
SQLHENV env1 ;
SQLRETURN result;
{
arg1 = &env1
}
if (!PyArg_ParseTuple(args,(char *)":SQLAllocEnv")) SWIG_fail;
result = (SQLRETURN)SQLAllocEnv(arg1);
resultobj = SWIG_From_short((short)(result));
return resultobj;
We’re passing in the right arguments, but we are returning the SQLRETURN. SQLRETURN is an error code, which we should check and probably raise an exception. But first, we should probably cast the SQLHENV to a pointer type. (My preference would be a reference-counted pointer type, so that we can call SQLDealloc automatically, but I don’t know how yet).
To create a pointer to the SQLHENV, I used the SWIG_NewPointerObj incantation. Where did I get this from? I cheated. I created a prototype like this SQLHENV Dummy(); and observed what Swig generated. This is almost certainly not the right way to do this.
%typemap(argout) SQLHENV *OUTPUT
{
$result = SWIG_NewPointerObj((SQLHENV *)memcpy((SQLHENV *)malloc(sizeof(SQLHENV)),$1,sizeof(SQLHENV)), SWIGTYPE_p_SQLHENV, SWIG_POINTER_OWN | 0 );
}
However, the wrapper code generated is almost decent.
result = (SQLRETURN)SQLAllocEnv(arg1);
resultobj = SWIG_From_short((short)(result));
{
resultobj = SWIG_NewPointerObj((SQLHENV *)memcpy((SQLHENV *)malloc(sizeof(SQLHENV)),arg1,sizeof(SQLHENV)), SWIGTYPE_p_SQLHENV, SWIG_POINTER_OWN | 0 );
}
It would be a good time to check for exceptions.
/* I found it necessary to add the following include */
%include "exception.i"
%typemap(ret) SQLRETURN
{
if (!SQL_SUCCEEDED($1)) SWIG_exception(SWIG_RuntimeError, "failed!");
}
Which gives us the following:
#ifdef __cplusplus
extern "C" {
#endif
SWIGINTERN PyObject *_wrap_SQLAllocEnv(PyObject *SWIGUNUSEDPARM(self), PyObject *args) {
PyObject *resultobj = 0;
SQLHENV *arg1 = (SQLHENV *) 0 ;
SQLHENV env1 ;
SQLRETURN result;
{
arg1 = &env1
}
if (!PyArg_ParseTuple(args,(char *)":SQLAllocEnv")) SWIG_fail;
result = (SQLRETURN)SQLAllocEnv(arg1);
resultobj = SWIG_From_short((short)(result));
{
resultobj = SWIG_NewPointerObj((SQLHENV *)memcpy((SQLHENV *)malloc(sizeof(SQLHENV)),arg1,sizeof(SQLHENV)), SWIGTYPE_p_SQLHENV, SWIG_POINTER_OWN | 0 );
}
{
if (FAILED(result)) SWIG_exception(SWIG_RuntimeError, "failed!");
}
return resultobj;
fail:
return NULL;
}
I don’t really like the SWIG_NewPointerObj bit. It seems a little too error-prone. Instead, I investigated an alternate approach of letting SWIG generate all its typemaps.
%module sql
typedef short SQLSMALLINT;
typedef SQLSMALLINT SQLRETURN;
/* SQL_API is __stdcall on Windows */
#define SQL_API
%rename(SQLAllocEnv) SQLAllocEnv_new;
%inline %{
SQLHENV SQLAllocEnv_new()
{
SQLHENV hEnv;
SQLRETURN result = SQLAllocEnv(&hEnv);
if (SQL_SUCCEEDED(result))
return hEnv;
}
%}
The result is better, but now I’ve lost my exception handling capability.
SWIGINTERN PyObject *_wrap_SQLAllocEnv(PyObject *SWIGUNUSEDPARM(self), PyObject *args) {
PyObject *resultobj = 0;
SQLHENV result;
if (!PyArg_ParseTuple(args,(char *)":SQLAllocEnv")) SWIG_fail;
result = SQLAllocEnv_new();
resultobj = SWIG_NewPointerObj((SQLHENV *)memcpy((SQLHENV *)malloc(sizeof(SQLHENV)),&result,sizeof(SQLHENV)), SWIGTYPE_p_SQLHENV, SWIG_POINTER_OWN | 0 );
return resultobj;
fail:
return NULL;
}
With a little help from StackOverflow, we are now closer to a final solution.
%module sql
typedef short SQLSMALLINT;
typedef SQLSMALLINT SQLRETURN;
/* SQL_API is __stdcall on Windows */
#define SQL_API
%exception SQLAllocEnv_new {
$action;
if (!SQL_SUCCEEDED($1)) SWIG_exception(SWIG_RuntimeError, "failed!");
}
%rename(SQLAllocEnv) SQLAllocEnv_new;
%inline %{
SQLHENV SQLAllocEnv_new()
{
SQLHENV hEnv;
SQLRETURN result = SQLAllocEnv(&hEnv);
if (SQL_SUCCEEDED(result))
return hEnv;
}
%}
This gives us an almost equivalent result to the first attempts.
SQLHENV SQLAllocEnv_new()
{
SQLHENV hEnv;
SQLRETURN result = SQLAllocEnv(&hEnv);
if (SQL_SUCCEEDED(result))
return hEnv;
}
#ifdef __cplusplus
extern "C" {
#endif
SWIGINTERN PyObject *_wrap_SQLAllocEnv(PyObject *SWIGUNUSEDPARM(self), PyObject *args) {
PyObject *resultobj = 0;
SQLHENV result;
if (!PyArg_ParseTuple(args,(char *)":SQLAllocEnv")) SWIG_fail;
{
result = SQLAllocEnv_new();;
if (!SQL_SUCCEEDED($1)) SWIG_exception(SWIG_RuntimeError, "failed!");
}
resultobj = SWIG_NewPointerObj((SQLHENV *)memcpy((SQLHENV *)malloc(sizeof(SQLHENV)),&result,sizeof(SQLHENV)), SWIGTYPE_p_SQLHENV, SWIG_POINTER_OWN | 0 );
return resultobj;
fail:
return NULL;
}
About this entry
You’re currently reading “ Learning SWIG ,” an entry on Chui's Counterpoint
- Published:
- 8.22.12 / 2am
- Category:
- Engineering notes, Python
No comments
Jump to comment form | comments rss [?]