As an example let's work with the following header file (lib.h):
#include <stdint.h>
struct foo {
const uint32_t *data;
};
void bar(struct foo *f);
and a corresponding implementation (lib.c):
#include "lib.h"
#include <stdio.h>
#include <assert.h>
void bar(struct foo *f) {
assert(f);
assert(f->data);
for (unsigned i = 1; i <= f->data[0]; ++i) {
fprintf(stdout, "%d\n", (int)f->data[i]);
}
}
I compiled this with:
gcc -Wall -Wextra -shared -o lib.so lib.c -std=c99 -g
and generated some test data in a file (input.dat) with:
with open('input.dat', 'wb') as f:
f.write(struct.pack("IIII", 3,1,2,3))
this assumes that the data you're planing to read is already in machine endian form in your file.
Passing the contents of input.dat from Python to bar via the foo.data can be done with both ctypes and SWIG.
Ctypes
import ctypes
lib = ctypes.CDLL("lib.so")
class foo(ctypes.Structure):
_fields_ = [('data', ctypes.POINTER(ctypes.c_uint32))]
bar = lib.bar
bar.argtypes = [ctypes.POINTER(foo)]
# Call the function with the argument:
arg = foo()
with open('input.dat', 'rb') as f:
arg.data = ctypes.cast(ctypes.create_string_buffer(f.read()), ctypes.POINTER(ctypes.c_uint32))
bar(ctypes.byref(arg))
The main work here is the call to ctypes.cast with the string buffer created from what we read in from the file that allows the assignment to happen (correctly).
(I think the call to ctypes.byref(arg) when calling bar is redundant also because we already set argtypes for bar).
SWIG
For SWIG things are a little different and not the simplest SWIG usage. This is because of the low-level casting we need to do. (Normally you'd do %array_class and build the array in Python, or simply use a Python list instead).
This is the module interface I used for a SWIG equivalent:
%module method2
%{
#include "lib.h"
%}
%ignore foo::data; // #1
%include "lib.h"
%rename("%s") foo::data; // #2
// #3
%{
void foo_data_set(struct foo *f, char const *data) {
f->data = data;
}
const char *foo_data_get(const struct foo *f) {
return NULL; // Hard to make this meaningful
}
%}
%extend foo {
const char *data; // #4
}
- Tell SWIG to ignore
foo::data when it see it inside the headerfile - we want to replace it with something that can be used as a String
- After reading lib.h clear the %ignore so we can %extend and add our alternative version
- C code only implementation of getters and setters for our special version of
foo::data. All the hardwork is actually done behind the scenes for us by he SWIG libraries.
- Tell SWIG that we'd like to pretend that
foo::data is a const char * instead.
We build it with:
swig -python -Wall method2.i && gcc -Wall -Wextra method2_wrap.c -I/usr/include/python2.7/ -lpython2.7 -shared -o _method2.so ./lib.so
This lets us write something like:
from method2 import *
# Call the function with the argument:
arg = foo()
with open('input.dat', 'rb') as f:
arg.data = f.read()
bar(arg)
Which is simple and works exactly as you'd hope. (Note: this only works with Python 2.7, a Python 3 solution is slightly more complex since f.read() is bytes not str)