坑太多兮,老衲 one by one 地填

我司给客户开发客户端工具,号称「跨平台」,其实,在此之前,只是跨了两个平台:windowslinuxmac 平台最近因为小明的加盟才得以支持,蛤蛤。

linux 下跨平台倒好办:主流的 Debian 系的下面的 deb 包和 RedHat 系的 rpm 包都可以很方便地编译、打包。

mac 平台下也容易折腾,和 linux同宗同源,都是自己人,移植下来也不是难事。

恶心我的是 windows 编译环境。

windows 下面的编译工具用的是 mingw,呵呵,大名鼎鼎的 mingw。But,这套编译环境真的是 too old:系统是 xp sp21/11/2006 年装的系统(都满十周岁了耶),一个 512MB 内存的虚拟机,不知道哪位前辈在 2009 年装完 mingwC 盘只剩下 700 多 MB 了,不能升级系统到 sp 3,不能升级 mingw,不然硬盘空间不足,升级失败,回滚,小可屡败屡试、屡试不爽。

软硬件条件如此之差,以至于都不能装个像样版本的 svn,用 SSH 图形界面拷贝文件还会显示乱码。我忍了,还好我只拿这东西编译下 windows 平台的命令行客户端,每次编译完、用 SSH copy 到别的开发机器,copy 完走人,连眼珠子都不转过去一下,真的是太 ugly 、太 disgusting 了。

为什么不重新搞一套 mingw 编译环境?一直以来都有这股冲动,最近闲了几天,终于下定决心,把这个坑填了。

考虑到兼容性,系统这次还是选择了 winsdows xp,不过是 sp 3 了, 首先来点体力活,重新装个 mingw,从 3.4.5 升级到 4.5.2

接下来,正式开始编译,出问题了:

坑之一

1
2
3
4
5
6
In file included from ../include/jpeglib.h:30:0,
from dna_gen.thread.cpp:33:
../include/jmorecfg.h:229:13: error: conflicting declaration 'typedef int boolean'
c:\mingw\bin\../lib/gcc/mingw32/4.5.2/../../../../include/rpcndr.h:52:23: error:
'boolean' has a previous declaration as 'typedef unsigned char boolean'
make: *** [thread] Error 1

好熟悉的编译 Error:类型声明冲突

由于 #include <windows.h>, 这个头文件经过几层 include 后会引入 rpcndr.h 头文件,人家在第 52 行已经声明 booleanunsigned char 了:

1
2
3
4
51: typedef unsigned char byte;
52: typedef unsigned char boolean;
53: #define NDRSContextValue(hContext) (&(hContext)->userContext)
54: #define cbNDRContext 20

然后该 .cpp 文件又在 windows.h#include "jpeglib.h" ,而 jpeglib.h 又引入 jmorecfg.h,这个头文件的第 229 行又试图再次声明 booleanint

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
221:/*
222: * On a few systems, type boolean and/or its values FALSE, TRUE may appear
223: * in standard header files. Or you may have conflicts with application-
224: * specific header files that you want to include together with these files.
225: * Defining HAVE_BOOLEAN before including jpeglib.h should make it work.
226: */
227:
228: #ifndef HAVE_BOOLEAN
229: typedef int boolean;
230: #endif
231: #ifndef FALSE /* in case these macros already exist */
232: #define FALSE 0 /* values of boolean */
233: #endif
234: #ifndef TRUE
235: #define TRUE 1
236: #endif

原因找到了,解决方法也很简单:按照jmorecfg.h221~226行的注释,如果在#include "jpeglib.h"之前已声明了 boolean 类型的话,需要在前面加宏 #define HAVE_BOOLEAN

坑之二

1
2
3
4
5
6
7
8
In file included from ../include/afp_wrap.h:18:0,
from dna_gen.thread.cpp:30:
../include/afp.h:170:2: error: expected identifier before '(' token
../include/afp.h:170:2: error: expected '}' before '(' token
../include/afp.h:170:2: error: expected ')' before numeric constant
In file included from dna_gen.thread.cpp:30:0:
../include/afp_wrap.h:116:1: error: expected declaration before '}' token
make: *** [thread] Error 1

莫名其妙的 Error。查看了 afp.h 并没有所说的 (){} 括号不匹配的情况啊?初步怀疑是经过预处理后,头文件被#define指令篡改了。为了验证此想法,使用 g++ ... -E -o 生成经过预处理后的源文件。果然,有这么段:

1
2
3
4
5
enum error_type{
((HRESULT)0x00000000L) = 0,
GENERICERROR = -1,
MEMORYERROR = -2
};

对应的头文件afp.h170 行附近内容:

1
2
3
4
5
169: enum error_type{
170: NOERROR = 0,
171: GENERICERROR = -1,
172: MEMORYERROR = -2
173:};

看样子是 NOERROR被其他 define 指令替换成 ((HRESULT)0x00000000L) 了。

在 windows 系统中,NOERROR是系统的一个预定义符号。该.cpp文件首先 #include <windows.h>,该头文件又会引入 winerror.h,在此头文件第 1888~1913行:

1
2
3
1888: #define NOERROR S_OK
...
1913: #define S_OK ((HRESULT)0x00000000L)

因此, NOERROR —> ((HRESULT)0x00000000L)

由于先引入了windows.h,再引入afp_wrap.h,导致 #include <windows.h> 这行代码后面所有被引入的文件都会将 NOERROR 替换成 ((HRESULT)0x00000000L) 了,这就是 ((HRESULT)0x00000000L) = 0,的原因。

可以看出,当年写这个头文件的人有两大问题:

  • 对 windows 不熟悉。NOERRORwindows 系统的预置常量啊,亲~
  • 不专业。一般在头文件中声明常量名都会加个公司名或项目代号之类的前缀,就是未来避免和系统中已存在或其它项目中的常量名同名。

解决办法:我是不敢乱改 afp.h 的这位 Zorro “大神”的代码哦~To make compiler happy,姑且先#include "afp_wrap.h",再#include <windows.h>

坑之三

还有些小坑,都 one by onefix 了~

彦祖老师 wechat