I am trying to create a Python wrapper for an old C program that takes inputs as pointers. At this point, I can get the program to run but cannot figure out how to get back the values at the designated pointers.
This is a simplified C script:
#include <stdlib.h>
void cprogram(double *wts, double *res, int *kks, int n, double *ex) {
int m;
m=n+1;
res[0]=1.0;
kks[0]=1.0;}
And this is my simplified Python code:
from ctypes import *
import sys
libc = CDLL("src/program.so")
class CONTEXT(Structure):
_fields_ = [
("wts", POINTER(c_double)), //tried just to see if it would work
("res", c_double),
("kks", c_int),
("n", c_int),
("ex", c_double)]
def runner():
kk = (1,2,3)
n = 3
mm = n + 1
wts = (c_double * n)(1, 1, 1)
res = (c_double * mm)(0)
kks = (c_int * len(kk))(*kk)
n = c_int(n)
ex = c_double(0)
libc.cprogram.restype = POINTER(CONTEXT)
tmp = libc.cprogram(wts, res, kks, n, ex)
runner()
I have tried commands like print tmp[1].wts[1]
and print tmp[2]
but this only prints the memory address and not the value (or incredibly small values that are incorrect like 2.15880221124e-314). I would like to be able to return a list of the values of wts.
Your C function returns void
, not a CONTEXT *
. So, your code is just casting random uninitialized memory to a CONTEXT *
, and then trying to deref it.
On top of that, even if it did return a CONTEXT
object by reference, tmp[1]
would try to deref the memory after that object, so it would still be garbage.
When you try to interpret random memory as a double, you're going to get segfaults or values like 2.15880221124e-314
if you're lucky—if you're unlucky, you'll get correct-looking but still random values like 0.0
.
Meanwhile, since your C function modifies its arguments in place, you don't need to do anything fancy here. Just use the variables you passed in.
So:
def runner():
kk = (1,2,3)
n = 3
mm = n + 1
wts = (c_double * n)(1, 1, 1)
res = (c_double * mm)(0)
kks = (c_int * len(kk))(*kk)
n = c_int(n)
ex = c_double(0)
libc.cprogram.restype = None
libc.cprogram(wts, res, kks, n, ex)
print wts[1]
This works, and prints out 1.0
.
And if your C function did return an array of CONTEXT
structs matching your ctypes
declaration, this would all work fine. For example:
#include <stdlib.h>
typedef struct {
double *wts;
double res;
int kks;
int n;
double ex;
} CONTEXT;
CONTEXT *cprogram(double *wts, double *res, int *kks, int n, double *ex) {
int m;
m=n+1;
res[0]=1.0;
kks[0]=1.0;
CONTEXT *contexts = malloc(sizeof(CONTEXT) * 4);
for (int i=0; i!=4; ++i) {
double *wtsses = malloc(sizeof(double) * 5);
for (int j=0; j!=4; ++j) {
wtsses[j] = i + j;
}
CONTEXT context = { wtsses, *res, *kks, m, *ex };
contexts[i] = context;
}
return contexts;
}
Compile this, run your existing Python script with an added print tmp[1].wts[1]
, and it will print out 2.0
.
Finally, for your followup, first let's change the C code to take int *n
, where *n
is the input:
void cprogram(double *wts, double *res, int *kks, int *n, double *ex) {
int m;
m=*n+1;
res[0]=1.0;
kks[0]=1.0;}
Now, to call this from Python, you have to create a c_int
and pass a pointer to it. Since you're already doing the first half (which wasn't necessary before—just set argtypes
instead… but it's necessary now, which is convenient), it's just a one-line change:
libc.cprogram(wts, res, kks, pointer(n), ex)
This even works if n
is an in-out parameter.
But really, you don't need to see the pointer object at all from the Python; the only thing you're doing with it is creating it to pass to the function, then letting it get collected. To pass a C pointer without creating a ctypes pointer object (which again works even if n
is an in-out parameter), use byref
:
libc.cprogram(wts, res, kks, byref(n), ex)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With