ctypes结构体(Structure)通用格式化输出打印等

背景

在使用pythonc\c++混合编程的时候,我们通常使用pythonctypes方案,这时在编码过程中就免不了要与c的结构体struct打交道。
在编码过程,尤其是调试中,我们有时需要便捷地查看或者日志打印结构体信息,如果我们按c的方式一个个结构体成员手工编码输出,这是比较复杂且费力的,因此有必要实现一种通用的结构体格式化输出的功能,便于查阅结构体对象信息。
本文就是基于上述需求实现的一种方案:定义一个基类,实现对结构体成员变量的格式化输出,其中dump_dict将结构体转换为字典,__str__实现对象的字符串类型转换,show使用``pprint`输出字典数据结果。

实现及代码验证

1.格式化输出结构体信息

定义基类

# 自定义基类结构体
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~~~

2.内存地址操作,字符串内存块输出

直接将结构体转为内存卡地址,并将其数据输出,实现代码为:

# 字符串信息
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'

3.内存地址操作,二进制格式化输出

对指定内存块的数据,以统一的数据数据类型解析输出,一般用于输出纯字符串或其他简单类型的数组,实现代码为:

# 按表格输出二进制数据
# 参数 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()

你可能感兴趣的:(c++,工具,Python)