排错实战 | 记一次曲折的多资源文件拆分折腾过程(1)

缘起

最近这些天,我一有时间就捣鼓 .rc 。用 git 管理过 .rc 文件的小伙伴儿应该都知道,.rc 文件会被 git 默认当作二进制文件来管理。每次合并代码的时候,只要涉及到 .rc 文件的变更,很大概率会报冲突,而且工具不能很好的解决,只能手动解决。为了解决这个问题,我想到了两个方案:

  1. git.rc 文件当成普通文件来管理。
  2. 把一个大的 .rc 文件拆成多个小的 .rc 文件,这样可以有效的降低冲突的概率。

这两个方案相互之间没有任何冲突,所以这次折腾的时候,这两个方案是同时进行的。本以为没什么值得写的,没想到收获了意外惊喜,于是有了本篇总结。

拆分 rc

按照微软官方文档 TN035: Using Multiple Resource Files and Header Files with Visual C++ 的指示,很快就拆分好了。满心欢喜的准备编译运行,验收成果。

没想到……

初遇错误

编译很快结束了,不过 vs 报告了下图所示的编译错误:

unexpected-end-of-file-error

乍一看有一点懵,不过仔细看文件内容后知道怎么回事了。原来这是一个“老朋友”了,之前遇到过。不是什么大问题,只需要在后面多加一个空行就行了。

温馨提示:

如果在 resource.h 中包含自定义的头文件时,也需要在被包含的头文件的末尾加一个空行。否则也会报错。

在末尾加好空行后,再次编译,成功,正常运行。

multiple-rc-include-success

解决了在同一工程中使用多个 .rc 的问题,下一个问题是让 git 不再把 .rc 文件当成二进制文件来管理。默认情况下,.rc 文件会被当成 binary file 来管理的,当 .rc 中有差异的时候,在命令行中执行 git diff 命令,不能显示出有意义的信息。

binary-rc-diff-fail

修改 git 设置

按照 gitattribtes帮助文档 中的说明,我在项目根目录建立了 .gitattributes 文件,把 .rc.rc2 文件都当成 UTF-16 来管理,这样再冲突的时候就可以比较差异了。

gitattributes

说明:

因为现有项目中的 .rc 文件都被当成了二进制文件,所以我在 .gitattributes 中末尾的位置把旧的 .rc.rc2 文件强制设置为了 binary

设置好 .gitattributes 文件后,手动修改 .rc 文件内容,然后执行 git diff 查看差异。结果如下图,这次显示的差异简直完美。以说明新增加的设置是生效的。

git-diff-rc-success

本以为本次折腾就这么顺利的结束了,可以打扫战场,编写操作文档,收工了。

没想到……

高兴的太早了

撤销完变更后,编译,居然出现了下图中的错误:

error-RC1020-unexpected-endif

这是什么错误?一脸懵逼,难道上面哪个地方多写了 #endif 导致不匹配了?赶紧把改动过的文件都检查一遍,尤其是刚刚改动过的文件。

check-file-modification

每个文件都反反复复看了好几遍,什么问题都没看出来。#if#endif 都是成对儿的。这可如何是好,网上搜索 error RC1020,没有一条能解决我遇到的问题。

绝望

看了很长时间也没发现哪不对,于是我开始玄学了。

  • 难道是在 .rc 中不能加重复包含的保护?不应该啊,在其它地方看到过类似的用法,还特意模仿着写的。不管三七二十一了,删除,编译,依然报错。

  • 难道 #include 的头文件路径不对?调整包含路径,依然报错。

  • 难道不能在 .rc 中包含头文件?不应该啊,默认生成的 .rc 就包含了 resource.h 啊。删除,可想而知,依然报错。

  • 难道……

就这样排除了很多种“可能性”,试了很多方案,都没有用。但是得到了一个结论:只要包含这个 .rc 文件,编译就有问题。要不是刚开始在拆分完 .rc 后已经成功运行程序了,我都有些怀疑是不是不支持在同一个工程中使用多个 .rc了。

柳暗花明

实在不知道是哪里出问题了,于是起来接了杯水,在回座位的路上,脑子里突然蹦出来一个想法:双击打开有问题的 .rc 文件看看能不能看出点什么门道来。如下图:

open-rc-by-double-click

嗯?怎么是空的?我的对话框去哪了?(内心一阵窃喜)赶紧打开一个正常的 .rc 文件看看,如下图:

open-normal-rc-by-double-click

该有的都有。手动把正常显示的对话框的内容拷贝到有问题的 .rc 中,看看能否打开,发现居然还是打不开!!!炸了,赶紧通过 beyond compare 对比看看。

compare-two-rc-files

虽然提示 Difference not found,但是如果仔细看的话,两边的编码是不同的。为了进一步确认,于是打开 Hex Editor 查看文件头,果然不一样。hex-compare-bom-header

正常的 .rc 文件是按小端存储的—— FF FE,也就是UTF16 LE with Bom。有问题的 .rc 文件是按大端存储的—— FE FF,也就是 UTF16 BE With Bom

知道了原因,于是赶紧修改有问题的 .rc 文件为小端模式.

警告:

不能直接修改文件头,否则打开时文件内容不对。可以使用 notepad.exe 打开有问题的 .rc 文件,然后另存为的时候选 UTF16 LE 即可。

save-as-utf16-le

再次双击打开发现可以正常打开了,并且编译也没问题了。特地贴一张编译通过的截图,我太难了。

compile-success

又遇错误

本以为编译都通过了,应该就万事大吉了吧,没想到运行的时候直接崩溃了。我也要崩溃了。

startup-fail

说明:
因为我设置了 windbgJIT 调试器,所以程序崩溃后直接到 windbg 中了。

vs 中按 F10 启动,单步跟踪,很快就发现是获取对话框的时候失败了。

debug-startup-fail

看来又是 .rc 问题,难道生成的程序的资源段有问题了?使用 CFF Explorer 打开程序查看资源段,如下图:

view-pe-resource-segment

果然,程序资源段中没有任何对话框,难怪查找对话框的时候找不到。看来跟刚才的 .rc 是一个问题,查看对应的 .rc 文件的文件编码,果然是一样的问题。修改文件编码,然后重新生成,启动后终于正常了。

总结

这次的折腾真是一波三折,有点意思。几个关键点总结如下:

  • 如果想在 resource.h#include 自定义的头文件,务必确保自定义头文件结尾多留一个空行,同样的道理,如果一个 .rc 文件包含在另外一个 .rc 文件中,也务必在结尾多留一个空行。
  • .rc 文件要求是UTF-16 LE With Bom 格式的,一定不能搞错。 p.s. 在查资料过程中,发现也可以指定为 UTF8 格式,但是我没成功。
  • CFF Explorer 真的是查看 PE 的好帮手,你值得拥有。
  • 程序是最讲道理的,对就是对,不对就是不对。遇到诡异问题,一定不能迷信,也不能瞎猜。

参考资料

未完待续

虽然问题解决了,但是为什么 .rc 文件的编码变了?而且还变错了。会是 gitbug 吗?

敬请期待后续文章……

BianChengNan wechat
扫描左侧二维码关注公众号,扫描右侧二维码加我个人微信:)
0%