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

typemap.dat 是 gSOAP 的类型映射表,wsdl2h 读它决定「生成什么类型」。 ONVIF 要改三处:命名空间前缀、SOAP Header、时长类型。 而改法必须是「按 key 改」,不是「按行号改」。

📝 本节和前面几节的关系

工作空间建好了,这一节动第一个「有技术含量」的文件。 它是整章唯一的「配置」——后面 wsdl2h 与 soapcpp2 的行为都由它决定。 改错的后果不是报错,是「生成了但不对」,这是最难查的一类错。

一、它到底在映射什么

typemap.dat 的每一行都是「名字 = 值」, wsdl2h 用它决定「WSDL 里的这个类型,在 C 里写成什么」。 它管三件事:

管什么 例子 不改会怎样
命名空间 → 前缀 tt = "http://…/ver10/schema" 类型全串
Header 的结构 SOAP_ENV__Header = | struct … 没法鉴权
特殊类型映射 xsd__duration = | LONG64 时长字段是字符串

第一行是 ONVIF 必须改的:规范里的类型全带前缀(tt:Resolution、tds__GetProfiles), 而 gSOAP 不知道 tt 是谁——不告诉它,它会把所有 tt: 类型当成「同名的另一个类型」, 生成一堆重复结构体,最后编译报「重复定义」。 第二行是鉴权的前置条件:默认的 Header 是空的,空 Header 里没有地方放 wsse:Security, 所以鉴权代码根本无处落脚(08.01.05 展开讲)。

二、为什么不能按行号改

很多教程写的是「打开 typemap.dat,在第 37 行加入…」,这种做法换个 gSOAP 版本就废了:

做法 2.8.116 能用吗 换版本会怎样
按 key 找 能 照样能用
按行号改 能 改到别人家的行上
整段覆盖 能 新版的特有映射被你删了

第二行的后果不是报错:你改的那一行恰好也是另一条映射, 于是「ONVIF 的前缀加上了,gSOAP 自己的某条映射没了」——症状是某几个类型突然变成 char*。 第三行更隐蔽:新版 gSOAP 可能给新类型加了映射, 你整段覆盖回旧版内容,那些新类型就没人管了。 所以正确做法是「读进来、按 key 改、写回去」, 这也是为什么我建议用脚本改而不是手工改——脚本可以写断言「这一行必须命中恰好 1 次」。

三、三处要改的具体内容

第一处:命名空间前缀。 需要 8 到 10 行,每个 service 一行,加 tt 一行,加 wsdd 一行:

前缀 URI 结尾 用在哪
tds /ver10/device/wsdl 设备管理
trt /ver10/media/wsdl 媒体
tptz /ver20/ptz/wsdl 云台
tt /ver10/schema 所有数据类型
wsdd /ws/2005/04/discovery 设备发现

第四行是出现频率最高的:tt 开头的类型占了 ONVIF 的一半, 而它最容易漏——因为 tds、trt 看着像「主类型」,tt 像「辅助的」。 第二处:SOAP_ENV__Header。 一行,把 Header 换成带 wsse__Security 的结构。 第三处:xsd__duration。 一行,把时长映射成整数或自定义类型。 第三处不改也能跑,但 MaximumRetentionTime 这种字段会变成字符串, 后面算「能存几天」时就得自己解析 ISO 8601(07.02.17 讲过那个格式)。

四、改错不报错这件事

这一节最该带走的一句话:typemap.dat 改错,wsdl2h 不一定报错。

错法 wsdl2h 的反应 什么时候炸
key 拼错 照常跑完 编译期一堆未定义类型
值写错(URI 少字符) 照常跑完 运行期 unbound prefix
整行删掉 照常跑完 那个类型变成默认类型

三行都是「照常跑完」,这就是它难查的原因:你看到的第一个错误,离真正的原因隔了好几层。 所以要用脚本改、要写断言: 「命中 1 次」是正常的,「命中 0 次」必须当场停下来—— 0 次说明这个版本的 key 名不一样,硬往下走只会生成一个空映射。 空映射的表现是「文件改了、内容没变」,而人眼很难发现「我改的那行好像没生效」。

五、动手 Demo:typemap.dat 到底改什么

Demo 真写一份 typemap.dat,再按 key 改两次,量「命中几行、改完几行」。 重点看最后那句「命中 1 行」——它就是脚本该断言的返回值。

# 按 key 改:命中几行就返回几
def setkey(t, key, val):
    out, hit = [], 0
    for ln in t.split("\n"):
        if ln.startswith(key + " ") or ln.startswith(key + "="):
            out.append(key + " = " + val); hit += 1
        else:
            out.append(ln)
    return "\n".join(out), hit

