📹 第八章 ONVIF框架代码案例应用
📄 一句话概括

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 秒实验

  1. 把 media20.wsdl 从清单里去掉,看总数变成多少。
  2. 数一下 onvif.xsd 里 complexType 的个数,看和你预期差多少。
  3. 查一下 -c 与 -s 同时在 gSOAP 文档里的说明,看能不能不写 -s。

常见误区

  1. 以为设备服务只有十几个接口。 devicemgmt.wsdl 一个就 103 个操作,存储、证书、网络、 用户全在里面。
  2. 漏写 -t typemap.dat。 它不报错,只是所有类型都不带前缀, 同名类型被合并,字段互相覆盖。
  3. 把 ver10 与 ver20 两套 WSDL 一起喂进去。 同名类型会定义两遍,编译报重复定义。
自测三问(点开对答案) 1. `wsdl2h` 的 `-t` 漏了会怎样? —— **不报错,但所有类型都不带命名空间前缀**, **同名类型被合并,结果是「编译过了、运行起来全串」。** 2. 为什么 `onvif.h` 一定在万行量级? —— **因为有 417 个操作、1810 个全局 element**,**每个 element 都要生成请求/响应结构体。** 3. `_tt__Resolution` 这个双下划线是什么? —— **gSOAP 的命名约定**:`_` + 前缀 + `__` + 类型名, **用来避免和用户自己的 `Resolution` 冲突。**

下一节讲鉴权:为什么它要在「生成期」就埋进去,而不是运行期加。

相关笔记