7

Given a simple C file:

#include <stdio.h>

typedef struct point {
    int x;
    int y;
} POINT; 

POINT get_point() 
{
    POINT p = {1, 2};
    return p;
}

And I have a simple python file:

from ctypes import *
import os

lib_name = '/testlib.so' 
test_lib = CDLL(os.getcwd() + lib_name)

class POINT(Structure):
    _fields_ = [('x', c_int),
                ('y', c_int)]

# Sets p1 to the integer 1
p1 = test_lib.get_point()
# Sets p2 to the struct POINT with values {1, 0}
p2 = POINT(test_lib.get_point())

How can I set my returned value to the struct POINT with values {1, 2}?

tots_o_tater
  • 529
  • 1
  • 5
  • 12
  • Any reason you use a tag for a `typedef`ed `struct`? Pretty useless. If you don't understand this comment, you have to learn about `struct`s and `typedef` before commencing. – too honest for this site Jul 29 '16 at 14:47
  • @Olaf I've always used `typedef` for `struct`s in C for aliases. Is there something inherently wrong with that? – tots_o_tater Jul 29 '16 at 14:51
  • I did not comment on using `typedef`. But your comment proves my assumption correct. Don't just follow a pattern, but learn **why** you have to/should write something a specific way! – too honest for this site Jul 29 '16 at 14:52
  • @Olaf, with his specific style ;), is telling you that `typedef struct point { int x; int y; } POINT;` can simply be `typedef struct { int x; int y; } POINT;` – LPs Jul 29 '16 at 14:56
  • Isn't that mostly preference? Based off [this question](http://stackoverflow.com/questions/1110944/c-typedef-and-struct-question), they appear to be pretty similar. I'm not referencing `point` in my `struct`, but is there harm in writing it still? – tots_o_tater Jul 29 '16 at 15:01
  • @LPs: "specific style" - maybe. I'm just getting more and more tired about ppl who put more effort into asking others/refusing to learn for themselves than it would take to just learn. Maybe I'm special - does thinking hurt most people? I get that impression more and more. – too honest for this site Jul 29 '16 at 15:10
  • 3
    @Olaf I agree, but the whole `typedef` `struct` thing is not really related to the question and just boils down to bikeshedding. – Daniel Margosian Jul 29 '16 at 15:12
  • @DanielMargosian: This site is to improve coding quality. I did comment on a matter of code-quality or even a potential error (OP could very well have forgotten to add something. The reply to my comment also shows there are things not understood. From my (quite long time) experience that means there are likely more (and more serious) missunderstandings/problems possibly more related to the current issue. Like just following a pattern without having it understood. – too honest for this site Jul 29 '16 at 15:19
  • @Olaf The purpose of the site is to ask and answer questions. – Daniel Margosian Jul 29 '16 at 15:20
  • 1
    @Olaf I understand and probably I agree with last part, but I think is the problem of such a "service" like SO, where people are up-voted even if they answer to "code for me" questions. So askers become used to ask-before-think because of someone will give them the work done. – LPs Jul 29 '16 at 15:20
  • @LPs that is good point, should we reward people who answer bad questions? – Daniel Margosian Jul 29 '16 at 15:21
  • @DanielMargosian No, that is the Parents or teachers job. We ask for a minimum level of quality.... – LPs Jul 29 '16 at 15:21
  • @LPs What if someone is trying to teach themselves? – Daniel Margosian Jul 29 '16 at 15:23
  • 3
    @DanielMargosian This is not the right place to continue this chat, meta exists for this reason too. BTW, last comment, people that want to teach themselves should ask good question respecting [MCVE](http://stackoverflow.com/help/mcve) and SO people will be pleased to help them all. – LPs Jul 29 '16 at 15:30
  • @LPs Did I disrespect MCVE? – tots_o_tater Jul 29 '16 at 15:33
  • @tots_o_tater That comment is not strictly liked to your post, as I wrote this is not the right place for those kind of comments. About your question I'm not a python expert, but I'd add what is the actual output of the posted code. – LPs Jul 29 '16 at 15:39
  • 2
    `test_lib.get_point.restype = POINT` is missing. Some hopefully useful links [answer](http://stackoverflow.com/questions/5033162/is-there-a-reason-why-pythons-ctypes-cdll-cannot-automatically-generate-restype) [ctypesgen](https://github.com/davidjamesca/ctypesgen) – J.J. Hakala Jul 29 '16 at 16:13
  • @J.J.Hakala That's what I was looking for. Much appreciated. – tots_o_tater Jul 29 '16 at 16:54
  • @tots_o_tater: No, but a MCVE is not the only requirement. Don't just pick the raisins from comments, but read and undertand them completely (that is another point which makes me generally upset). Just take my comment as it was meant: learn, think first for yourself before asking others. Getting the correct balance is a virtue, though. – too honest for this site Jul 29 '16 at 17:26

2 Answers2

5

What you ar asking is nto the sole problem in your example. Just to answer just what you asked first: you have to annotate the C function return type, so that ctypes know it is a memory address - otherwise it is a (4 byte) integer by default (while in any 64 bit OS, pointers are 8 bytes long).

Then you can create Python side POINT structures by using the (hidden) "from_address" method in your POINT class:

test_lib.get_point.restype = c_void_p
p = POINT.from_address(test_lib.get_point())

print(p.x, p.y)

Before that works, however, you have a more fundamental issue on the C side: the POINT structure you declare on your example only exists while get_point is running, and is deallocated afterwards. The code above would lead to a segmentation fault.

Your C code have to allocate memory properly. And also, you should take provisions to deallocate data structures you allocate in C - otherwise you will have memory leaks as each call to the function in C allocates more memory and you don't free that. (Notice that this memory won't be freed by itself when the Python POINT object goes out of scope).

Your C code could be like this:

#include <stdlib.h>
#include <stdio.h>

typedef struct point {
    int x;
    int y;
} POINT;

POINT *get_point()
{
    POINT *p;
    POINT initial = {1, 2};
    p = malloc(sizeof(POINT));
    *p = initial;
    return p;
}

void free_point(POINT *p)
{
    free(p);
}

And with this Python part:

from ctypes import *
import os

lib_name = '/testlib.so'
test_lib = CDLL(os.getcwd() + lib_name)

class POINT(Structure):
    _fields_ = [('x', c_int),
                ('y', c_int)]

test_lib.get_point.restype = c_void_p

p1 = POINT.from_address( test_lib.get_point())
print (p1.x, p1.y)

test_lib.free_point(byref(p1))
del p1

everything should just work.

(just so that this answer is a complete ctypes example, I will add the GCC commands to build the testlib file:

gcc -c -fPIC test.c -o test.o
gcc test.o -shared -o testlib.so

)

jsbueno
  • 99,910
  • 10
  • 151
  • 209
  • 4
    Thank you for the answer! As mentioned by @J.J.Hakala in my original post, doing `test_lib.get_point.restype = POINT` is also valid if I want to return the `struct` by value. This is a great reference for returning struct pointers, though. – tots_o_tater Jul 29 '16 at 16:52
  • I am not sure returning a struct "by_value" can work at all - besides the syntax, of course. It has to exist somewhere in memory. – jsbueno Jul 29 '16 at 16:55
  • 2
    A struct can be a return type in C although I think it is usually avoided as a matter of style. http://stackoverflow.com/questions/9653072/return-a-struct-from-a-function-in-c – J.J. Hakala Jul 29 '16 at 17:26
  • 1
    Wow, there are 64 **byte** OSses? Is that not a bit too much considering there are not even full-grown 128 **bit** CPUs? ;-)) – too honest for this site Jul 29 '16 at 17:28
  • 64bit obviously. But I think the oribinal PS2 did sport a full grown 128bit CPU. – jsbueno Jul 29 '16 at 17:29
0

Python 3 docs say:

By default functions are assumed to return the C int type. Other return types can be specified by setting the restype attribute of the function object.

So you just need to set the restype of the function to the type you need, like this:

test_lib.get_point.restype = POINT
p1 = test_lib.get_point()

Python 2 docs say the same thing, but I haven't tested it.

Adham Zahran
  • 1,973
  • 2
  • 18
  • 35
  • 1
    You link to the Python 3 docs. The question, however, was asked (almost seven years ago) about Python 2.7. – Friedrich Apr 05 '23 at 11:19
  • 1
    @Friedrich oh good point. the python2 docs seem to be saying the same thing, but I haven't tested it. I'll modify my answer. – Adham Zahran Apr 05 '23 at 12:18