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

内容和 08.01.03 完全一样,Linux 侧只多一个新问题:换行符。 Windows 存过的文件行尾是 \r\n,而 wsdl2h 会把那个 \r 当成「值的一部分」。

📝 本节和前面几节的关系

上一节编好了工具, 这一节又要动 typemap.dat——因为 Windows 那份和 Linux 这份本该一样,实际却常常不一样。 这一节讲的就是那个「为什么不一样」。

一、同一个文件,两个文件系统

typemap.dat 是纯文本,而纯文本的「行尾」在不同系统里定义不同:

系统 行尾字节 名字
Windows 0D 0A CRLF
Linux/macOS 0A LF
老 Mac OS 0D CR(已淘汰)

这两列本身没问题,问题是「跨系统传文件」时没有转换:在 Windows 上编辑过、扔到 Linux 上, 文件里每一行末尾都多一个 0D。 而 wsdl2h 读配置文件是「按行切、去掉行尾」—— 它按 0A 切,切完之后每行末尾还留着一个 0D。 于是 tt = "http://…/schema" 这一行的值变成了 "http://…/schema"\r—— URI 末尾多一个不可见字符,全表失配。

二、为什么它特别难查

三个原因叠在一起,让这个错几乎无法用眼睛发现:

原因 表现 后果
\r 不可见 打印出来一模一样 肉眼对比不出来
URI 很长 78 个字符里多一个 看不出哪里不一样
报错不指向它 只报 unbound 或 Fault 怀疑方向全错

第三行是最要命的: 你会去怀疑「鉴权算错了」「时钟不对」「密码错了」,唯独不会怀疑「配置文件里多了个回车」。 而验证办法其实很简单:把那一行读出来,看末尾的字符码——repr() 一打就出来了, 但前提是你想到「要去看这个」。 第二行那个「看不出哪里不一样」有个例外:如果你用 cmp 比两个文件, 它会明确告诉你「第几个字节不同」——这比肉眼看快得多,也是跨平台传文件后的标准动作。

三、归一化怎么做

工具 命令 说明
dos2unix dos2unix typemap.dat 最直接
sed sed -i 's/\r$//' typemap.dat 没有 dos2unix 时用
编辑器 「以 LF 保存」 VS Code/Notepad++ 都有
版本库 .gitattributes 里 * text=auto 长期方案

第四行才是根本办法:在版本库里配好 text=auto, 检出时按平台转换、提交时统一成 LF——这样「谁在哪个系统上编辑过」就不再影响结果。 前三行是「已经出问题了怎么救」,第四行是「不让它出问题」。 工程上两者都要有:先救,再防。 第二行那个 sed 要注意:\r 在有些 sed 版本里不认, 更保险的写法是 sed -i 's/\x0d$//'——用十六进制码,绕开转义差异。

四、反过来也有问题

Linux 生成的文件拿到 Windows 上,也有坑:

文件 表现 严重吗
onvif.h VS 能读 LF 一般不严重
带宏续行的代码 \ 后面多一个 \r,宏断了 真会编不过
.nsmap 看读取方式 可能解析失败

第二行是真问题:C 的宏续行靠行尾的 \,如果 \ 后面还有一个 \r, 编译器会认为「这不是续行」——于是宏定义被截断,报的错离真正原因十万八千里。 不过 gSOAP 生成的文件里宏续行不多, 所以这个问题在 ONVIF 项目里不常见——但如果你自己写了带长宏的文件跨平台传,一定要想到它。 第三行那个 .nsmap 视实现而定:它既有「读文件」也有「编进程序」两种用法, 前一种会被 \r 影响,后一种不受影响。

五、动手 Demo:Windows 改过的 typemap.dat 到 Linux 会坏

Demo 用同一份内容做 CRLF 与 LF 两个版本,量字节差、再看末尾字符。 重点是最后那两行 repr()——它是唯一能让 \r 现形的办法。

src = open("typemap.dat", encoding="utf-8").read()
crlf = src.replace("\n", "\r\n")     # Windows 存法
lf = src.replace("\r\n", "\n")       # Linux 存法
print(len(crlf.encode()) - len(lf.encode()))   # 16

