I have a C struct like this:
typedef struct {
my_domain_type_t type;
my_domain_union_t u;
my_domain_int32_list_t list;
} my_domain_value_t;
typedef struct {
int32_t min;
int32_t max;
} my_domain_int32_range_t;
C function that I’d like to call from ctypes:
int64_t myData::get_min(const my_domain_value_t &value)
{
int min_value = 0;
my_domain_type_t dt = value.type;
if (dt == 0)
{
my_domain_int32_range_t range = value.u.range;
min_value = range.min;
printf("min_value=%d\n", min_value);
}
return min_value;
}
ctypes defintions:
class myDomainInt32RangeT(Structure):
_fields_ = [ ('min', c_long),
('max', c_long) ]
class myDomainUnionT(Union):
_fields_ = [ ('range', myDomainInt32RangeT ) ]
class myDomainValueT(Structure):
_fields_ = [ ('type', c_int ),
('u', myDomainUnionT ),
('list', myDomainInt32ListT ) ]
class myData(object):
def __init__(self):
self.object = myDataX.myData_new()
def get_min(self, arg1):
myDataX.myData_get_min.argtypes = [ c_void_p, POINTER(myDomainValueT) ]
myDataX.myData_get_min.restype = c_longlong
return myDataX.mydata_get_min(self.object, arg1)
Python code:
mydataY = myData()
domainRange = myDomainInt32RangeT()
domainRange.min = c_long(3)
domainRange.max = c_long(5)
domainUnion = myDomainUnionT()
domainUnion.range = domainRange
domainValue = myDomainValueT()
domainValue.type = 0
domainValue.u = domainUnion
domainValue.list = myDomainInt32ListT()
b = mydataY.get_min( byref(domainValue) )
print(b)
I’m expecting a value of 3 for min_value but I keep getting 0. The C code also prints 0. Looks like the union doesn’t get setup/transferred correctly.
What did I do wrong ?.
TIA,
John
If you want the myDomainInt32RangeT Structure to be used interchangeably with the my_domain_int32_range_t struct, they have to define compatible types. But they don’t:
This defines a pair of int32_t values.
This defines a pair of whatever long values.
The problem is that int32_t and long aren’t the same type. The fix is easy: change one to match the other (e.g., use c_int32 instead of c_long).
If you want to understand why you get 0, that’s a bit more involving.
The rules for int32_t say it must be 32 bits. The rules for long say it must be at least 32 bits. On most 32-bit platforms, and on 64-bit Windows, it’s exactly 32 bits. On most other 64-bit platforms, however, it’s 64 bits. (See the discussion on LLP64 vs. LP64 at http://en.wikipedia.org/wiki/64-bit for details.)
You’re probably on a 64-bit Intel Mac or Linux system, and using the default Python. Therefore, your long, and therefore ctypes.c_long, are 64-bit integers. So, look at the layout of a myDomainInt32RangeT:
The layout of a my_domain_int32_range_t, by contrast, is this:
So, if you construct a myDomainInt32RangeT(3, 5), what you’re creating is:
When you try to interpret that as a my_domain_int32_range_t, it sees:
So your “min” value is 3, and your “max” values is 0.
You may also end up slicing objects and/or overwriting memory by passing around things that some code thinks is 128 bits while other code thinks it’s 64 bits. For example, if you create a my_domain_int32_range_t, pass it by reference into Python, then try to set its “max” value, you’re setting the 3rd and 4th 32 bits of an object that only has 2 of them, which means you’re actually overwriting the next object in memory.
The details above assume you’re on a little-endian system (like x86_64), as opposed to a big-endian system or something different (are there any VAX-endian LP64 platforms? with Python?). On a PowerMac G5 with a 64-bit big-endian PowerPC build of Python, you’ll get (0, 3) instead of (3, 0). But the basic idea is the same.