吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1607|回复: 5
上一主题 下一主题
收起左侧

[会员申请] 申请会员ID:picklemorty【未报到,已注销】

[复制链接]
跳转到指定楼层
楼主
吾爱游客  发表于 2024-5-3 09:56 回帖奖励 |自己
  1. 申请会员ID:picklemorty
  2. 个人邮箱:2371905571@qq.com
  3. 原创技术文章

python如何快速高效的调用古老厂商SDK中的Dll文件

ctypesgen使用记录

前言

本文举例对象为大华提供的设备SDK(C语言端)
主要工具就是副标题的 ctypesgen 模块

快速高效是指ctypesgen可以批量处理C语言头文件并生成ctypes包装器
古老厂商是指厂商SDK中提供的demo里含有2004年的编写注释,且代码结构过于臃肿庞大。

Q: 厂商有提供python端的SDK吗?
A: 提供了。
Q: 那为什么还有自己 造轮子 呢?
A: 函数原型定义的太少了,不及C端的十分之一。简单说就是用户不能通过python端SDK进行 同步NTP服务器,获取设备时间,异步下载录像等重要功能。而dll本身是支持这些功能的。

先简单介绍下一般调用dll的流程

  1. 准备DLL文件
  2. 了解DLL函数签名,厂商一般提供的有SDK手册
  3. 定义函数原型,这一步最为繁杂,不过有ctypesgen这个神器。
  4. ctypes.WinDLL来加载DLL文件,同样可以由ctypesgen负责加载。
  5. 开始使用

然后看一下我们本次要处理的重量级头文件内容吧
我顺便把海康的sdk也给统计了一下

类别 大华网络SDK头文件 海康网络SDK头文件
结构体数量 7398 2661
函数数量 1357 627
枚举数量 1737 269
全局变量 3178 8131

手动编写ctypes包装器都有哪些问题?

如果我们没有工具批量生成ctypes包装器的话我们就需要下面这样手动操作

随便拿一个结构体和函数举例


// 网络访问规则 结构体
typedef struct tagNET_NETACCESS_RULE_INFO
{
    EM_NET_ACCESS_TYPE              emNetAccessType;        // 访问模式
    UINT                            nAllowAddrNum;          // 允许名单控制地址个数
    NET_ALLOW_ADDR_INFO             stuAllowAddrInfo[128];  // 允许名单控制地址列表
    BYTE                            bReserverd1[4];         // 字节 对齐
    UINT                            nBlockAddrNum;          // 禁止名单控制地址个数
    NET_BLOCK_ADDR_INFO             stuBlockAddrInfo[128];  //  禁止名单控制地址列表    
    BYTE                            bReserverd[1024];       // 保留字节
} NET_NETACCESS_RULE_INFO;

// 获取设备配置 函数
CLIENT_NET_API BOOL  CALL_METHOD CLIENT_GetDevConfig(LLONG lLoginID, DWORD dwCommand, LONG lChannel, LPVOID lpOutBuffer, DWORD dwOutBufferSize, LPDWORD lpBytesReturned,int waittime=500);

需要编写的ctypes包装为

# 网络访问规则 结构体
class NET_NETACCESS_RULE_INFO():
    __slot__ = [ 'emNetAccessType', 'nAllowAddrNum', 'stuAllowAddrInfo', 'bReserverd1', 'nBlockAddrNum', 'stuBlockAddrInfo', 'bReserverd', ]
    _fields_ = [
    ('emNetAccessType', EM_NET_ACCESS_TYPE),
    ('nAllowAddrNum', c_uint),
    ('stuAllowAddrInfo', NET_ALLOW_ADDR_INFO * int(128)),
    ('bReserverd1', c_ubyte * int(4)),
    ('nBlockAddrNum', c_uint),
    ('stuBlockAddrInfo', NET_BLOCK_ADDR_INFO * int(128)),
    ('bReserverd', c_ubyte * int(1024)),
]

# 获取设备配置 函数
CLIENT_GetDevConfig.argtypes = [c_longlong, c_uint, c_int, POINTER(None), c_uint, POINTER(c_uint), c_int]
CLIENT_GetDevConfig.restype = c_bool

