缘起
最近这些天,我一有时间就捣鼓 .rc
。用 git
管理过 .rc
文件的小伙伴儿应该都知道,.rc
文件会被 git
默认当作二进制文件来管理。每次合并代码的时候,只要涉及到 .rc
文件的变更,很大概率会报冲突,而且工具不能很好的解决,只能手动解决。为了解决这个问题,我想到了两个方案:
- 让
git
把.rc
文件当成普通文件来管理。 - 把一个大的
.rc
文件拆成多个小的.rc
文件,这样可以有效的降低冲突的概率。
这两个方案相互之间没有任何冲突,所以这次折腾的时候,这两个方案是同时进行的。本以为没什么值得写的,没想到收获了意外惊喜,于是有了本篇总结。
拆分 rc
按照微软官方文档 TN035: Using Multiple Resource Files and Header Files with Visual C++ 的指示,很快就拆分好了。满心欢喜的准备编译运行,验收成果。
没想到……
初遇错误
编译很快结束了,不过 vs
报告了下图所示的编译错误:
乍一看有一点懵,不过仔细看文件内容后知道怎么回事了。原来这是一个“老朋友”了,之前遇到过。不是什么大问题,只需要在后面多加一个空行就行了。
温馨提示:
如果在
resource.h
中包含自定义的头文件时,也需要在被包含的头文件的末尾加一个空行。否则也会报错。
在末尾加好空行后,再次编译,成功,正常运行。
解决了在同一工程中使用多个 .rc
的问题,下一个问题是让 git
不再把 .rc
文件当成二进制文件来管理。默认情况下,.rc
文件会被当成 binary file
来管理的,当 .rc
中有差异的时候,在命令行中执行 git diff
命令,不能显示出有意义的信息。
修改 git 设置
按照 gitattribtes帮助文档 中的说明,我在项目根目录建立了 .gitattributes
文件,把 .rc
和 .rc2
文件都当成 UTF-16
来管理,这样再冲突的时候就可以比较差异了。
说明:
因为现有项目中的
.rc
文件都被当成了二进制文件,所以我在.gitattributes
中末尾的位置把旧的.rc
和.rc2
文件强制设置为了binary
。
设置好 .gitattributes
文件后,手动修改 .rc
文件内容,然后执行 git diff
查看差异。结果如下图,这次显示的差异简直完美。以说明新增加的设置是生效的。
本以为本次折腾就这么顺利的结束了,可以打扫战场,编写操作文档,收工了。
没想到……
高兴的太早了
撤销完变更后,编译,居然出现了下图中的错误:
这是什么错误?一脸懵逼,难道上面哪个地方多写了 #endif
导致不匹配了?赶紧把改动过的文件都检查一遍,尤其是刚刚改动过的文件。
每个文件都反反复复看了好几遍,什么问题都没看出来。#if
和 #endif
都是成对儿的。这可如何是好,网上搜索 error RC1020
,没有一条能解决我遇到的问题。
绝望
看了很长时间也没发现哪不对,于是我开始玄学了。
难道是在
.rc
中不能加重复包含的保护?不应该啊,在其它地方看到过类似的用法,还特意模仿着写的。不管三七二十一了,删除,编译,依然报错。难道
#include
的头文件路径不对?调整包含路径,依然报错。难道不能在
.rc
中包含头文件?不应该啊,默认生成的.rc
就包含了resource.h
啊。删除,可想而知,依然报错。难道……
就这样排除了很多种“可能性”,试了很多方案,都没有用。但是得到了一个结论:只要包含这个 .rc
文件,编译就有问题。要不是刚开始在拆分完 .rc
后已经成功运行程序了,我都有些怀疑是不是不支持在同一个工程中使用多个 .rc
了。
柳暗花明
实在不知道是哪里出问题了,于是起来接了杯水,在回座位的路上,脑子里突然蹦出来一个想法:双击打开有问题的 .rc
文件看看能不能看出点什么门道来。如下图:
嗯?怎么是空的?我的对话框去哪了?(内心一阵窃喜)赶紧打开一个正常的 .rc
文件看看,如下图:
该有的都有。手动把正常显示的对话框的内容拷贝到有问题的 .rc
中,看看能否打开,发现居然还是打不开!!!炸了,赶紧通过 beyond compare
对比看看。
虽然提示 Difference not found
,但是如果仔细看的话,两边的编码是不同的。为了进一步确认,于是打开 Hex Editor
查看文件头,果然不一样。
正常的 .rc
文件是按小端存储的—— FF FE
,也就是UTF16 LE with Bom
。有问题的 .rc
文件是按大端存储的—— FE FF
,也就是 UTF16 BE With Bom
。
知道了原因,于是赶紧修改有问题的 .rc
文件为小端模式.
警告:
不能直接修改文件头,否则打开时文件内容不对。可以使用 notepad.exe 打开有问题的 .rc 文件,然后另存为的时候选
UTF16 LE
即可。
再次双击打开发现可以正常打开了,并且编译也没问题了。特地贴一张编译通过的截图,我太难了。
又遇错误
本以为编译都通过了,应该就万事大吉了吧,没想到运行的时候直接崩溃了。我也要崩溃了。
说明:
因为我设置了windbg
为JIT
调试器,所以程序崩溃后直接到windbg
中了。
在 vs
中按 F10
启动,单步跟踪,很快就发现是获取对话框的时候失败了。
看来又是 .rc
问题,难道生成的程序的资源段有问题了?使用 CFF Explorer
打开程序查看资源段,如下图:
果然,程序资源段中没有任何对话框,难怪查找对话框的时候找不到。看来跟刚才的 .rc
是一个问题,查看对应的 .rc
文件的文件编码,果然是一样的问题。修改文件编码,然后重新生成,启动后终于正常了。
总结
这次的折腾真是一波三折,有点意思。几个关键点总结如下:
- 如果想在
resource.h
中#include
自定义的头文件,务必确保自定义头文件结尾多留一个空行,同样的道理,如果一个.rc
文件包含在另外一个.rc
文件中,也务必在结尾多留一个空行。 .rc
文件要求是UTF-16 LE With Bom
格式的,一定不能搞错。 p.s. 在查资料过程中,发现也可以指定为UTF8
格式,但是我没成功。CFF Explorer
真的是查看PE
的好帮手,你值得拥有。- 程序是最讲道理的,对就是对,不对就是不对。遇到诡异问题,一定不能迷信,也不能瞎猜。
参考资料
- https://docs.microsoft.com/en-us/cpp/mfc/tn035-using-multiple-resource-files-and-header-files-with-visual-cpp?view=msvc-160
- https://git-scm.com/docs/gitattributes
未完待续
虽然问题解决了,但是为什么 .rc
文件的编码变了?而且还变错了。会是 git
的 bug
吗?
敬请期待后续文章……