Linux 下没有「exe 同目录」这个概念,所以 .nsmap 一般不落地成文件。
做法是把那张表编进程序,而表里有两个名字只差一个字符、意思完全不同。
08.01.07 讲的是 Windows 版:把 *.nsmap 拷到 exe 旁边。
这一节讲 Linux 版:同一个信息,换个放法,
顺带说清 SOAP-ENV 与 SOAP_ENV 这对容易搞混的名字。
一、两种放法
Linux 不是不能读文件,是「文件该放哪儿」没有标准答案:
| 做法 | 怎么写 | 适合 |
|---|---|---|
| 读文件 | soap_load_namespaces |
要能换配置 |
| 编进程序 | Namespace 数组 |
固定部署 |
第二行更适合 Linux:可执行文件在 bin/,数据在 /usr/share、配置在 /etc,
一个 .nsmap 到底该去哪儿,不同发行版各有各的规矩。
编进程序就绕开了这个问题:
二进制拷过去就能跑,不用管配置文件在哪——这正好是 Linux 部署最烦的那件事。
代价是「改命名空间要重新编译」:不过命名空间是按规范定的,一般十年不变,
所以这个代价可以接受。
二、表长什么样
十个条目,一条一个命名空间:
struct Namespace namespaces[] = {
{"SOAP-ENV", "http://www.w3.org/2003/05/soap-envelope"},
{"xsd", "http://www.w3.org/2001/XMLSchema"},
{"xsi", "http://www.w3.org/2001/XMLSchema-instance"},
{"tds", "http://www.onvif.org/ver10/device/wsdl"},
{"trt", "http://www.onvif.org/ver10/media/wsdl"},
{"tptz", "http://www.onvif.org/ver20/ptz/wsdl"},
{"tt", "http://www.onvif.org/ver10/schema"},
{"wsdd", "http://schemas.xmlsoap.org/ws/2005/04/discovery"},
{"wsa", "http://schemas.xmlsoap.org/ws/2004/08/addressing"},
{"wsse", "http://docs.oasis-open.org/wss/2004/01/"
"oasis-200401-wss-wssecurity-secext-1.0.xsd"},
{NULL, NULL}
};
这十个条目覆盖了第八章用到的全部服务:设备管理、媒体、云台、类型定义、发现、寻址、鉴权。
最后那个 wsse 的 URI 太长,在 C 里用两个字符串字面量拼接——编译器会把它们接成一个,
这样源码里就不会有一行两百字符。
三、三条必须遵守的规矩
| 规矩 | 违反了会怎样 |
|---|---|
最后一项是 {NULL, NULL} |
读到野内存 |
| 第一项是信封命名空间 | 查表顺序错 |
| 名字要和代码里一致 | unbound prefix |
第一行是硬约束:gSOAP 用「两个空指针」判断表结束, 数组长度它不认——漏了这一行,它会一直往后读,读到的是栈上或静态区里的随机字节。 表现是随机崩溃或者「找不到命名空间」, 而崩溃位置每次都不同——这种 bug 最难查,因为加一行打印就「好了」(内存布局变了)。 第三行是逻辑约束:表里的名字是「前缀」,代码里的也是「前缀」,两处必须逐字符相同, 包括大小写和连字符。
四、SOAP-ENV 与 SOAP_ENV 是两个东西
这一条是本节的真正坑,因为它看起来像是同一个名字的两种写法:
| 写法 | 出现在哪 | 是什么 |
|---|---|---|
SOAP-ENV |
XML 前缀 | 报文里的字符串 |
SOAP_ENV |
C 语言标识符 | 结构体名 |
第一行带连字符,是 XML 里的前缀,它出现在真正的报文里:<SOAP-ENV:Envelope> 这种。
第二行带下划线,是 C 的标识符,
因为 C 的变量名不能有连字符——
于是 gSOAP 把它写成 SOAP_ENV__Header、SOAP_ENV__Envelope。
typemap.dat 里写的是后者(SOAP_ENV__Header),而 soapcpp2 生成的报文里用的是前者。
两个都存在、各管一处:把 SOAP-ENV 写进 typemap.dat 会解析失败,
把 SOAP_ENV 写进 namespaces[] 会 unbound prefix。
五、自己的命名空间怎么加
很多厂家的设备有私有扩展,这些扩展不在 ONVIF 标准命名空间里:
| 情形 | 要不要加 | 放哪 |
|---|---|---|
| 只用标准操作 | 不用加 | — |
| 用了厂家私有字段 | 要加 | 表末尾 |
| 自己定义的扩展 | 要加 | 表末尾 |
第二、三行都要加在表末尾,不要插在标准项中间——因为第一项固定是信封命名空间,
中间插入会让「第几项是什么」这种依赖顺序的逻辑错位。
加之前先确认前缀没被占用:标准里已经用了 tds、trt、tt、tptz,
再起一个同名的会把标准项顶掉,表现是「标准操作突然 unbound prefix」。
这也是「先对清单再动手」的道理:表里已有的十个前缀,就是你的可用前缀黑名单。
六、动手 Demo:Linux 下命名空间表怎么放
Demo 把那张表按 C 的写法打印出来,顺便验证三件事:条数、结尾项、第一项。
输出里那十行就是可以直接粘进 .c 的代码,不是伪代码。
ns = [("SOAP-ENV", "http://www.w3.org/2003/05/soap-envelope"),
("xsd", "http://www.w3.org/2001/XMLSchema"),
("xsi", "http://www.w3.org/2001/XMLSchema-instance"),
("tds", "http://www.onvif.org/ver10/device/wsdl"),
("trt", "http://www.onvif.org/ver10/media/wsdl"),
("tptz", "http://www.onvif.org/ver20/ptz/wsdl"),
("tt", "http://www.onvif.org/ver10/schema"),
("wsdd", "http://schemas.xmlsoap.org/ws/2005/04/discovery"),
("wsa", "http://schemas.xmlsoap.org/ws/2004/08/addressing"),
("wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd")]
for k, v in ns:
print(' { "%s", "%s" },' % (k, v))
print("共 %d 项,结尾补 {NULL, NULL}" % len(ns))
实跑输出:
--------------------------------------------------------------------
Linux 下命名空间表怎么放
--------------------------------------------------------------------
**Windows 版是把 `*.nsmap` 拷到 exe 同目录**,
**Linux 版更常见的做法是「编进程序里」**——
**因为 Linux 下没有「exe 同目录」这个概念**(可执行文件在 `bin/`,数据在 `/usr/share`)。
做法 怎么写 适合
------------------------------------------------------------------------
**读文件** `soap_load_namespaces()` 要能换配置
**编进程序** `struct Namespace namespaces[] = {…}` **固定部署**
**第二行更适合 Linux**:**一个二进制文件拿过去就能跑**,
**不用管「配置文件放哪儿」**——
**这正好是 Linux 部署最烦的那件事。**
**本机把这张表按 C 的写法打出来(10 项)**:
{ "SOAP-ENV", "http://www.w3.org/2003/05/soap-envelope" },
{ "xsd", "http://www.w3.org/2001/XMLSchema" },
{ "xsi", "http://www.w3.org/2001/XMLSchema-instance" },
{ "tds", "http://www.onvif.org/ver10/device/wsdl" },
{ "trt", "http://www.onvif.org/ver10/media/wsdl" },
{ "tptz", "http://www.onvif.org/ver20/ptz/wsdl" },
{ "tt", "http://www.onvif.org/ver10/schema" },
{ "wsdd", "http://schemas.xmlsoap.org/ws/2005/04/discovery" },
{ "wsa", "http://schemas.xmlsoap.org/ws/2004/08/addressing" },
{ "wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" },
**注意三点**:
**① 这个数组必须以 `{NULL, NULL}` 结尾**,
**因为 gSOAP 用「空指针」判断表结束**,漏了会读到野内存;
**② 第一项必须是 SOAP 信封的命名空间**,顺序不能乱;
**③ 表里的名字要和生成代码里用的前缀完全一致**——
**`SOAP-ENV` 带连字符,写成 `SOAP_ENV` 就对不上**(这两个在 gSOAP 里是不同的东西)。
**第三点是真事故**:`soapcpp2` 生成的行里用的是 `SOAP-ENV`,
**而 `typemap.dat` 里写的是 `SOAP_ENV`(下划线)**,
**两个都存在,各管一处**——**搞混了就是 unbound prefix。**
十行输出无一例外都以 { 开头、}, 结尾——这就是 C 的初始化列表语法,
所以这段输出可以整段粘进源码,不用改一个字符。
那句 print 的花括号与引号要数清:外层是单引号包字符串、内层双引号当字面量,
所以内层双引号不用转义——这正是选单引号当外层的理由。
最后一行那个「补 {NULL, NULL}」是提醒不是代码:Python 里没有 NULL,
这一行只能靠手写,所以要在输出里明说——这种「代码生成不了的部分」一定要标出来,否则会漏。
注意十个条目里 SOAP-ENV 带连字符、其余都是短前缀:这个不对称是有原因的,
因为 SOAP-ENV 是 W3C 定的 XML 前缀,不能改。
30 秒实验
- 把
{NULL, NULL}那行删掉再编,看会不会崩(在虚拟机里试)。 - 把
SOAP-ENV改成SOAP_ENV,看报文里前缀变成什么。 - 把自己公司域名当命名空间加一项,看它会不会影响标准操作。
常见误区
- 以为
SOAP-ENV和SOAP_ENV是同一个东西。 一个是 XML 前缀、一个是 C 标识符, 写错地方就是 unbound prefix。 - 表末尾不写
{NULL, NULL}。 gSOAP 靠空指针判结束,漏了会读到野内存, 崩的位置每次都不同。 - 把自己的命名空间插在表中间。 第一项必须是信封命名空间,中间插入会让依赖顺序的逻辑错位。
自测三问(点开对答案)
1. 为什么 Table 的第一项必须是信封命名空间? —— **因为 gSOAP 查表时按「第一项是 SOAP 信封」来定位报文最外层的解析规则。** 顺序一乱, 整个 XML 的解析起点就错了。 2. `{NULL, NULL}` 漏了为什么会「随机」崩? —— **因为没有结束标记,查表会一直往后读**,读到静态区里别的变量。 加一行打印就「好了」,是因为内存布局变了。 3. 加了厂家私有命名空间之后,为什么要先查前缀有没有被占用? —— **同名会把标准项顶掉**,表现是标准操作突然 unbound prefix。 表里已有的前缀就是可用前缀的黑名单。下一节讲封装:把「拼信封、解 XML」这两件事关进一个类里。
- ☐ (本节完,下一节 08.08.10 代码封装)