wsdl2h 只做一件事:把 14 个 WSDL 的描述,揉成一个 onvif.h。
四个参数里 -t 最容易漏,而漏了不报错,只是生成的头文件里所有类型都不带前缀。
上一节把 typemap.dat 改好了,这一节是它的第一个消费者。
wsdl2h 是全章唯一「读 WSDL」的工具,
后面 soapcpp2 只读它产出的 onvif.h——所以 onvif.h 错了,后面全错。
一、四个参数各管什么
| 参数 | 作用 | 漏了会怎样 |
|---|---|---|
-o onvif.h |
输出文件名 | 默认写 typemap.h,找错文件 |
-c |
生成 C 结构体 | 生成 C++ 类,C 工程用不了 |
-s |
不使用 STL | 带 std::string,嵌入式编不过 |
-t typemap.dat |
指定映射表 | 类型全不带前缀,且不报错 |
第二、三行是「目标语言」的选择:-c 决定结构体还是类,
-s 决定字符串用 char* 还是 std::string。
两者要配套:用了 -c 但没加 -s,
生成的结构体里可能仍然出现 std::string(取决于 WSDL 里有没有用到字符串),
C 编译器当场报「未知类型 std::string」。
第四行是最危险的:漏了 -t 不报错,
所有类型都不带命名空间前缀——
于是 tds__GetProfiles 与 trt__GetProfiles 变成同一个 GetProfiles,
结构体被合并,字段互相覆盖。
二、这活儿到底有多大
很多人以为「设备服务就十几个接口」,这个印象是错的:
| service | portType 操作数 | 备注 |
|---|---|---|
devicemgmt.wsdl |
103 | 设备、网络、用户、证书、存储全在里面 |
media10.wsdl |
79 | 媒体配置 |
media20.wsdl |
59 | Media2,另一套写法 |
deviceio.wsdl |
29 | 继电器、串口 |
| 其余 10 个 | 4~25 | — |
第一行那个 103 是这节最反直觉的数:
ONVIF 没有把「存储管理」「证书管理」拆成独立 service,全塞在 device 里。
所以「我只做设备发现,不碰存储」也要把 103 个操作的声明全编进来——
这也是 onvif.h 一定在万行量级的原因。
合计 417 个操作、1810 个全局 element,这就是 wsdl2h 一次要处理的工作量。
三、生成出来的东西长什么样
onvif.h 里只有三类内容,没有实现、没有函数体:
| 内容 | 形态 | 谁用 |
|---|---|---|
| 结构体定义 | struct _tt__Resolution { int Width; … } |
soapcpp2 |
| 操作声明 | int __tds__GetDeviceInformation(...) |
soapcpp2 |
| 命名空间表 | struct Namespace namespaces[] |
soapcpp2 |
第一行那些 _tt__ 双下划线不是手写的:
gSOAP 用「_ + 前缀 + __ + 类型名」来避免和用户类型冲突。
所以看到 _tt__Resolution 就知道:
这是 tt 命名空间下的 Resolution,不是我自己定义的那个 Resolution。
第三行那个表更有意思:它在 onvif.h 里就生成了,
但真正被用到是在链接阶段(08.01.07 会讲它少一项会怎样)。
四、为什么必须工具生成
一句话:因为 417 个操作、1810 个 element,手写不完。 但比「写不完」更重要的原因是「改不起」:
| 变化 | 手写要改 | 工具生成要改 |
|---|---|---|
| 规范加一个字段 | 改结构体、改序列化、改调用处 | 重跑一条命令 |
| 厂家私有扩展 | 改遍所有相关函数 | 加一个 WSDL 再跑一次 |
| 换 gSOAP 版本 | 不改 | 重跑,可能结构体名变 |
第三行是「工具生成」的代价:升级 gSOAP 会改结构体名, 于是你的业务代码要跟着改——所以工程上要锁版本(08.08.02 会讲为什么跟教材用 2.8.116)。 第一行和第三行合起来说明一件事:WSDL 与 gSOAP 版本都是「输入」,都要进版本库, 否则「同一份代码在不同机器上生成出不同结果」——这种问题在联调时几乎无法解释。
五、动手 Demo:wsdl2h 生成 onvif.h
Demo 不跑 wsdl2h(本机没有),而是「数它要处理多少东西」——
用 14 个真 WSDL 数出操作数与 element 数,作为 onvif.h 规模的依据。
这是本节唯一能给出硬数字的角度。
# 数每个 service 的 portType 操作与全局 element
for fn in SVC:
r = ET.parse(os.path.join(W, fn)).getroot()
ops = [op.get("name")
for pt in r.iter(WSD + "portType")
for op in pt.iter(WSD + "operation")]
els = [e.get("name") for e in r.iter(XSD + "element")
if e.get("name")]
print(fn, len(ops), len(els))
实跑输出:
--------------------------------------------------------------------
wsdl2h 生成 onvif.h
--------------------------------------------------------------------
**书上这条命令是第八章的核心**:
`wsdl2h -o onvif.h -c -s -t typemap.dat wsdl/devicemgmt.wsdl ...`
参数 作用 漏了会怎样
----------------------------------------------------------------------------
`-o onvif.h` 输出文件名 默认写 `typemap.h`,找错文件
`-c` **生成 C 不是 C++** 生成 `class`,C 编不过
`-s` **不使用 STL** 带 `std::string`,嵌入式编不过
`-t typemap.dat` 指定类型映射表 **没有 ONVIF 前缀,类型全串**
**第一行那个默认值不是瞎猜**:不写 `-o` 时输出 `typemap.h`,**跟 `typemap.dat` 只差一个扩展名**,
很容易看错文件。
**最容易被漏的是 `-t`,而且漏了不报错**——
**所有类型都不带命名空间前缀,`soapcpp2` 会把它们当同一个类型合并。**
**结果是编译过了、跑起来全串**,这种错最难查。
**本机没有 `wsdl2h`,所以生成不了真头文件。**
**但「生成出来有多大」可以数出来**:按它读进去的 WSDL 数。
service portType 操作 全局 element
-----------------------------------------------------
`devicemgmt.wsdl` 103 401
`media10.wsdl` 79 310
`media20.wsdl` 59 278
`ptz10.wsdl` 18 86
`ptz20.wsdl` 29 131
`event.wsdl` 10 52
`imaging.wsdl` 11 45
`analytics.wsdl` 14 59
`recording.wsdl` 25 122
`search.wsdl` 18 120
`replay.wsdl` 4 15
`receiver.wsdl` 8 30
`display.wsdl` 10 43
`deviceio.wsdl` 29 118
**合计 417 个操作、1810 个全局 element。**
**这是「14 个全下」的上限**——实际只挑需要的版本,
**比如 media 用 ver10 就别再喂 ver20,否则同一批类型会定义两遍。**
**`wsdl2h` 给每个 element 生成一对请求/响应结构体**,
**再串进一个 `struct SOAP_ENV__Header`**,
**所以 onvif.h 一定是万行量级**——这个量级是按 1810 个 element 数的。
**注意 `devicemgmt.wsdl` 一个就 103 个操作**,
**比很多人以为的「设备服务就十几个接口」多一个数量级**:
**它把存储、证书、网络、用户、日志全塞进 device service 了。**
**这也解释了为什么 onvif.h 必须用工具生成**:
**417 个操作手写不完,改一个 WSDL 版本还得重来。**
这段代码读的是真 WSDL,所以 417 与 1810 是数出来的,不是估的。
注意 portType 那里为什么要用 r.iter() 而不是 find():
因为 WSDL 里 portType 可能不止一个(一个 WSDL 可以定义多个接口),
用 find 只会拿到第一个。
第二列那个 103 值得多看一眼:如果你只做「取流 + 抓拍」,理论上只需要 media 的 79 个操作,
但实际操作上没人会为了减小体积去裁剪 WSDL——因为裁剪过的 WSDL 下次升级就没法直接换了。
最后那句「417 是上限」也要注意:media 和 ptz 各有 ver10 与 ver20 两套,
实际工程只喂其中一套给 wsdl2h,否则同名类型会定义两遍,编译报重复定义。
30 秒实验
- 把
media20.wsdl从清单里去掉,看总数变成多少。 - 数一下
onvif.xsd里complexType的个数,看和你预期差多少。 - 查一下
-c与-s同时在 gSOAP 文档里的说明,看能不能不写-s。
常见误区
- 以为设备服务只有十几个接口。
devicemgmt.wsdl一个就 103 个操作,存储、证书、网络、 用户全在里面。 - 漏写
-t typemap.dat。 它不报错,只是所有类型都不带前缀, 同名类型被合并,字段互相覆盖。 - 把 ver10 与 ver20 两套 WSDL 一起喂进去。 同名类型会定义两遍,编译报重复定义。
自测三问(点开对答案)
1. `wsdl2h` 的 `-t` 漏了会怎样? —— **不报错,但所有类型都不带命名空间前缀**, **同名类型被合并,结果是「编译过了、运行起来全串」。** 2. 为什么 `onvif.h` 一定在万行量级? —— **因为有 417 个操作、1810 个全局 element**,**每个 element 都要生成请求/响应结构体。** 3. `_tt__Resolution` 这个双下划线是什么? —— **gSOAP 的命名约定**:`_` + 前缀 + `__` + 类型名, **用来避免和用户自己的 `Resolution` 冲突。**下一节讲鉴权:为什么它要在「生成期」就埋进去,而不是运行期加。
- ☐ (本节完,下一节 08.01.05 鉴权(认证))