在使用python
和c\c++
混合编程的时候,我们通常使用python
的ctypes
方案,这时在编码过程中就免不了要与c
的结构体struct
打交道。
在编码过程,尤其是调试中,我们有时需要便捷地查看或者日志打印结构体信息,如果我们按c
的方式一个个结构体成员手工编码输出,这是比较复杂且费力的,因此有必要实现一种通用的结构体格式化输出
的功能,便于查阅结构体对象信息。
本文就是基于上述需求实现的一种方案:定义一个基类,实现对结构体成员变量的格式化输出,其中dump_dict
将结构体转换为字典,__str__
实现对象的字符串类型转换,show
使用``pprint`输出字典数据结果。
定义基类
# 自定义基类结构体
class myStructure(Structure):
pass
然后在类myStructure
实现我们的需求,其中dump_dict
为:
# 结构体转字典
def dump_dict(self):
info = {}
# 通过_fields_获取每一个字段
# 检查每个字段的类型,根据不同类型分别处理
# 支持递归迭代
for k, v in self._fields_:
av = getattr(self, k)
if type(v) == type(Structure):
av = av.dump_dict()
elif type(v) == type(Array):
av = cast(av, c_char_p).value.decode()
else:
pass
info[k] = av
return info
对结构体的字段集合_fields_
遍历,使用getattr
获取对应成员变量的属性值信息,然后根据该信息格式化输出,我们这里格式化到字典类型。
其中对结构体类型
和数组
类型的需要特殊处理,结构体
需要递归调用,数组
则按字符串输出(因在我的实际使用中,数组均为字符串)。
其中__str__
和show
分别基于函数dump_dict
再次封装成我们所需要的功能即可。
# 字符串转换
def __str__(self):
info = self.dump_dict()
return repr(info)
# 打印信息
def show(self):
from pprint import pprint
pprint(self.dump_dict())
接下来,写代码测试实现效果
# 地址资料
class ST_ADDR(myStructure):
_fields_ = [
('Addr', c_char*32),
('Port', c_int),
]
# ST_ADDR 测试代码
addr = ST_ADDR()
# 显示:{'Addr': '', 'Port': 0}
addr.show()
# 显示:{'Addr': '', 'Port': 0}
print(addr)
# 赋值
addr.Addr = b'127.0.0.1'
addr.Port = 8080
# 显示:{'Addr': '127.0.0.1', 'Port': 8080}
addr.show()
测试发现效果还不错,输出结果较为友好,详见代码注释。
嵌套结构体测试代码:
# 地址资料
class ST_ADDR(myStructure):
_fields_ = [
('Addr', c_char*32),
('Port', c_int),
]
# HOOK
class ST_HOOK(myStructure):
_fields_ = [
('UserID', c_uint), # 请求者的ID号码
('HostName', c_char * 64), # 主机名
('QueueName', c_char * 64), # 队列名
('QueueType', c_uint), # 队列类型
]
# ST_PACKHEAD:包头结构
class ST_PACKHEAD(myStructure):
_fields_ = [
('RequestType', c_uint), # 请求号码(交易编码)
('addr', ST_ADDR), # 请求着的地址(6个子节)
('hook', ST_HOOK), # 请求者的私有数据(通讯平台内部使用的)
('userdata', c_uint), # 请求者用户数据(应答包会原样返回)
('ParmBits',c_ubyte*64), # 包体参数描述
]
def main():
# ST_ADDR 测试代码
addr = ST_ADDR()
# 显示:{'Addr': '', 'Port': 0}
addr.show()
# 显示:{'Addr': '', 'Port': 0}
print(addr)
# 赋值
addr.Addr = b'127.0.0.1'
addr.Port = 8080
# 显示:{'Addr': '127.0.0.1', 'Port': 8080}
addr.show()
# ST_HOOK 测试代码
hook = ST_HOOK()
# 显示:{'HostName': '', 'QueueName': '', 'QueueType': 0, 'UserID': 0}
hook.show()
# 显示:{'UserID': 0, 'HostName': '', 'QueueName': '', 'QueueType': 0}
print(hook)
# 赋值
hook.UserID = 578
hook.HostName = b'127.0.0.1'
hook.QueueName = b'q_req'
hook.QueueType = 5205
# 显示:{'UserID': 578, 'HostName': '127.0.0.1', 'QueueName': 'q_req', 'QueueType': 5205}
print(hook)
# ST_PACKHEAD 复杂结构体,嵌套结构体
head = ST_PACKHEAD()
# 显示内容如下:
# 'ParmBits': '',
# 'RequestType': 0,
# 'addr': {'Addr': '', 'Port': 0},
# 'hook': {'HostName': '', 'QueueName': '', 'QueueType': 0, 'UserID': 0},
# 'userdata': 0}
head.show()
# 显示:{'RequestType': 0, 'addr': {'Addr': '', 'Port': 0}, 'hook': {'UserID': 0, 'HostName': '', 'QueueName': '', 'QueueType': 0}, 'userdata': 0, 'ParmBits': ''}
print(head)
# 赋值
head.RequestType = 404456
head.userdata = 1234
head.addr.Addr = b'127.0.0.1'
head.hook = hook
# 显示内容如下:
# {'ParmBits': '',
# 'RequestType': 404456,
# 'addr': {'Addr': '127.0.0.1', 'Port': 0},
# 'hook': {'HostName': '127.0.0.1',
# 'QueueName': 'q_req',
# 'QueueType': 5205,
# 'UserID': 578},
# 'userdata': 1234}
head.show()
发现嵌套后的复杂结构体依旧可以正常输出,|゚ρ゚ )ノ哦哟!
work over~~~
直接将结构体转为内存卡地址,并将其数据输出,实现代码为:
# 字符串信息
def string(self):
return string_at(addressof(self), sizeof(self))
测试代码:
# ST_ADDR 测试代码
addr = ST_ADDR()
addr.Addr = b'127.0.0.1'
addr.Port = 8080
print(addr.string())
输出结果为:
b'127.0.0.1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x90\x1f\x00\x00'
对指定内存块的数据,以统一的数据数据类型解析输出,一般用于输出纯字符串或其他简单类型的数组,实现代码为:
# 按表格输出二进制数据
# 参数 ctp : 每一列的数据类型,其类型为ctypes数据类型,如c_char, c_int
# 参数 row_num : 每一行的列数
# 参数 full : 是否完整显示数据,如果是且结构体大小不是ctp大小的整数倍,则将ctp强制转为c_char类型
def show_meminfo(self, ctp=c_char, row_num = 8, full = False):
import struct
# # 长度不够时,取char类型
if full and sizeof(self)%sizeof(ctp):
ctp = c_char
block = int(sizeof(self)/sizeof(ctp))
addr = string_at(addressof(self), sizeof(self))
for i in range(block):
v = struct.unpack(ctp._type_, addr[i*sizeof(ctp):(i+1)*sizeof(ctp)])
pend = '\n' if (i+1)%row_num==0 else ' '
print(repr(v[0]), end=pend)
print()
测试代码为:
class ST_DATA(myStructure):
_fields_ = [
('day1', c_int*3),
('day2', c_int * 4),
('day3', c_int * 5),
('day4', c_int * 6),
('day5', c_int * 7),
]
def main():
# ST_DATA 测试代码
data = ST_DATA()
data.day1 = (c_int*3)(11, 12, 13)
data.day2 = (c_int * 4)(21, 22, 23, 24)
data.day3 = (c_int * 5)(31, 32, 33, 34, 35)
data.day4 = (c_int * 6)(41, 42, 43, 44, 45, 46)
data.day5 = (c_int * 7)(51, 52, 53, 54, 55, 56, 57)
data.show_meminfo(c_int, 4)
其输出结果为:
11 12 13 21
22 23 24 31
32 33 34 35
41 42 43 44
45 46 51 52
53 54 55 56
57
可以发现输出结果中数据按c_int
单元解析,并对齐输出
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from ctypes import *
# 自定义基类结构体
class myStructure(Structure):
# 结构体转字典
def dump_dict(self):
info = {}
# 通过_fields_获取每一个字段
# 检查每个字段的类型,根据不同类型分别处理
# 支持递归迭代
for k, v in self._fields_:
av = getattr(self, k)
if type(v) == type(Structure):
av = av.dump_dict()
elif type(v) == type(Array):
av = cast(av, c_char_p).value.decode()
else:
pass
info[k] = av
return info
# 字符串转换
def __str__(self):
info = self.dump_dict()
return repr(info)
# 打印信息
def show(self):
from pprint import pprint
pprint(self.dump_dict())
# 地址资料
class ST_ADDR(myStructure):
_fields_ = [
('Addr', c_char*32),
('Port', c_int),
]
# HOOK
class ST_HOOK(myStructure):
_fields_ = [
('UserID', c_uint), # 请求者的ID号码
('HostName', c_char * 64), # 主机名
('QueueName', c_char * 64), # 队列名
('QueueType', c_uint), # 队列类型
]
# ST_PACKHEAD:包头结构
class ST_PACKHEAD(myStructure):
_fields_ = [
('RequestType', c_uint), # 请求号码(交易编码)
('addr', ST_ADDR), # 请求着的地址(6个子节)
('hook', ST_HOOK), # 请求者的私有数据(通讯平台内部使用的)
('userdata', c_uint), # 请求者用户数据(应答包会原样返回)
('ParmBits',c_ubyte*64), # 包体参数描述
]
def main():
# ST_ADDR 测试代码
addr = ST_ADDR()
# 显示:{'Addr': '', 'Port': 0}
addr.show()
# 显示:{'Addr': '', 'Port': 0}
print(addr)
# 赋值
addr.Addr = b'127.0.0.1'
addr.Port = 8080
# 显示:{'Addr': '127.0.0.1', 'Port': 8080}
addr.show()
# ST_HOOK 测试代码
hook = ST_HOOK()
# 显示:{'HostName': '', 'QueueName': '', 'QueueType': 0, 'UserID': 0}
hook.show()
# 显示:{'UserID': 0, 'HostName': '', 'QueueName': '', 'QueueType': 0}
print(hook)
# 赋值
hook.UserID = 578
hook.HostName = b'127.0.0.1'
hook.QueueName = b'q_req'
hook.QueueType = 5205
# 显示:{'UserID': 578, 'HostName': '127.0.0.1', 'QueueName': 'q_req', 'QueueType': 5205}
print(hook)
# ST_PACKHEAD 复杂结构体,嵌套结构体
head = ST_PACKHEAD()
# 显示内容如下:
# 'ParmBits': '',
# 'RequestType': 0,
# 'addr': {'Addr': '', 'Port': 0},
# 'hook': {'HostName': '', 'QueueName': '', 'QueueType': 0, 'UserID': 0},
# 'userdata': 0}
head.show()
# 显示:{'RequestType': 0, 'addr': {'Addr': '', 'Port': 0}, 'hook': {'UserID': 0, 'HostName': '', 'QueueName': '', 'QueueType': 0}, 'userdata': 0, 'ParmBits': ''}
print(head)
# 赋值
head.RequestType = 404456
head.userdata = 1234
head.addr.Addr = b'127.0.0.1'
head.hook = hook
# 显示内容如下:
# {'ParmBits': '',
# 'RequestType': 404456,
# 'addr': {'Addr': '127.0.0.1', 'Port': 0},
# 'hook': {'HostName': '127.0.0.1',
# 'QueueName': 'q_req',
# 'QueueType': 5205,
# 'UserID': 578},
# 'userdata': 1234}
head.show()
if __name__ == '__main__':
main()