1

I'm having trouble accessing elements in structures that are embedded within other structures using Python ctypes.

Here is the C:

struct GSList
{
    void* data;
    GSList* next;
};

struct att_range
{
    uint16_t start;
    uint16_t end;
};

struct gatt_primary
{
    char uuid[38];
    int changed;
    struct att_range* range;
};

typedef void( *python_callback_t )( GSList* services );

static python_callback_t pycb = NULL;

// pycb is set to a valid function pointer before the callback below is called.

static void primary_all_cb( GSList *services, uint8_t status )
{
    if( status == 0 ) {
        pycb( services );
    }
}

Here is the Python:

import ctypes

# CDLL is used to access a shared library that contains the C code above

class range(ctypes.Structure):
    _fields_ = [
        ('starthnd', ctypes.c_uint16),
        ('endhnd', ctypes.c_uint16)
    ]

class service(ctypes.Structure):
    _fields_ = [
        ('uuid', ctypes.c_char_p),
        ('changed', ctypes.c_int),
        ('range', ctypes.POINTER(range))
    ]

class servicelist(ctypes.Structure):
    pass
servicelist._fields_ = [
    ('data', ctypes.POINTER(service)),
    ('next', ctypes.POINTER(servicelist))
]

CB_FUNC_TYPE = ctypes.CFUNCTYPE(None, ctypes.POINTER(servicelist))
def cb(csvclist):
    svclist = ctypes.cast(csvclist, ctypes.POINTER(servicelist))
    data = ctypes.cast(svclist.contents.data, ctypes.POINTER(service))
    next = ctypes.cast(svclist.contents.next, ctypes.POINTER(servicelist))
    hndrange = ctypes.cast(data.contents.range, ctypes.POINTER(range))
    starthnd = ctypes.cast(hndrange.contents.starthnd, ctypes.c_uint16)
    endhnd = ctypes.cast(hndrange.contents.endhnd.contents,  ctypes.c_uint16)
    uuid = ctypes.cast(data.contents.uuid, ctypes.c_char_p)
    print('start: 0x%X, end: 0x%X, uuid: %s' % (starthnd, endhnd, uuid))
cb_func = CB_FUNC_TYPE(cb)

// gatt is the shared library. ble_start source is not shown here, but it sets the callback function pointer.
gatt.ble_start(cb_func)

I'm using Python 3.4 and gcc 4.7. This does not include the functions initially called to trigger the callback function or the Python code to access the shared library. I've verified that all the information does fill the structures in C and have been able to print the contents of the uuid in Python at one point. I get a segmentation fault when I try to access hndrange. I can print out the object reference of hndrange in Python, but if I try to access the elements, I get a segmentation fault.

What am I doing wrong?

Any help is appreciated.

1 Answer 1

1

Your service class doesn't match the gatt_primary structure. uuid should be an array of char, not a pointer:

class service(ctypes.Structure):
    _fields_ = [
        ('uuid', ctypes.c_char*38),
        ('changed', ctypes.c_int),
        ('range', ctypes.POINTER(range))
    ]

Besides it's not a good idea to use casts for returned fields of a struct. Having a look at the Structures and unions documentation, you'll find, that for the fundamental data types the associated python types are returned. All other derived types are returned as is.

So for example ctypes.cast(hndrange.contents.starthnd, ctypes.c_uint16) will try to cast a python int type to ctypes.c_uint16.

Sign up to request clarification or add additional context in comments.

6 Comments

Thanks. I did that, but I still get a seg fault. It occurs when I try to access one of the structure elements from range, i.e. right at the line: starthnd = ctypes.cast(hndrange.contents.starthnd, ctypes.c_uint16) I can see the address to that element matches the address in the C code. Apparently, dereferencing it causes the seg fault.
I added some information about the casts you're doing. I hope that helps.
I believe I know what's happening, but please correct me if I'm wrong. I've been attempting to access memory from another process. In Linux (and most likely most OSs), shared memory between processes is not normally allowed (for good reason). So, I believe it's actually the OS that's issuing the seg fault and has nothing to do with the Python or C implementation (besides trying to share memory across processes).
@JohnasCukier, the first 4 cast operations are unnecessary. The next two (starthnd and endhnd) are not only unnecessary but wrong since you can only cast to a pointer type. The final cast of uuid is unnecessary, as well as motivated by a mistaken use of a char * pointer (4 or 8 bytes) instead of using a 38-byte char array. If you use the c_char * 38 array type, as suggested, you'll find that accessing a c_char array field returns a Python string. There's no need to cast anything here.
@JohnasCukier, it's also a bad idea to shadow the name of a common built-in function such as range.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.