Reputation: 2040
I am trying to implement python-to-c binding via ctypes
for libnfc
.
I have a structure, here is a wrong variant:
class nfc_iso14443a_info(Structure):
_fields_ = [
('abtAtqa', c_uint8 * 2),
('btSak', c_uint8),
('szUidLen', c_uint8 * 8),
('abtUid', c_uint8 * 10),
...
At some moment during debug session it looks like that:
The problem here is that I want szUidLen
to be 64-bit unsigned integer equal to 7. More precisely, it must match size_t szUidLen;
from nfc-types.h
. So i tried an obvious variant and changed c_uint8 * 8
to c_size_t
and it doesn't work:
class nfc_iso14443a_info(Structure):
_fields_ = [
('abtAtqa', c_uint8 * 2),
('btSak', c_uint8),
('szUidLen', c_size_t),
('abtUid', c_uint8 * 10),
...
What am I missing here?
Upvotes: 1
Views: 3424
Reputation: 1027
from ctypes import *
c_size_t = c_unit64
and go ahead
You may need to specify ._pack_=1
too (if your compiler generates code that way) before defining _fields_
.
Update: There is ready made c_size_t
(and c_ssize_t
) type in ctypes
.
Note: (c_char * 8)
is not equal to c_int64
or c_long
because of possible alignment issues (c_char
fields are not aligned). ctypes.alignment(c_type)
may give you a hint on how c_type is aligned:
In [7]: c.alignment(c.c_char * 8), c.alignment(c.c_size_t)
Out[7]: (1, 8)
Upvotes: -2
Reputation: 365617
The issue here is that the C struct you're trying to map is packed, as (tersely) explained in the Structure/union alignment and byte order section of the docs:
By default, Structure and Union fields are aligned in the same way the C compiler does it. It is possible to override this behavior be specifying a
_pack_
class attribute in the subclass definition. This must be set to a positive integer and specifies the maximum alignment for the fields. This is what#pragma pack(n)
also does in MSVC.
That only makes sense if you already know about packing and alignment in C, but it's not that complicated.
By default, C structure elements are aligned to start on nice boundaries. For example, a 32-bit int following an 8-bit int doesn't run from bytes 1-4, it runs from bytes 4-7 (and bytes 1-3 are unused padding). So, ctypes
follows the same rules.
That means that, while szUidLen
runs from bytes 3-10 when it's defined as an array of 8-bit ints, it gets aligned to bytes 8-15 (or 4-11, depending on your compiler) when it's defined as a 64-bit int. You can see this by printing out nfc_iso14443a_info.szUidLen.offset
.
So, the first one gets the bytes 7, 0, 0, 0, 0, 0, 0, 0
, which is little-endian int64 for 7
, while the second one gets the bytes 0, 0, 0, a, b, c, d, e
, where abcde
are the first 5 bytes of the next field, which is little-endian int64 for some huge number (unless the next field happens to be 0).
Of course you don't want to just guess that this is the problem. If you based your Structure
on a struct
from a C header, this can only be true if the header or the compile flags specify some non-default packing, like the #pragma pack(1)
used by MSVC. If you based your Structure
on something like an RFC packet description, the alignment is not even according to C rules, but is defined somewhere in the documentation you're reading (although protocol RFCs almost always use 1-byte alignment).
Anyway, the docs don't explain the problem very well, but they explain the solution:
class nfc_iso14443a_info(Structure):
_pack_ = 1
_fields_ = [
('abtAtqa', c_uint8 * 2),
('btSak', c_uint8),
('szUidLen', c_size_t),
('abtUid', c_uint8 * 10),
...
Now szUidLen
runs from bytes 3-10, but it's interpreted as a 64-bit int instead of an array of 8-bit ints.
Upvotes: 3