line = [x for x in crlf.split("\n") if x.startswith("tds ")][0]
print(repr(line[-6:]))                 # 'wsdl"\r'

实跑输出:

--------------------------------------------------------------------
Windows 改过的 typemap.dat 到 Linux 会坏
--------------------------------------------------------------------
**这一节只有一个知识点,但它是真实事故:换行符。**
**Windows 记事本存出来的文件行尾是 `\r\n`(CRLF)**,
**Linux 下很多解析器只认 `\n`(LF)**。
**`wsdl2h` 读 typemap.dat 时,如果行尾多一个 `\r`**,
**它会把这个 `\r` 当成「值的一部分」**——
**于是命名空间 URI 末尾多一个不可见字符,全表失配。**

**用同一份内容做两种换行,量一遍**:

  版本                    行数     字节     每行多
  ------------------------------------------------------
  **LF(Linux)**         16       724      —
  **CRLF(Windows)**     16       740      **16 字节**

**第三列差的就是行数**:16 行,每行多 1 个字节,正好 16。
**这 16 个字节在编辑器里看不见,在字符串比较里却是致命的。**

**验证办法:把一行读出来,看末尾是什么。**

CRLF 版那一行的末尾 6 个字符:'wsdl"\r'
转成 LF 之后:'/wsdl"'

**差的就是那个 `\r`**:CRLF 版末尾多一个它,所以同样取 6 个字符,窗口整体右移了一位。
**比较两个 URI 时,`.../wsdl\r` 和 `.../wsdl` 不相等**,
**但打印出来一模一样**——这就是它难查的原因。

**所以跨平台传文本文件,一定要做一次换行归一**:
**`dos2unix typemap.dat`,或者编辑器里选「以 LF 保存」。**
**反过来也一样**:**Linux 上生成的 `.h`/`.cpp` 拿到 VS 里打开**,
**VS 能认 LF,但如果混着 CRLF,某些版本会在宏续行处报错。**

那 16 个字节就是「16 行 × 每行多一个 \r」—— 这个数字能对上,说明差异完全来自换行符,没有别的东西。 第二段的 repr() 是关键:直接 print(line[-6:]) 只会看到 wsdl", \r 会把光标移回行首,把前面的内容覆盖掉——这是终端行为,不是文件内容丢了。 所以「用 print 看不可见字符」这件事本身就不可靠,必须用 repr()、hex()、或者 cmp。 输出里那个「窗口整体右移一位」也要理解: CRLF 版末尾多一个字符,所以取末尾 6 个字符时,看到的内容比 LF 版少一个—— 这正好说明「多了一个字节」。

30 秒实验

  1. 用 cmp -l 文件A 文件B 比较两个版本,看它报哪些字节不同。
  2. 把 repr(line[-6:]) 改成 line[-6:].encode().hex(),看十六进制码。
  3. 查一下你项目里有没有 .gitattributes,看有没有配 text=auto。

常见误区

  1. 用 print 看文件内容判断有没有 \r。 \r 会把光标移回行首覆盖显示, 看起来和没有一样。要用 repr() 或 hex()。
  2. 跨系统传文件不做换行归一。 在 Windows 编辑过、扔到 Linux, 每个 URI 末尾都会多一个不可见字符。
  3. 只救不防。 dos2unix 是救急,长期要在版本库配 text=auto。
自测三问(点开对答案) 1. Windows 编辑过的 `typemap.dat` 到 Linux 上会怎样? —— **每行末尾多一个 `\r`**,`wsdl2h` 会把它当成「值的一部分」,**于是命名空间 URI 全对不上。** 2. 为什么这个错特别难查? —— **`\r` 不可见、URI 很长、报错不指向它**。你会去怀疑鉴权、时钟、密码,不会怀疑配置文件。 3. 根治办法是什么? —— **在版本库配 `.gitattributes` 的 `text=auto`**,提交时统一成 LF,检出时按平台转换。

下一节生成 onvif.h:Linux 侧的命令只差三处,但每一处都会让命令跑不起来。

相关笔记