Q: 大眼一看,貌似写个脚本就可以了呀?
A: 嗯,定睛一看再仔细一想,脚本需要考虑的情况包括但不限于

  1. 删除注释
    • 单行注释
    • 用了\换行符的单行注释
    • 多行注释
  2. 结构体
    • 枚举成员类型
    • 结构体成员类型
    • 成员变量类型转换为对应ctypes模块中的变量类型。比如win头文件的BYTE需要先转c的unsigned char然后再转换为ctypes的c_ubyte
    • 成员变量数组的大小
  3. 枚举同上
  4. 函数则是定义返回值类型和入参类型

上面这些都还好,唯独不可能简单解决的问题就是代码格式和各种纰漏。首先可以确定的是头文件本身可以编译并成功运行,我们可以用代码格式化工具整理一下,但依旧含有部分代码格式无法被统一处理。还有代码本身的错误,格式化工具是无法完美处理的。这 116259 行,占用空间 6.25MB 的头文件总是会不断的给你的小脚本一些你考虑不到的问题,需要特殊处理的情况太多太多了。

Q: 那为什么ctypesgen可以呢?
A: 因为ctypesgen使用LEX生成了一个词法分析器,可以从C语法上正确识别代码,考虑到的情况肯定比小脚本要多得多。

ctypesgen使用流程

删除注释

关于代码格式这一步依旧需要我们手动处理一下,不然ctypesgen容易报错。
第一个要处理的就是注释,虽然ctypesgen会自动忽略,不过注释会影响我们处理头文件格式时的繁杂度,容易眼花缭乱
如上所说,C的注释有三种,我写了三段正则用于匹配这些代码

    # 1.多行注释 /* */                      2.单行注释 // 用了换行符 \        3.单行注释 //
    # (/*(.|\r\n|\n)*?\*/)        |        (//(.*\\\n)+.*)        |         (//.*)

    # OrgText = open("headFile.h","r",encoding="utf8")
    pattern = r"(/\*(.|\r\n|\n)*?\*/)|(\/\/(.*\\\n)+.*)|(\/\/.*)"
    text1 = re.sub(pattern, "", OrgText, flags=re.M)
    pattern = "^\\s*(?=\r?$)\n"  # 删除所有空行。11万的头文件,删完注释而留下的空行也很多,需要处理。
    text2 = re.sub(pattern, "", text1, flags=re.M)
    # print(text2)
    # return text2

格式化代码

因为VS IDE里自带的代码格式化速度太慢(要刷新UI,语法分析,代码高亮等,我的电脑配置低容易卡死),又为配置格式化格式和复现方便,而单独用的 clang-format,我的版本是 clang-format version 16.0.0。

LLVM的发行页面:https://github.com/llvm/llvm-project/releases
下载完成后不要进行安装,解压这个exe文件,会看到有一个clang-format.exe。把这个可执行文件放到需要的地方就可以了。

首先生成基于llvm的格式化配置文件  

    clang-format -style=llvm -dump-config > .clang-format

生成的文件名称尽量不要改动哈,不然还要加参指定格式化配置文件,别掉坑里了。  

然后修改.clang-format文件中的数值

ColumnLimit:0             # 每行最大字符数
IndentWidth: 4            # 缩进宽度
PointerAlignment: Left  # 指针对齐方式
BreakBeforeBraces: Custom # 大括号换行方式
AfterStruct:     true     # 结构体大括号统一格式
AfterEnum:       true
AfterUnion:      true      # 就比如这个,clang-format无法控制处理嵌套数据类型中的 { 和 } 符号位置等
IndentPPDirectives: BeforeHash  # 预处理指令对齐

ctypesgen生成包装器

ctypesgen可以作为模块安装,然后可以使用命令行窗口操作。不过我是推荐你最好把ctypesgen的源代码clone下来,这样查错跑调试方便点。

