ctypes Structure strange behaviour?

I'm defining my structure in python according to the docs (or so I think) but when calling the c function the values don't match:

Python code:

print(f"SchemaID:{appHandResp.supportedAppProtocolRes.SchemaID}, SchemaID_isUsed:{appHandResp.supportedAppProtocolRes.SchemaID_isUsed}")
errn = openv2g.encode_appHandExiDocument(byref(oStream), byref(appHandResp))

C code:

int encode_appHandExiDocument(bitstream_t* stream, struct appHandEXIDocument* exiDoc) {
    if (exiDoc->supportedAppProtocolRes_isUsed)
        printf("SchemaID:%d, SchemaID_isUsed:%d\n", exiDoc->supportedAppProtocolRes.SchemaID, exiDoc->supportedAppProtocolRes.SchemaID_isUsed);

Output:

# in Python right before calling the C function
SchemaID:2, SchemaID_isUsed:1
# in the C function
SchemaID:1, SchemaID_isUsed:0

The structure definitons:

# Python3.8

appHandresponseCodeType = ctypes.c_uint8
appHandresponseCodeType_OK_SuccessfulNegotiation,\
appHandresponseCodeType_OK_SuccessfulNegotiationWithMinorDeviation,\
appHandresponseCodeType_Failed_NoNegotiation = map(appHandresponseCodeType, range(3))

class appHandAnonType_supportedAppProtocolRes(ctypes.Structure):
    _fields_ = [
        ("ResponseCode", appHandresponseCodeType),
        ("SchemaID", ctypes.c_uint8),
        ("SchemaID_isUsed", ctypes.c_uint, 1),
    ]

.

// C

typedef enum {
    appHandresponseCodeType_OK_SuccessfulNegotiation = 0,
    appHandresponseCodeType_OK_SuccessfulNegotiationWithMinorDeviation = 1,
    appHandresponseCodeType_Failed_NoNegotiation = 2
} appHandresponseCodeType;

struct appHandAnonType_supportedAppProtocolRes {
    appHandresponseCodeType ResponseCode ;
    uint8_t SchemaID ;
    unsigned int SchemaID_isUsed:1;
};

Playing with the structure definition in python I was able to get the expected result:

appHandresponseCodeType = ctypes.c_uint
appHandresponseCodeType_OK_SuccessfulNegotiation,\
appHandresponseCodeType_OK_SuccessfulNegotiationWithMinorDeviation,\
appHandresponseCodeType_Failed_NoNegotiation = map(appHandresponseCodeType, range(3))


class appHandAnonType_supportedAppProtocolRes(ctypes.Structure):
    _fields_ = [
        ("ResponseCode", appHandresponseCodeType),
        ("SchemaID", ctypes.c_uint8),
        ("SchemaID_isUsed", ctypes.c_uint8),
    ]

Output:

# in Python right before calling the C function
SchemaID:2, SchemaID_isUsed:1
# in the C function
SchemaID:2, SchemaID_isUsed:1

I can't explain why this is the case, am I defining my structure in ctypes wrong?

2 answers

  • answered 2021-04-21 15:09 mevets

    Not 100%, but I think that this shows the underlying problem:

    #include <stdio.h>
      
    typedef struct A {
            char x;
            unsigned y:1;
    } A;
    
    
    int main() {
            printf("%zu\n", sizeof(A));
            return 0;
    }
    

    prints the value '4' using clang/macos/64bit, gcc/linux/64bit. It is because of the bitfield permitting the compiler to compact the structure.

  • answered 2021-04-21 18:18 Mark Tolonen

    This should work for you. A C enum is equivalent to a c_int. You don't need to cast the enum values. Just assign them to ResponseCode as needed. It is odd to use a bitfield for a single bit. That one bit will still occupy a full c_uint (typically 32-bits) anyway. There will also normally be three padding bytes between the c_uint8 and the next c_uint for alignment purposes, unless you also set _pack_ as well (must be before _fields_ if used). Check the C header for any packing directives.

    from ctypes import *
    
    appHandresponseCodeType_OK_SuccessfulNegotiation = 0
    appHandresponseCodeType_OK_SuccessfulNegotiationWithMinorDeviation = 1
    appHandresponseCodeType_Failed_NoNegotiation = 2
    
    class appHandAnonType_supportedAppProtocolRes(Structure):
        # _pack_ = 1   # if needed, uncomment.
        _fields_ = (('ResponseCode',c_int),
                    ('SchemaID',c_uint8),
                    ('SchemaID_isUsed',c_uint,1))