Ctypes is a module in Python for facilitating easy interface between Python and C. Interfacing Python with C can be useful for improving the performance of a Python program. Interfacing C with Python can be useful from a testing point of view. Either way, python-c interfacing is going to save our lives and give us better results(most of the time).
The procedure in using ctypes is very simple.
(a) Create a shared library from c-code
(b) Load the shared library in Python using ctypes
(c) Create equivalent data structures in Python
(d) Call the functions in shared object with equivalent data structures
(a) Creating a shared library
gcc -shared -fPIC exlib.c -o libex.so
Here, I am assuming that we have the c code in a file named exlib.c
(b) Loading shared library in Python
# import ctype library
import ctypes as ct
# load the shared library
exlib = ct.cdll.LoadLibrary("./libex.so")
Once, we have these two working, we can proceed to examples.
C code is given first, followed by Python code.
--------------------------------------------------------------------------------
(1) Simplest One: Takes nothing and Returns nothing
void function_01(void)
{
printf("Hello World\n");
}
printf("Hello World\n");
}
exlib.function_01()
--------------------------------------------------------------------------------
(2) Integer and String
void function_02(char *pString, int value)
{
printf("%s%d\n", pString, value);
}
exlib.function_02("this year is ", 2012)
a = 1024
print "a =", a
exlib.function_02("a = ", a)
--------------------------------------------------------------------------------
(3) Unsigned int, Long, Unsigned Long
void function_03( unsigned int ui,
long l,
unsigned long ul )
{
printf( "uint = %u\n"
"long = %ld\n"
"ulong = %lu\n",
ui, l, ul);
}
ui = ct.c_uint ( 2**31 )
l = ct.c_long ( 2 ** 62 )
ul = ct.c_ulong( 2 ** 63 )
print "uint = ", ui.value
print "long = ", l .value
print "ulong = ", ul.value
exlib.function_03(ui, l, ul)
# Setting value directly
ui.value = 1000
l .value = 2000
ul.value = 3000
exlib.function_03(ui, l, ul)
--------------------------------------------------------------------------------
(4) Two Doubles go in, One Double comes out
double function_04(double a, double b)
{
return a * b;
}
# specifying input types, this will make the function call safe
exlib.function_04.argtypes = [ct.c_double, ct.c_double]
# specifying return type
exlib.function_04.restype = ct.c_double
a = ct.c_double( 10.0 )
b = ct.c_double( 2.0 )
c = exlib.function_04(a, b)
print c
--------------------------------------------------------------------------------
(5) Sending data by reference
void function_05( double * a)
{
*a = 2 * (*a);
}
a = ct.c_double( 100 )
print a.value
exlib.function_05( ct.byref(a) )
print a.value
--------------------------------------------------------------------------------
(6) Making things complicated with structures
typedef struct
{
double real;
double imag;
} complex_t;
void function_06( complex_t a)
{
printf("Real = %f, Imag = %f\n", a.real, a.imag);
}
class ComplexType(ct.Structure):
_fields_ = [("real", ct.c_double), ("imag", ct.c_double) ]
cc = ComplexType(10, 20)
exlib.function_06( cc )
cc.imag = 33
exlib.function_06( cc )
--------------------------------------------------------------------------------
(7) Making things more complicated with pointers and array
void function_07( int* a, int len)
{
int i;
for( i = 0 ; i < len; i++)
{
printf("%d, ", a[i]);
}
printf("\n");
}
a_len = 20
# defining array type
array_type = ct.c_int * a_len
# making an array
a = array_type()
# assigning values
for i in xrange(a_len) :
a[i] = i*i;
#taking the address
ap = ct.pointer(a)
exlib.function_07(ap, a_len)
--------------------------------------------------------------------------------
(8) With pointers and arrays inside structure
typedef struct
{
int a_len;
complex_t a[10];
int b_len;
complex_t *b;
} AB_t;
void function_08( AB_t* inp)
{
int i;
for(i = 0; i < inp->a_len; i++)
{
printf("%f,", inp->a[i].real);
printf("%f\n", inp->a[i].imag);
}
printf("\n");
for(i = 0; i < inp->b_len; i++)
{
printf("%f,", inp->b[i].real);
printf("%f\n", inp->b[i].imag);
}
}
ComplexArrayType10 = ComplexType * 10
ComplexArrayType100 = ComplexType * 100
class ABType(ct.Structure):
_fields_ = [ ("a_len", ct.c_int),
("a",ComplexArrayType10),
("b_len", ct.c_int),
("b", ct.POINTER(ComplexType) ) ]
x = ABType()
x.a_len = 8
for ii in xrange( x.a_len ):
x.a[ii].real = ii * 3;
x.a[ii].imag = ii * -3;
x.b_len = 5
b = ComplexArrayType100()
for ii in xrange( x.b_len ):
b[ii].real = ii * 10
b[ii].imag = ii * -10
# the following will not work without cast
x.b = ct.cast( ct.pointer(b), ct.POINTER(ComplexType) )
exlib.function_08( ct.pointer(x) )
--------------------------------------------------------------------------------
(9) Playing with Numpy Arrays
double function_09(double* inpVec, int stride, int length)
{
/*
* stride - address difference between successive elements
*/
int i;
int step = stride / sizeof(double);
double sum = 0;
double elem;
double* elem_p;
if(0 != stride % sizeof(double))
{
/* not needed if we specify argtypes in python */
printf("ERROR: Stride should be multiple of sizeof(double)\n");
return 0.0;
}
/*
* (a) find sum
* (b) do an inplace value change
*/
sum = 0;
printf("stride = %d, length = %d\n", stride, length);
for( i = 0; i < length; i++)
{
/* pointer to i th element */
elem_p = inpVec + i * step;
/* value of i th element */
elem = *elem_p;
/* calculate sum */
sum = sum + elem;
/* inplace value change */
*elem_p = 1.1 * elem + 0.01;
}
return sum;
}
# wrapper function
def wr_function_09( ipvec ):
# define input argument types
exlib.function_09.argtypes=[np.ctypeslib.ndpointer(dtype=np.double,
ndim = 1),
ct.c_int,
ct.c_int]
# define retrun type
exlib.function_09.restype = ct.c_double
stride = ipvec.strides[0]
length = ipvec.size
sum_r = exlib.function_09( ipvec, stride, length )
return sum_r
a = np.arange(6.0)
print a
sa = wr_function_09(a)
print sa
print a
sa = wr_function_09(a)
print sa
print a
# our function should work for both rows and columns
b = np.zeros( (6, 6), dtype = np.double )
# sending 4th column
sb = wr_function_09(b[:,4])
print b
# sending 3rd row
sb = wr_function_09(b[3,:])
print b
--------------------------------------------------------------------------------
(10) Handling Numpy Matrices
/* 2 dimensional matrix structure */
typedef struct
{
double *pData;
int rstride;
int cstride;
int rlen;
int clen;
} matrix_t;
/* function to get the address of element (i, j) */
double* get_elem_p( matrix_t m, int i, int j)
{
return (double *) ( (char *)(m.pData) +
i * m.rstride +
j * m.cstride );
}
void function_10( matrix_t a, matrix_t b)
{
/*
* A simple function to do an inplace matrix addition
*/
/* TODO:
* check for stride % sizeof(double) has to be done for both a and b
* check for rlen and clen has to be done for both a and b
*/
int i, j;
printf("%d, %d, %d, %d\n" ,
a.rstride, b.rstride, a.cstride, b.cstride);
printf("%d, %d, %d, %d\n" ,
a.rlen, b.rlen, a.clen, b.clen);
for( i = 0; i < a.rlen; i++ )
{
for( j = 0; j < a.clen; j++ )
{
*get_elem_p(a, i, j) += *get_elem_p(b, i, j);
}
}
}
class MatrixType(ct.Structure):
_fields_ = [("pData", ct.POINTER(ct.c_double)),
("rstride", ct.c_int),
("cstride", ct.c_int),
("rlen", ct.c_int),
("clen", ct.c_int)]
# wrapper function
def wr_function_10( inpmx1, inpmx2 ):
m1 = MatrixType()
m2 = MatrixType()
m1.pData = inpmx1.ctypes.data_as(ct.POINTER(ct.c_double))
m2.pData = inpmx2.ctypes.data_as(ct.POINTER(ct.c_double))
m1.rstride = inpmx1.strides[0]
m1.cstride = inpmx1.strides[1]
m2.rstride = inpmx2.strides[0]
m2.cstride = inpmx2.strides[1]
m1.rlen = inpmx1.shape[0]
m1.clen = inpmx1.shape[1]
m2.rlen = inpmx2.shape[0]
m2.clen = inpmx2.shape[1]
print m1.rstride, m2.rstride, m1.cstride, m2.cstride
print m1.rlen, m2.rlen, m1.clen, m2.clen
exlib.function_10(m1, m2)
a1 = np.zeros((5, 4), dtype = np.double)
a2 = a1 + 0.5
print a1
print a2
wr_function_10(a1, a2)
print a1
wr_function_10(a1, a2)
print a1
wr_function_10(a1, a2)
print a1
--------------------------------------------------------------------------------
Notes :
[1] Always make sure that memory allocation is handled by Python. Dynamic allocation inside c function for results required in Python can be problematic. Instead, create the required structures in Python and send their reference to C.
