0

I would like to put a malloc a function in C. I would then like to call this function from Python 3.10 via ctypes.DLL. I then would like to free it.

However, I get a segmentation fault. Here's my very simple C code:

#include <stdlib.h>

struct QueueItem {
    void *value;
    struct QueueItem *next;
};

struct Queue {
    struct QueueItem* head;
    struct QueueItem* tail;
};

struct Queue * new_queue(void * value) {
    struct Queue* queue = malloc(sizeof(struct Queue));

    struct Queue queue_ = { value, NULL };

    return queue;
}

void delete_queue(struct Queue* queue) {
    free(queue);
};

I'll compile this with gcc -fPIC -shared src/queue.c -o queue.so, and thenn on the python side:

import ctypes

queue = ctypes.CDLL("./queue.so")

q = ctypes.POINTER(queue.new_queue(1))
print(q)
print(type(q))

queue.delete_queue(q)

But running this will yield:

-1529189344
<class 'int'>
Segmentation fault

The question is, how do I malloc in C, pass the pointer through python, and then free it again in C?.

Primary Resources Consulted:

npengra317
  • 126
  • 2
  • 7
  • You need to declare the return type properly (as a `c_void_p` at the least, or a properly defined pointer type). The default return type for `ctypes` imported functions is `int`, which, on a 64 bit system, is half the required width. You can't just go calling the function without defining the `restype` attribute on it properly and casting the result. The `delete_queue` call might not *need* `argtypes` defined (if you pass it a `c_void_p`, I believe it'll assume that's what it's supposed to pass), but you should still do it for safety. – ShadowRanger Nov 15 '22 at 03:04
  • I didn't even notice that the int was half the size I wanted. Ah, got it, if you (or someone) wants to make an answer, I'll accept it. Thank you. – npengra317 Nov 15 '22 at 03:07
  • Duplicate of [\[SO\]: C function called from Python via ctypes returns incorrect value (@CristiFati's answer)](https://stackoverflow.com/a/58611011/4788546)? – CristiFati Nov 16 '22 at 06:39

1 Answers1

0

If you don't define the restype and argtypes for a function, the restype is assumed to be a C int (c_int), and the argument types are guessed at based on what you pass. The problem here is that the implicit restype of C int is (on a 64 bit system) half the width of a pointer, so the value returned by new_queue is only half of a pointer (which is completely useless).

For safety, and error-checking, you should define both before calling a function, especially the restype which can't be inferred.

So for your code, you might do:

import ctypes

queue = ctypes.CDLL("./queue.so")

queue.new_queue.argtypes = (c_void_p,)
queue.new_queue.restype = c_void_p

queue.delete_queue.argtypes = (c_void_p,)
queue.delete_queue.restype = None

q = queue.new_queue(1)
print(q)
print(type(q))

queue.delete_queue(q)

Note that passing along 1 as the argument to new_queue is almost certainly incorrect, but I suspect it will work here since none of the code will actually try to use it.

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • I want to use it eventually, what should I use instead? I want to pass in arbitrary objects eventually. – npengra317 Nov 15 '22 at 03:42
  • 1
    @npengra317: Well, one, you need to describe what you want. You're passing an `int` to something that accepts a `void*`. That means, by default, Python passes along the numeric value as a pointer address (it's a pointer to memory address 0x1, which is almost always unusable for dereferencing). You can create actual `c_int`s and pass their `ctypes.pointer(c_int_instance)` or the like. You can change `new_queue`'s return type and `delete_queue`'s argtype to the `ctype.POINTER` of a custom `ctypes.Structure`. Or write Cython extensions, raw C extensions, or use interface generators like SWIG. – ShadowRanger Nov 15 '22 at 11:28