申请会员ID:picklemorty【未报到,已注销】
1. 申请会员ID:picklemorty2. 个人邮箱: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包装器的话我们就需要下面这样手动操作
随便拿一个结构体和函数举例
```C
// 网络访问规则 结构体
typedef struct tagNET_NETACCESS_RULE_INFO
{
EM_NET_ACCESS_TYPE emNetAccessType; // 访问模式
UINT nAllowAddrNum; // 允许名单控制地址个数
NET_ALLOW_ADDR_INFO stuAllowAddrInfo;// 允许名单控制地址列表
BYTE bReserverd1; // 字节 对齐
UINT nBlockAddrNum; // 禁止名单控制地址个数
NET_BLOCK_ADDR_INFO stuBlockAddrInfo;//禁止名单控制地址列表
BYTE bReserverd; // 保留字节
} NET_NETACCESS_RULE_INFO;
// 获取设备配置 函数
CLIENT_NET_API BOOLCALL_METHOD CLIENT_GetDevConfig(LLONG lLoginID, DWORD dwCommand, LONG lChannel, LPVOID lpOutBuffer, DWORD dwOutBufferSize, LPDWORD lpBytesReturned,int waittime=500);
```
需要编写的ctypes包装为
``` python
# 网络访问规则 结构体
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 =
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的注释有三种,我写了三段正则用于匹配这些代码
``` python
# 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 = # 我这个只用到了-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
### 包装器使用
最后只需要像导入模块一样导入整个文件就好了
``` python
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,这段代码已经非常清晰了对吧。 I D:picklemorty
邮箱:2371905571@qq.com
申请通过,欢迎光临吾爱破解论坛,期待吾爱破解有你更加精彩,ID和密码自己通过邮件密码找回功能修改,请即时登陆并修改密码!
登陆后请在一周内在此帖报道,否则将删除ID信息。 领导,我收不到密码重置的邮件呀:'(weeqw。再给个机会吧 不好意思啊,我间隔两天尝试获取密码重置邮件,却一直没收到,这俩天挺忙的,把这事给忘了。麻烦大哥再给次机会吧。顺便问下,为啥没收到密码重置的邮件呀? 十任带我嘎嘎飞 发表于 2024-5-19 12:50
不好意思啊,我间隔两天尝试获取密码重置邮件,却一直没收到,这俩天挺忙的,把这事给忘了。麻烦大哥再给次 ...
两次邮件都发成功了,你是不是搞错邮箱了,可以自行按论坛教程排查:https://www.52pojie.cn/thread-98585-1-1.html 未报到,已注销。
页:
[1]