Reputation: 33628
I have a function in my C library, say runsim()
which takes pointer to struct repdata
as one of the arguments, where struct repdata
is given by
struct repdata {
int *var1;
int *var2;
int *var3;
char *var4;
double *var5;
double *var6;
int *var7;
};
When using C exclusively, I initialize a variable of type struct repdata
calling the function,
struct repdata data;
void create_data_container(struct repdata *data, int len_data)
{
data -> var1 = malloc( sizeof(int) * len_data );
data -> var2 = malloc( sizeof(int) * len_data );
data -> var3 = malloc( sizeof(int) * len_data );
data -> var4 = malloc( sizeof(char) * len_data );
data -> var5 = malloc( sizeof(double) * len_data);
data -> var6 = malloc( sizeof(double) * len_data);
data -> var7 = malloc( sizeof(int) * len_data);
}
and then fill this struct as simulation proceeds. After I have written the data to a file, I free the memory using the standard
free(data.var1);
free(data.var2);
.
.
.
free(data.var7);
I want to call the runsim()
function from Python using Python Ctypes. For this I need to pass a pointer to a variable (that is equivalent to the type of struct repdata
) as one of the runsim()
arguments. Suppose that in Python I have defined an equivalent of struct repdata
in the following manner.
import ctypes as C
class Repdata(C.Structure):
_fields_ = [
("var1", C.POINTER(C.c_int)),
("var2", C.POINTER(C.c_int)),
("var3", C.POINTER(C.c_int)),
("var4", C.POINTER(C.c_char)),
("var5", C.POINTER(C.c_double)),
("var6", C.POINTER(C.c_double)),
("var7", C.POINTER(C.c_int)),
]
What is the equivalent of create_data_container
function shown above on the Python side? I want to initialize an instance of Repdata which can be passed to C code and has sufficient memory for storing the replication data. And, once simulation is completed, how do I free the memory from Python?
I am using Ubuntu Linux 12.04.
Thanks in advance for your help.
Upvotes: 20
Views: 23757
Reputation: 177715
You can allocate buffers using ctypes
and assign them to the pointers. Once the Python ctypes objects have no references they will be freed automatically. Here's a simple example (with a Windows DLL...don't have a Linux machine handy, but the idea is the same) and a Python wrapper.
create_string_buffer
allocates a writable buffer that can be passed from Python to C that ctypes
will marshal as a char*
.
You can also create writable arrays of ctypes
types with the syntax:
variable_name = (ctypes_type * length)(initial_values)
test.c
#include <stdio.h>
#include <string.h>
#ifdef _WIN32
# define API __declspec(dllexport)
#else
# define API
#endif
typedef struct example {
char* data;
int len; // of data buffer
double* doubles;
int count; // of doubles
} example;
API void func(example* p) {
// Put some example data in the buffers
strcpy_s(p->data, p->len, "hello, world!");
for(int i = 0; i < p->count; i++)
p->doubles[i] = 1.1 * (i + 1);
}
test.py
import ctypes as ct
class Example(ct.Structure):
_fields_ = (('data', ct.POINTER(ct.c_char)),
('len', ct.c_int),
('doubles', ct.POINTER(ct.c_double)),
('count', ct.c_int))
def __init__(self, length, count):
self.data = ct.create_string_buffer(length)
self.len = length
self.doubles = (ct.c_double * count)()
self.count = count
def __repr__(self):
'''Return string describing how to print an Example object.
'''
# Note that slicing a pointer to a specific
# length returns a list of if its objects.
return (f'Example({ct.string_at(self.data)}, {self.doubles[:self.count]}')
class Dll:
def __init__(self):
self.dll = ct.CDLL('./test')
self.dll.func.argtypes = ct.POINTER(Example),
self.dll.func.restype = None
def func(self, ex):
self.dll.func(ct.byref(ex))
d = Dll()
e = Example(20, 5)
print('before:', e)
d.func(e)
print('after:', e)
Output:
before: Example(b'', [0.0, 0.0, 0.0, 0.0, 0.0])
after: Example(b'hello, world!', [1.1, 2.2, 3.3000000000000003, 4.4, 5.5])
Upvotes: 21