2

I am trying to access elements of a structure from ctypes. The structure is created in an init function in the C code and pointer to it is returned to Python. The problem I am having is that I get a segfault when trying to access elements of the returned structure. Here is the code:

The C code (which I've called ctypes_struct_test.c):

#include <stdio.h>
#include <stdbool.h>

typedef struct {
    bool flag;
} simple_structure;

simple_structure * init()
{
    static simple_structure test_struct = {.flag = true};
    if (test_struct.flag) {
        printf("flag is set in C\n");
    }
    return &test_struct;
}

The Python code (which I've called ctypes_struct_test.py):

#!/usr/bin/env python

import ctypes
import os

class SimpleStructure(ctypes.Structure):
    _fields_ = [('flag', ctypes.c_bool)]

class CtypesWrapperClass(object):

    def __init__(self):

        cwd = os.path.dirname(os.path.abspath(__file__))
        library_file = os.path.join(cwd,'libctypes_struct_test.so')

        self._c_ctypes_test = ctypes.CDLL(library_file)

        self._c_ctypes_test.init.restypes = ctypes.POINTER(SimpleStructure)
        self._c_ctypes_test.init.argtypes = []

        self.simple_structure = ctypes.cast(\
                self._c_ctypes_test.init(),\
                ctypes.POINTER(SimpleStructure))


a = CtypesWrapperClass()
print 'Python initialised fine'
print a.simple_structure.contents
print a.simple_structure.contents.flag

The C is compiled under Linux as follows:

gcc -o ctypes_struct_test.os -c --std=c99 -fPIC ctypes_struct_test.c
gcc -o libctypes_struct_test.so -shared ctypes_struct_test.os

On running python ctypes_struct_test.py, I get the following output:

flag is set in C
Python initialised fine
<__main__.SimpleStructure object at 0x166c680>
Segmentation fault

Is there some problem with what I am trying to do, or the way I am trying to do it?

Henry Gomersall
  • 8,434
  • 3
  • 31
  • 54

2 Answers2

4

To make this work, replace the wrong line

self._c_ctypes_test.init.restypes = ctypes.POINTER(SimpleStructure)

by the correct line

self._c_ctypes_test.init.restype = ctypes.POINTER(SimpleStructure)

Also consider to remove the pointless cast

self.simple_structure = ctypes.cast(
    self._c_ctypes_test.init(), ctypes.POINTER(SimpleStructure))

It's casting from ctypes.POINTER(SimpleStructure) to ctypes.POINTER(SimpleStructure)!

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • 1
    Wow, well that was a stupid mistake to make. I'm a little surprised that ctypes is so willing to let an invalid attribute pass. Thanks for that! – Henry Gomersall Aug 18 '11 at 20:02
1

You declare test_struct as a static local variable inside your init routine -- I'm suspicious of that at first blush. (Bear with me; my C is a bit rusty.)

Static local variables in C are supposed to persist across multiple calls to the same function, but their scope is the same as an automatic local variable -- and returning a pointer to an automatic local variable would certainly give you a segfault. Even if this would ordinarily work if you were calling that function from the same translation unit in C, the fact that you're calling it from Python (and that you're segfaulting as soon as you try to access the struct) is a red flag.

Instead of returning a pointer to a static variable, try using malloc() in your init routine and returning the pointer you get from that.

[[[ WARNING: this will result in a memory leak. ]]]

But that's OK; if the segfault goes away, you'll know that that was the problem. Then you can fix the memory leak by providing a second routine in your C code that calls free(), and making sure you invoke it from your python code when you're done with the struct.

  • No, initialising `test_struct` by calling malloc as: `test_struct = *(simple_structure*)malloc(sizeof(simple_structure));` still leads to a segfault (I also get an expected warning about returning the address of a local variable during compilation). – Henry Gomersall Aug 18 '11 at 15:21
  • Dexter, I think you're correct about this. See this answer: http://stackoverflow.com/questions/93039/where-are-static-variables-stored-in-c-c/93079#93079 @Henry, is test_struct still static? – Mitch Lindgren Aug 18 '11 at 15:22
  • well, it is now. For some unknown reason I removed the static prefix when adding the malloc code. Still segfaults. – Henry Gomersall Aug 18 '11 at 15:25
  • While probably not that best programming style, there is no technical reason why you should not return a pointer to a static variable. – Sven Marnach Aug 18 '11 at 16:42
  • Oops, now that you mention it I realize that I completely misunderstood the answer I linked to when I first read it. Thanks for the clarification, @Sven. – Mitch Lindgren Aug 18 '11 at 16:57
  • @Sven, what is the best style? – Henry Gomersall Aug 18 '11 at 20:05
  • @Henry: I know it's been solved, but I'm curious: when you recompiled with the malloc call, did you return test_struct or &test_struct? (I ask because the compiler shouldn't have returned such an error -- but if you *did* return &test_struct (a pointer to a pointer), that would be why the revised code segfaulted. – Dexter Taylor Aug 18 '11 at 20:18
  • Well, as written in my first comment I dereference the pointer, so test_struct is actually the structure, in which case it was correct to return `&test_struct`. – Henry Gomersall Aug 18 '11 at 20:28
  • @Henry: You're right, I stand corrected! I'm gonna code this up and try it out, because it's something I'll have to tackle eventually (I've got some external code I'll need to call from Python) and I'm still of the opinion that malloc/free is what you want in these situations, so a bit of hypothesis-testing is in order. – Dexter Taylor Aug 18 '11 at 20:48