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 秒实验
- 把
xsd__duration那行的 key 故意写成xsd_duration(少一个下划线),看命中几次。 - 在
setkey里加一条print(key, hit),把两次改动的命中数都打出来。 - 想想为什么
SOAP_ENV__Header要写成| struct …的形式,而不是直接给个类型名。
常见误区
- 按行号改。 gSOAP 不同版本的
typemap.dat行数差四十多行, 按行号改迟早改到别人家的行上。 - 用最小片段覆盖真的
typemap.dat。 那会删掉 gSOAP 自己的映射, 症状是某些类型莫名变成char*。 - 漏掉
tt那一行。tt开头的类型占了 ONVIF 的一半, 少了它就是「所有数据类型都没有命名空间」。
自测三问(点开对答案)
1. `typemap.dat` 改错了,`wsdl2h` 会报错吗? —— **不一定。** key 拼错、值少字符、整行删掉,它都照常跑完,**错误要到编译期或运行期才出现。** 2. 为什么改 typemap.dat 要写「命中 1 次」的断言? —— **因为「静默无效」最难查**。 命中 0 次说明版本不匹配,**当场失败比十分钟后出现「未定义类型」好得多。** 3. ONVIF 必须加的那行 Header 声明起什么作用? —— **把默认的空 Header 换成带 `wsse__Security` 指针的结构**,**否则鉴权信息没有地方放。**下一节跑 wsdl2h:四个参数,其中一个漏了不报错但结果全错。
- ☐ (本节完,下一节 08.01.04 生成onvif.h文件)