Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Access data at memory address with ctypes

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.

like image 425
brebs Avatar asked Oct 21 '22 05:10

brebs


1 Answers

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)
like image 130
abarnert Avatar answered Oct 29 '22 17:10

abarnert