指令解析方面用的是argparse.ArgumentParser模块,所以传入 -h 参数就能看到详细指令了。

    dllFile = "./Libs/win64/dhnetsdk.dll"   # 大华NetSDK主DLL,其他的DLL放在同一路径就行。
    inputPath = "readyToGenCtypesWrapperFile.h"
    outputPath = "generatedCtypesWrapperFile.py"

    arg_list = [inputPath, "-l", dllFile, "-o", outputPath] # 我这个只用到了-l指定LIBRARY文件
    print("指令列表为", arg_list)   # 检查路径的

    if log2file is TrgeneratedCtypesWrapperFilee:  # 希望将错误报告输出到文件中
        error_file = open('ctypesgen.log', 'w')
        sys.stderr = error_file
        from sub_ctypesgen.run import main as ctypesgenscript
        ctypesgenscript(arg_list)

附赠cl指令手册,预处理器节点https://learn.microsoft.com/zh-cn/cpp/build/reference/compiler-options-listed-by-category?view=msvc-170#preprocessor

不出意外的话你就能看到 244800 行的py文件了。这个就是ctypesgen生成的包装器。在上面使用了-l参数时,包装器会自动加载这个dll。部分代码如下

    # Begin libraries
    _libs["Libs/win64/dhnetsdk.dll"] = load_library("Libs/win64/dhnetsdk.dll")

    # 1 libraries
    # End libraries

包装器使用

最后只需要像导入模块一样导入整个文件就好了

    import UnifyNetSDK.dahua.dh_netsdk_wrapper as DH

    class DaHuaNetSDK(AbsNetSDK):
        netDll = DH  # netsdk.dll由ctypesgen包装器加载
        ...

    @classmethod
    def getNTP_CFG(cls, userID: int) -> UninfyNTPArg:
        # 获取设备NTP配置
        lChannel = 0    # 通道,部分指令下本参数无效
        dwOutBufferSize = sizeof(DH.DHDEV_NTP_CFG)  # 输出缓冲区大小
        lpOutBuffer = DH.DHDEV_NTP_CFG()            # 输出缓冲器
        lpBytesReturned = c_ulong()                 # 实际返回的字节大小
        waittime = 1000                             # 等待时长,单位ms
        result = cls.netDll.CLIENT_GetDevConfig(userID, DH.DH_DEV_NTP_CFG, lChannel, byref(lpOutBuffer), dwOutBufferSize, byref(lpBytesReturned), waittime)

最终效果就是上面这段代码了,相比于C,这段代码已经非常清晰了对吧。

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

沙发
Hmily 发表于 2024-5-6 12:26
I D:picklemorty
邮箱:2371905571@qq.com

申请通过,欢迎光临吾爱破解论坛,期待吾爱破解有你更加精彩,ID和密码自己通过邮件密码找回功能修改,请即时登陆并修改密码!
登陆后请在一周内在此帖报道,否则将删除ID信息。
3#
吾爱游客  发表于 2024-5-19 10:41 |自己
领导,我收不到密码重置的邮件呀。再给个机会吧
4#
吾爱游客  发表于 2024-5-19 12:50 |自己
不好意思啊,我间隔两天尝试获取密码重置邮件,却一直没收到,这俩天挺忙的,把这事给忘了。麻烦大哥再给次机会吧。顺便问下,为啥没收到密码重置的邮件呀?

点评

两次邮件都发成功了,你是不是搞错邮箱了,可以自行按论坛教程排查:https://www.52pojie.cn/thread-98585-1-1.html  详情 回复 发表于 2024-5-21 10:59
5#
Hmily 发表于 2024-5-21 10:59
十任带我嘎嘎飞 发表于 2024-5-19 12:50
不好意思啊,我间隔两天尝试获取密码重置邮件,却一直没收到,这俩天挺忙的,把这事给忘了。麻烦大哥再给次 ...

两次邮件都发成功了,你是不是搞错邮箱了,可以自行按论坛教程排查:https://www.52pojie.cn/thread-98585-1-1.html
6#
Hmily 发表于 2024-6-6 15:40
未报到,已注销。

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 04:09

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表