I want to do something like that:
from ctypes import *
class Packet(BigEndianStructure):
_fields_ = [("length", c_ushort),
("session", c_uint),
("command", c_ushort)]
class PacketString(BigEndianStructure):
_fields_ = [("length", c_ushort),
("value", c_char_p)]
class InitialPacket(Packet):
_fields_ = [("time", PacketString)]
However I getting error because c_char_p can only be in native byte order. But maybe there is some other way how I can make strings whose length is specified right before them. I just like how structs are easy to read from/write to socket. And how you define just _fields_ and then can use it like that:
initialPacket = InitialPacket()
initialPacket.command = 128
The question is: How do I create variable-length field in BigEndianStructure? Because Python wouldn't allow me to use c_char_p. Script simply wont run at all. This is error:
Traceback (most recent call last):
File "C:\PKOEmu\test.py", line 8, in <module>
class PacketString(BigEndianStructure):
File "C:\Python27\lib\ctypes\_endian.py", line 34, in __setattr__
fields.append((name, _other_endian(typ)) + rest)
File "C:\Python27\lib\ctypes\_endian.py", line 24, in _other_endian
raise TypeError("This type does not support other endian: %s" % typ)
TypeError: This type does not support other endian: <class 'ctypes.c_char_p'>
This type:
class PacketString(BigEndianStructure):
_fields_ = [("length", c_ushort),
("value", c_char_p)]
… doesn't do what you think it does, even ignoring the endianness issue. It's a struct that contains a ushort length, and then a pointer to the actual string data somewhere else in memory.
In other words, it's just like this C structure:
struct PacketString {
unsigned short length;
char *value;
};
What you're looking for is a length-prefixed string, where the string is directly inline inside the struct. For that, the C structure is:
struct PacketString {
unsigned short length;
char value[1];
};
This is called the "struct hack". This is not actually legal C, but it happens to work with every known C89 compiler, and most C99 and C++ compilers. See the C FAQ entry fro details.
So, can you do the same thing in ctypes
? Well, yes, but it's not as useful:
class PacketString(BigEndianStructure):
_fields_ = [("length", c_ushort),
("value", c_char * 0)]
This can get complicated; see Variable-sized data types in the docs for details. In particular, you can't call resize
on p.value
; you need to calculate how much to resize p
itself, and then change the type of p._fields_[1]
to the have the right type, and then…
Well, this is why the docs say:
Another way to use variable-sized data types with ctypes is to use the dynamic nature of Python, and (re-)define the data type after the required size is already known, on a case by case basis.
In other words:
class LocalPacketString(BigEndianStructure):
_fields_ = [("length", c_ushort),
("value", c_char * length)]
ps = LocalPacketString(length, buff)
However, you may notice that this isn't really saving you much work over just keeping the types separate.
In summary, the struct hack isn't even valid C, and it doesn't map very well to ctypes
. A ctypes.Structure
is not a good way to represent a variable-length length-prefixed string.