I have the following in C:
typedef struct {
short Whole;
unsigned short Frac;
} FirstStruct, FAR *pFirstStruct;
typedef struct {
char FirstArr[3];
FirstStruct SecondArr[3][3]
} SecStruct, FAR * pSecStruct;
I would like to do something similar in Python. Found this answer explaining how to use ctypes
for this purpose, but I am having problems with SecondArr[3][3]
. Here's the code in Python:
class FirstStruct(ctypes.Structure):
_pack_ = 2
_fields = [("Whole", ctypes.c_short),
("Fract", ctypes.c_ushort)]
class SecStruct(ctypes.Structure):
class _A(ctypes.Array):
_type_ = ctypes.c_char
_length_ = 3
class _B(ctypes.Array):
_type_ = FirstStruct
_length_ = 3
class _C(ctypes.Array):
_type_ = _B
_length_ = 3
_pack_ = 2
_fields = [("FirstArr", _A),
("SecondArr", _C)]
By doing that, Pylance complains that "_B" is not defined
, and I'm not completely sure it will work, nor if it is safe to mix two subclasses in that way to create a new C structure.
Is this the correct way of doing it even if Pylance complains about it, or is there any other way to convert the structure mentioned?
CodePudding user response:
According to [Python.Docs]: ctypes - Arrays (emphasis is mine):
Arrays are sequences, containing a fixed number of instances of the same type.
The recommended way to create array types is by multiplying a data type with a positive integer:
TenPointsArrayType = POINT * 10
Also (not sure whether it's a typo) for structures, the attribute name holding member data is _fields_ (also ends with an UnderScore and not _fields (as in your case)).
I'm not going to discuss the problems (NameErrors) in your code, as they are generated by a misunderstanding and (as a consequence) are a totally different matter.
Here's a small example. Note that I declared the array types gradually (for readability), but they can be also declared on the fly (only where they are needed).
Instances can then be manipulated like in C.
code00.py:
#!/usr/bin/env python
import ctypes as cts
import sys
class FirstStruct(cts.Structure):
_pack_ = 2
_fields_ = (
("Whole", cts.c_short),
("Fract", cts.c_ushort),
)
FirstStructPtr = cts.POINTER(FirstStruct)
CharArr3 = cts.c_char * 3
FirstStructArr3 = FirstStruct * 3
FirstStructArr3Arr3 = FirstStructArr3 * 3 # FirstStruct * 3 * 3 # Equivalent
class SecStruct(cts.Structure):
_pack_ = 2
_fields_ = (
("FirstArr", CharArr3),
("SecondArr", FirstStructArr3Arr3),
)
SecStructPtr = cts.POINTER(SecStruct)
def main(*argv):
ss = SecStruct()
print("FirstArr:", ss.FirstArr)
ss.FirstArr = b"XYZ"
print("FirstArr:", ss.FirstArr, ss.FirstArr[2])
print("SecondArr:", ss.SecondArr)
print("SecondArr[0]:", ss.SecondArr[0])
print("SecondArr[0][0]:", ss.SecondArr[0][0])
print("SecondArr[0][0].Whole", ss.SecondArr[0][0].Whole)
ss.SecondArr[0][0].Whole = 0xFF
print("SecondArr[0][0].Whole", ss.SecondArr[0][0].Whole)
if __name__ == "__main__":
print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
64 if sys.maxsize > 0x100000000 else 32, sys.platform))
rc = main(*sys.argv[1:])
print("\nDone.\n")
sys.exit(rc)
Output:
[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q075351547]> "e:\Work\Dev\VEnvs\py_pc064_03.10_test0\Scripts\python.exe" ./code00.py Python 3.10.9 (tags/v3.10.9:1dd9be6, Dec 6 2022, 20:01:21) [MSC v.1934 64 bit (AMD64)] 064bit on win32 FirstArr: b'' FirstArr: b'XYZ' 90 SecondArr: <__main__.FirstStruct_Array_3_Array_3 object at 0x000001C128675F40> SecondArr[0]: <__main__.FirstStruct_Array_3 object at 0x000001C128BDC9C0> SecondArr[0][0]: <__main__.FirstStruct object at 0x000001C128BDC2C0> SecondArr[0][0].Whole 0 SecondArr[0][0].Whole 255 Done.
Might also worth reading:
[SO]: C function called from Python via ctypes returns incorrect value (@CristiFati's answer)
[SO]: C & Python: Pass and return a 2D double pointer array from python to c (@CristiFati's answer)
[SO]: Dynamically defining/updating ctypes structure in Python (@CristiFati's answer)
CodePudding user response:
Below shows multiplying a type by an integer creates an array, but note that C stores arrays in row-major order. To create the arrays with the correct memory layout, multiply by the column size first to create a single row array, then by the row size to complete the array. In your case of [3][3] it won't matter, but it will if the sizes are different, as I've intentionally shown.
A __repr__
function is also included with each struct to define how the structure can display itself and an instance is created from a consecutively-numbered byte buffer to illustrate the correct little-endian layout:
import ctypes as ct
ROWS = 3
COLS = 2
class FirstStruct(ct.Structure):
_fields_ = (("Whole", ct.c_short),
("Frac", ct.c_ushort))
def __repr__(self):
return f'FirstStruct(Whole={self.Whole:#x}, Frac={self.Frac:#x})'
class SecStruct(ct.Structure):
_fields_ = (("FirstArr", ct.c_ubyte * COLS), # equivalent to FirstArr[COLS]
("SecondArr", FirstStruct * COLS * ROWS)) # equivalent to FirstStruct[ROWS][COLS]
def __repr__(self):
arr = '[' '\n '.join([str(x[:]) for x in self.SecondArr]) ']'
return (f'SecStruct(FirstArr={self.FirstArr[:]},\n'
f" SecondArr={arr})")
s = SecStruct.from_buffer_copy(bytes(range(ct.sizeof(SecStruct))))
print(s)
Output:
SecStruct(FirstArr=[0, 1],
SecondArr=[[FirstStruct(Whole=0x302, Frac=0x504), FirstStruct(Whole=0x706, Frac=0x908)]
[FirstStruct(Whole=0xb0a, Frac=0xd0c), FirstStruct(Whole=0xf0e, Frac=0x1110)]
[FirstStruct(Whole=0x1312, Frac=0x1514), FirstStruct(Whole=0x1716, Frac=0x1918)]])