t2, hit = setkey(txt, "xsd__duration", "| LONG64")
assert hit == 1, hit        # 命中 0 就该停

实跑输出:

--------------------------------------------------------------------
typemap.dat 到底改什么
--------------------------------------------------------------------
**`typemap.dat` 是 gSOAP 的类型映射表。**
**`wsdl2h` 读它,决定生成的 C 结构体里用什么类型、哪个前缀映射到哪个命名空间。**
**ONVIF 要用它做三件事**:分配前缀、把 SOAP Header 换成带鉴权的版本、换掉时长类型。

**这是本机真写出来的一份**:16 行、724 字节。

  # typemap.dat(摘录,形状与 gSOAP 自带的一致)
  # 每一行是「名字 = 值」,不是「行号 = 值」
  xsd__string = | std::string | wchar_t*
  xsd__duration = | xsd__duration
  SOAP_ENV__Header = | struct SOAP_ENV__Header
  # ---- ONVIF ----
  SOAP_ENV__Header = | struct SOAP_ENV__Header { struct wsse__Security *wsse__Security; }
  tds = "http://www.onvif.org/ver10/device/wsdl"
  trt = "http://www.onvif.org/ver10/media/wsdl"
  tptz = "http://www.onvif.org/ver20/ptz/wsdl"
  tev = "http://www.onvif.org/ver10/events/wsdl"
  tim = "http://www.onvif.org/ver20/imaging/wsdl"
  tan = "http://www.onvif.org/ver20/analytics/wsdl"
  tt = "http://www.onvif.org/ver10/schema"
  wsdd = "http://schemas.xmlsoap.org/ws/2005/04/discovery"

**注意那句注释:每一行是「名字 = 值」。**
**这就是为什么改它不能按行号**:
**gSOAP 2.8.116 与 2.8.132 的 `typemap.dat` 行数差四十多行**,
**按「改第 37 行」写下来的步骤,换个版本就改到别人家的行上去了。**
**正确做法是按 key 找、按 key 改**:
改 `xsd__duration` → 命中 1 行
改 `tt` → 命中 1 行
改完还是 16 行(只换值不加行)

**「命中 1 行」是正常的,「命中 0 行」要当场停下来。**
**0 行说明这个版本的 key 名不一样**,硬往下走会生成一个空映射。
**而空映射不报错**:`wsdl2h` 照样跑完,只是头文件里少了那几个类型,
**到编译时才炸**,报的是一堆「未定义类型」。

最后那行 assert hit == 1 是整段代码的重点:改配置文件这种事,最怕的就是「静默无效」。 加上断言之后,版本不匹配会当场失败——而不是在十分钟后以「未定义类型」的形式出现。 输出里那句「16 行、724 字节」也是有用的基准:真实的 typemap.dat 有两百多行, 我这份是「等价的最小片段」,只保留 ONVIF 相关的部分—— 所以行数对不上是正常的,key 名是对的。 这一点必须说清:如果你照着这 16 行去覆盖真的 typemap.dat, 会把 gSOAP 自己的映射全删掉,症状是「某些类型莫名变成 char*」。 正确做法是「在真的文件里加这几行」,不是「用这份替换它」。

30 秒实验

  1. 把 xsd__duration 那行的 key 故意写成 xsd_duration(少一个下划线),看命中几次。
  2. 在 setkey 里加一条 print(key, hit),把两次改动的命中数都打出来。
  3. 想想为什么 SOAP_ENV__Header 要写成 | struct … 的形式,而不是直接给个类型名。

常见误区

  1. 按行号改。 gSOAP 不同版本的 typemap.dat 行数差四十多行, 按行号改迟早改到别人家的行上。
  2. 用最小片段覆盖真的 typemap.dat。 那会删掉 gSOAP 自己的映射, 症状是某些类型莫名变成 char*。
  3. 漏掉 tt 那一行。 tt 开头的类型占了 ONVIF 的一半, 少了它就是「所有数据类型都没有命名空间」。
自测三问(点开对答案) 1. `typemap.dat` 改错了,`wsdl2h` 会报错吗? —— **不一定。** key 拼错、值少字符、整行删掉,它都照常跑完,**错误要到编译期或运行期才出现。** 2. 为什么改 typemap.dat 要写「命中 1 次」的断言? —— **因为「静默无效」最难查**。 命中 0 次说明版本不匹配,**当场失败比十分钟后出现「未定义类型」好得多。** 3. ONVIF 必须加的那行 Header 声明起什么作用? —— **把默认的空 Header 换成带 `wsse__Security` 指针的结构**,**否则鉴权信息没有地方放。**

下一节跑 wsdl2h:四个参数,其中一个漏了不报错但结果全错。

相关笔记