1

I've started to play around with SWIG, so that I can use C libraries in Python. I have this piece of code where I'm passing a Python String into a C function that expects a "void *"

example.h:

char test(void *buf);

example.c:

char test(void *buf) {
    char *c = (char*)buf;
    return *c;
}

Python:

import example
buf = "hello"
example.test(buf)

When I run it, I get the following error:

TypeError: in method 'test', argument 1 of type 'void *'

However, when I change the "void*" parameter to "char*", it seems to work. I'm a little confused as I thought "void*" matches any kind of pointer. Anyways, I dug around and came across the ctypes library and casted it to a c_void_p (Python: converting strings for use with ctypes.c_void_p()). That didn't seem to work for me.

As a work around I made a wrapper in my swig file:

/* File: example.i */
%module example
%include typemaps.i

%{
#include "example.h"
%}

%include "example.h"

%inline %{
    char test_wrapper(char *buf) {
        void *voidBuf = (void*)buf;
        return test(voidBuf);
    }
%}

This seems to work. However, I was wondering if someone could provide an explanation as to why the ctypes approach didn't work. If I was completely off the mark with the ctypes approach, is there a more appropriate way than creating an inline wrapper?

Thanks guys!

Community
  • 1
  • 1
trav.chung
  • 155
  • 2
  • 11

1 Answers1

1

Your inline wrapper approach is a sound generalized way to work around functions that aren't quite usable directly. Generally SWIG tries to mimic the behavior of C or C++ inside the target language. In this case though as you observed it doesn't quite do that. I think the reason for this is twofold: firstly it's not obvious how you'd go about doing that in some of the languages SWIG supports (e.g. Java). Secondly, even though you could do something in Python that would work for this specific case in general void* is pretty ambiguous to a C programmer without further guidance about how it is intended to be interpreted/used. There's a mis-match between C and Python here too - strings are reference counted and immutable in Python so it would be very easy to stray into circumstances where the Python behavior breaks in ways that are totally non-obvious to Python programmers.

Using ctypes to perform the cast isn't really a viable solution, SWIG and ctypes have fundamentally very different approaches to wrapping things and typing of arguments. (If you're curious I just wrote a fairly detailed answer looking at interpoerability between them)

The other option in the general case is to user %include <cpointer.i> to generate some code for casting/converting for you, e.g.:

%pointer_cast(type1, type2, name)

However in this instance since you're using char* so the note in the first note documentation applies:

Note: None of these macros can be used to safely work with strings (char * or char **).

Note: When working with simple pointers, typemaps can often be used to provide more seamless operation.

And in general I'd favor the point made by the second note, which is that by doing a little more work (e.g. %inline to supply an overload) gives a more intuitive behavior. So in your specific case the only change I'd recommend would be using %rename(test) test_wrapper; to make it into an overload inside Python. (If it were C++ instead of C that you're targeting then you could do it as an overload inside C++)

Community
  • 1
  • 1
Flexo
  • 87,323
  • 22
  • 191
  • 272