python中ctype的应用,C语言与python的完美映射,结构体与字符串的相互转换
1.简论–写在前面
在做通信的过程中,经常要用到解析协议,平时用到python来写一些小的脚本,所以想用python来做一个协议解析的脚本。
从功能上来说,python在处理字串是很方便的,你可以将字符串转换位16进制字符串后,对每一个BYTE进行操作,结合pack和unpack模块,只是比较麻烦。但是如果用的ctype模块就很方便。
具体讲ctype模块的应用的文章已经很多了,但是在协议解析这块,有明确讲解字符串转换位结构体,和结构体到字符串的转换的很少。这里专门讲解结构体到字符串的转换。所以专门记录下怎么用CTYPE模块,进行字符串与结构的转换。
1.ctype介绍
在做协议之前,绕不开的话题是先要了解ctype模块。了解内容包括,数据类型和函数。
这里贴出官方讲解类容: Ctype官方说明.
1.1 ctype数据类型
数据类型其实和C语言里面都是一一对应的,我们只需要记录这么一个表,用之前查一下表即可。
1.2 ctype常用函数
下面先引用我接下来的例子需要用的函数的官方的说明,有理可依。
ctypes.addressof(obj)
Returns the address of the memory buffer as integer. obj must be an instance of a ctypes type.
addressof函数,以整数形式返回一篇memory的地址。
ctypes.memmove(dst, src, count)
Same as the standard C memmove library function: copies count bytes from src to dst. dst and src must be integers or ctypes instances that can be converted to pointers.
memmove函数,与标准的C memmove库函数相同,将count个字节,从src负值到dst。
ctypes.sizeof(obj_or_type)
Returns the size in bytes of a ctypes type or instance memory buffer. Does the same as the C sizeof() function.
sizeof函数,与标准C sizeof()函数相同,都是返回ctype内省或实例缓存区大小,以字节位单位。
ctypes.string_at(address[, size])
This function returns the string starting at memory address address. If size is specified, it is used as size, otherwise the string is assumed to be zero-terminated.
string_at函数,返回从内存地址address开始的字符串。如果指定了size,则将其用作size,否则假定字符串以零结尾。
有了上面的函数,我们就可以用来解析了。
2.C语言的结构体在python中的应用
需要解析的协议结构如下:
该协议为私有协议,协议固定头为固定的“6767”, 协议固定尾为固定的“5050”。
2.1 C 语言中字符串和结构体的转换
对于C语言来说,只需要定义一个结构体,如果要解析字符串,只需要利用C语言的多态性,将字符串指向字符串的地址,直接访问结构体既可以获取协议内容。同样的,如果需要输出这个协议的字符串,只需要申请一片内存,然后结构体指向这片内存,然后对结构体进行赋值。
由于协议里面含有可变长度,这里需要将上面的协议拆分位3个部分:协议头+协议体+协议尾。协议解析原理都是一样的,所以这里只讲解协议头的解析,也就是前面26个字节。
C语言中的结构体:
/** pro message header */
typedef struct _pro_msg_hdr
{
/** pro message header */
uint16_t pro_fix_head;
/** source mac */
uint8_t src_mac[6];
/** target mac */
uint8_t target_mac[6];
/** module id */
uint16_t module_id;
/** crc check sum */
uint16_t pro_crc;
/** msg id */
uint32_t msg_id;
/** message length */
uint32_t msg_len;
} pro_msg_hdr_t;
有了结构体,分为2种情况,一种是有字符串解析协议体,第二种是生成字符串。
- 解析协议
uint8_t buf[] = [0x67, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x55, 0x5c, 0x00,
0x09, 0xbb, 0x02, 0x00, 0x6d, 0xe0, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01,
0x00, 0x14, 0x00, 0x00, 0x50, 0x50];
/* 定义一个结构体, 并将结构体指针指向字符串地址 */
pro_msg_hdr_t * pro_hdr = (pro_msg_hdr_t *)buf;
/* 直接访问,可以得到协议解析 */
print("module id:%d, msg id:%d\n", pro_hdr->module_id, pro_hdr_msg_id);
/* module id:2, msg id:2 */
- 生成字符串
/* 先申请一片内存 */
uint32_t size = sizeof(pro_msg_hdr_t)
void *buf= malloc(size);
if (buf == NULL) {
print("alloc mem fail\n");
assert(0)
}
/* 定义一个结构体, 并将结构体指针指向字符串地址 */
pro_msg_hdr_t * pro_hdr = (pro_msg_hdr_t *)buf;
/* 对结构体赋值*/
pro_hdr->module_id = 2;
pro_hdr->module_id = 2;
/* 输出字符串 */
print("string is :");
for (uint32_t i = 0; i < size; i++) {
print("%02x, ", *(uint8_t *)(buf + i));
}
print("\n");
上面书写了C语言的利用结构体,解析字符串和生成字符串的方法。我想已经一目了然了。
下面讲解python怎么来实现C语言同样的功能。
2.2 python实现结构体和字符串的转换
python中是没有结构体的,所以如果要想实现如同C语言中的结构,可以用class来实现。这里直接给出来:
# 必须继承ctypes的Structure类
class ProMsgHdr(ctypes.Structure):
_pack_ = 1 # 1字节对齐
_fields_ = [('pro_fix_head', c_ushort)
('src_mac', c_ubyte * 6),
('dsr_mac', c_ubyte * 6),
('module_id', c_ushort),
('cli_crc', c_ushort),
('msg_id', c_uint),
('msg_len', c_uint)
]
注意:整个结构必须如同上面的格式一致!如果你不需要1字节对齐就去掉,或者几字节对齐就写几字节。
- 用python来解析字符串
buf = b"676700000000000048555c0009bb02006de0020000000500000001001400005050"
# 赋值
msg_hdr = ProMsgHdr()
# 得到类的长度
msghdrlen = sizeof(ProMsgHdr)
# 将16进制转换为字符串
str_bytes = binascii.unhexlify(buf)
# 将msg_hdr地址指向str_bytes
ctypes.memmove(ctypes.addressof(msg_hdr), str_bytes, msghdrlen)
print("module id:%d, msg id:%d" % (msg_hdr.module_id, msg_hdr.msg_id))
# module id:2, msg id:2
- 用python来生成字符串
# 得到类的实例
msg_hdr = ProMsgHdr()
# 赋值
msg_hdr.module_id = 2
msg_hdr.msg_id = 2
# 得到字符串
hdr_byte = ctypes.string_at(ctypes.addressof(msg_hdr), ctypes.sizeof(msg_hdr))
python用上面的方法就很方便的就可以解析字符串和生成字符串,不用再像以前,每次用pack和unpack来更具长度取字符。