soapcpp2 生成的只是一半,另一半在 gSOAP 源码目录里。
这一节把「还要拷哪些」列成七行清单,并且给出一个拿清单对目录的动作。
08.08.07 生成的是「协议相关」的代码,这一节拷的是「运行时」。
08.01.08 是 Windows 版:那时拷的是 stdsoap2 与 wsse,这一节多讲「哪些可以不拷」。
一、七行清单
这七行就是全部,前两行必拷、中间两行看要不要鉴权、后三行看场景:
| 文件 | 是什么 | 要不要 |
|---|---|---|
stdsoap2.c |
运行时核心 | 必拷 |
stdsoap2.h |
运行时头 | 必拷 |
wsse.h |
鉴权结构体 | 要鉴权就拷 |
wsseapi.c |
鉴权实现 | 要鉴权就拷 |
duration.c |
时长类型转换 | 用到才拷 |
dom.c |
DOM 解析 | 不用就不拷 |
threads.c |
多线程支持 | 非多线程不拷 |
前两行是绝对的:stdsoap2.c 里有 soap_new、soap_call、XML 读写全套,
少了它,生成的那些 soapClient.c 一个函数都链接不上。
第七行那个 threads.c 有个前提:它是由 configure --enable-threads 决定要不要编的,
没开线程的话,stdsoap2.c 里根本不会去调它。
二、最容易漏的是 wsseapi.c
漏它的人特别多,而且漏的过程很合理:
| 阶段 | 表现 | 难度 |
|---|---|---|
| 编译 | 找不到 wsse.h |
好查 |
| 补上头文件 | 编译通过 | — |
| 链接 | soap_wsse_* 未定义 |
难查 |
第三行那句报错只给函数名,
不说「你少拷了哪个文件」——
而 soap_wsse_add_UsernameTokenDigest 这个名字,光看是猜不出它属于哪个 .c 的。
记住这条分界线就行:头文件错是编译期,实现错是链接期。
前者一眼就能看出缺什么,后者要反查。
反查的办法:grep -rl 函数名 gsoap源码目录,一条命令就能定位到那个 .c。
三、「用到才拷」的判据
duration.c 是最典型的例子,它的判据能说得很具体:
| 条件 | 结论 |
|---|---|
结构体里有 xsd__duration |
必须拷 |
用到 MaximumRetentionTime |
必须拷 |
| 只读厂家型号与流地址 | 可以不拷 |
第二行那个字段在 07.02.17 讲过:录像配置里的「最长保存时间」就是它,
类型是 xsd__duration,而它的序列化函数在 duration.c 里。
不拷的后果不是编译错:stdsoap2 用一个函数指针去调它,指针是空的时候编译完全正常,
运行到那个字段就崩。
这种「空指针才崩」的东西最难查,
因为它和「哪个操作」绑在一起——
同一个程序,调 GetDeviceInformation 没事,调 GetRecordingConfigurations 才崩。
四、拿清单对目录
这个动作看着笨,但它比重编译一遍快得多:
| 情形 | 做法 | 耗时 |
|---|---|---|
| 自己的工程 | 心里有数 | 不用对 |
| 接手别人的 | 先对清单再看代码 | 省一半排查 |
| 跨平台搬 | 对清单 + 对换行 | 两件事 |
第二行那个顺序很重要:
先对清单、再看代码——因为「链接不过」的九成原因是缺文件,不是逻辑错。
反过来查会走很多弯路:先读两百行业务代码,最后发现只是少拷一个 .c,
这种事在接手项目时非常常见。
第三行那个「对换行」是 Linux 侧独有的:Windows 上编辑过的 .c 拿到 Linux 编,
如果文件里有中文注释、又混了 CRLF,某些编译器会对宏续行报莫名其妙的错。
五、别整目录拷
「省事」的做法是把整个 gSOAP 源码目录拷进来,这个做法会带来三个新问题:
| 拷了什么 | 后果 |
|---|---|
soapServer.c |
多一套符号 |
autotest 等示例 |
重复 main |
| 整个目录 | 编到很多无关文件 |
第二行那个「重复 main」是最常见的链接错误:示例程序里都有 main,
和你的 main.cpp 撞在一起,报错是「multiple definition of main」。
第一行那个「多一套符号」更隐蔽:soapServer.c 里有服务端的 soap_serve,
它和客户端那套不冲突,但会白占体积,而且如果哪天真的用了 -l,两边还会重复定义。
所以正确做法是「按清单拷单个文件」,
不是「按目录拷」——清单只有七行,对着拷一遍不到一分钟。
六、动手 Demo:还要复制哪些文件
Demo 做的是「拿清单对目录」这个动作,用一个假的工程目录演示怎么算出「还缺几个」。 输出里的数字是代码算出来的,不是手写的——所以它和清单永远是同步的。
need = ["stdsoap2.c", "stdsoap2.h", "wsse.h",
"wsseapi.c", "duration.c", "dom.c", "threads.c"]
have = ["stdsoap2.c", "stdsoap2.h", "wsse.h", "duration.c"]
miss = [f for f in need if f not in have]
print("工程里现在有 %d 个:%s" % (len(have), "、".join(have)))
print("对照清单还缺 %d 个:%s" % (len(miss), "、".join(miss)))
实跑输出:
--------------------------------------------------------------------
还要复制哪些文件
--------------------------------------------------------------------
**`soapcpp2` 生成的只是一半,另一半在 gSOAP 源码里。**
**这一节就是把「哪些文件必须从 gSOAP 源码里拷出来」列清楚。**
文件 是什么 要不要
----------------------------------------------------------------
`stdsoap2.c` **运行时核心** **必拷**
`stdsoap2.h` 运行时头文件 **必拷**
`wsse.h` **鉴权结构体** 要鉴权就必拷
`wsseapi.c` 鉴权实现 要鉴权就必拷
`duration.c` 时长类型转换 用到 `xsd__duration` 才拷
`dom.c` DOM 解析(可选) 不用就不用拷
`threads.c` 多线程支持 非多线程可不拷
**第二行那个 `wsseapi.c` 最容易漏**:
**很多人只拷了 `wsse.h`(因为有编译错误提示「找不到头文件」),**
**结果头文件有了、结构体有了,**
**但 `soap_wsse_add_UsernameTokenDigest` 这个函数没有实现**,
**链接期才炸。**
**「头文件错是编译期,实现错是链接期」**——
**后者更难查,因为报错里只有函数名,没有「你少拷了哪个文件」。**
**第三行那句「用到才拷」也有讲究**:
**`duration.c` 提供的是 `xsd__duration` 与秒数互转**,
**ONVIF 里 `MaximumRetentionTime` 这种字段就是它**(07.02.17 讲过)。
**不拷它,编译能过、运行到那个字段就崩**——
**因为函数指针是空的。**
**我把这份清单整理成「核对用」的形式**:
假设工程里现在有 4 个:stdsoap2.c、stdsoap2.h、wsse.h、duration.c
对照清单还缺 3 个:wsseapi.c、dom.c、threads.c
**这个「拿清单对目录」的动作看着笨,但比重编译一遍快得多。**
**尤其是接手别人的工程**:**先对清单,再看代码**,
**能省掉一半「为什么链接不过」的排查。**
输出里「有 4 个、缺 3 个」正好加起来是清单的 7 行——这个自洽关系就是核对的意义:
数目对不上说明清单漏了或者目录多了。
这四行代码用的是列表推导,[f for f in need if f not in have] 一行就够,
不需要写循环——清单越短,这种写法越省事。
缺的那三个里有两个是鉴权相关的:wsseapi.c 和 dom.c,
而 dom.c 其实是 wsseapi.c 的依赖——因为鉴权头里那段 XML 是用 DOM 拼的。
所以「要鉴权就要拷三个文件」这条经验是有因果的:
wsse.h 给结构体、wsseapi.c 给实现、dom.c 给它干活。
30 秒实验
- 把你项目里所有
soap开头的文件列出来,看有几个是生成物。 - 用
grep -rl soap_wsse_add_UsernameTokenDigest在 gSOAP 源码里找它在哪个.c。 - 把
threads.c从工程里删掉再编一次,看是不是真的不用它。
常见误区
- 只拷
wsse.h不拷wsseapi.c。 编译能过、链接才炸,而且要 grep 才查得出缺哪个文件。 - 把整个 gSOAP 源码目录拷进工程。 示例程序自带
main, 会和自己的撞成「multiple definition」。 - 以为
duration.c不拷只是编不过。 它是函数指针形式调用的,编译正常、 运行到时长字段才崩。
自测三问(点开对答案)
1. 为什么「头文件错是编译期,实现错是链接期」这句话有用? —— **因为两阶段的报错信息量差很多**:编译期直接说「找不到头文件」,链接期只给函数名, 要自己 grep 反查属于哪个 `.c`。 2. 只读厂家型号和流地址的客户端,可以不拷哪几个文件? —— **可以不拷 `duration.c`、`dom.c`、`threads.c`。** 但一旦要读录像配置里的最长保存时间, `duration.c` 就必须补上。 3. 为什么「拿清单对目录」要排在「读代码」前面? —— **链接不过的九成原因是缺文件而不是逻辑错。** 先读两百行代码最后发现少拷一个 `.c`, 是最常见的白干。下一节讲命名空间表——Linux 下它不是个文件,是程序里的一段数组。
- ☐ (本节完,下一节 08.08.09 关联自己的命名空间)