VC++编译说明
目录
第1章编译步骤
使用Visual C++ 6.0新建一个MFC程序,然后编译。IDE(集成开发环境)下,可以看到编译信息:
图1.1 VC++6.0编译信息
可见其编译的基本步骤为:
1、删除项目的临时文件和输出文件;
2、编译资源,即编译rc文件,生成res文件;
3、预编译头文件,生成pch文件;
4、编译各个源文件(*.c、*.cpp、*.cxx),生成obj文件;
5、链接。具体的就是将obj、res文件合成为一个exe文件,中间可能还需要一些Lib文件。
注意:EVC3.0和EVC4.0的编译步骤与VC++6.0完全一致。VC++6.0之后的版本,其编译步骤发生了变化。第2步的编译资源被移至第4步之后。
从上面的编译步骤可以看到,编译时并没有编译头文件(*.h)。这样是否就意味着不需要头文件了?答案当然是否定的。事实上在编译源文件时,预编译器首先处理#include指令,它会将头文件包含至源文件后再进行编译。编译器是以这种方式使用头文件的,认识到这一点对于理解C/C++编译至关重要。
第2章编译源文件
VC++6.0里编译就是调用C:\Program Files\Microsoft Visual Studio\VC98\Bin\cl.exe将源文件(*.c、*.cpp、*.cxx)编译为obj文件。
2.1 编译器
假定1.cpp里有如下代码:
int a = 0; ++a; int b = a + 1; |
显然编译它是没问题的,但是把1.cpp改名为1.c,再次编译就会出问题。为什么?原因很简单:源文件的扩展名为c时,编译器按C语言的语法进行编译,生成C代码;源文件的扩展名为cpp或cxx时,编译器按C++语言的语法进行编译,生成C++代码。C和C++语法还是有一定差别的。
大家都知道C++支持函数重载,如下面两个函数在C++里是允许的:
int fun(); int fun(int a); |
但在C语言里是不允许的。为什么呢?因为C语言编译fun时不会将其更名,一个obj文件里是不能有两个同名函数的。C++语言就不同了,会将这两个函数自动更名。VC++6.0里将fun()更名为?fun@@YAHXZ,将fun(int a)更名为?fun@@YAHH@Z,程序员眼里的一个函数fun在编译器眼里仍是两个。
这样就引来一个问题:如果一个函数funA在.c文件里,而程序在.cpp里调用了funA函数会出现什么情况?答案是无法调用:因为funA在obj里的名字就是funA,但cpp要连接的funA已经被更名了。为了在cpp里调用c函数,函数声明时需要使用extern "C"语句。如下面代码。
extern "C" int fun(); extern "C" { int funcA(); int funcB(); } |
在C和C++混合编程的时候一定要注意这个问题。
2.2 包含头文件
编译源文件时,若遇到 #include "A.h" 语句,则编译器对头文件的搜索顺序如下:
1、#include语句所在文件的目录
如:C:\VC\1.c 里有 #include "A.h",则编译器会在1.c所在目录查找A.h,即在C:\VC下查找A.h;
如:C:\VC\1.c 里有 #include "../B.h",则编译器会在1.c所在目录查找../B.h,就是查找C:\VC\..\B.h,即C:\B.h。这里的..表示相对路径,即上一级目录。#include "../B.h"里的斜杠/可以换成反斜杠\,不过因为在双引号里\表示转义字符,所以需要两个反斜杠,即:#include "..\\B.h"。以一个斜杠代替两个反斜杠完全是为了简化;
说明:相对路径可通过vcHelper程序获取,这样较为方便。
2、上一级包含文件所在目录
如:C:\VC\1.c 里有 #include "A.h"。编译器在D:\Test目录下找到了A.h。这个A.h里又有#include "B.h",显然编译器会首先在A.h所在目录D:\Test下查找B.h,如果找不到则会在C:\VC\1.c所在目录继续查找。
3、Additional include directories 所列目录下依次进行查找
Additional include directories的设置仅对本项目有效,具体请参考图2.1或图2.2。VC++6.0的多个路径之间请以逗号分隔。路径可以使用绝对路径也可以使用相对路径。如:C:/VC,../Share,./Inc表示了三个目录:
C:\VC 绝对路径
..\Share 相对于项目文件(dsp、vcp、vcproj)的相对路径
.\Inc 相对于项目文件(dsp、vcp、vcproj)的相对路径
搜索头文件时,先搜索C:\VC目录,再搜索..\Share目录,最后搜索.\Inc目录。
图2.1 VC++6.0 的Additional include directories
图2.2 VC++7.0 的Additional include directories
4、在Standard Include Paths里依次进行查找
Standard Include Paths的设置对所有VC++项目都有效。
VC++6.0下,单击【Tools】菜单下的【Option】菜单项,然后进入Directories页面,如下图所示。
图2.3 VC++6.0 的Standard Include Paths
VC++7.0下,单击【Tools】菜单下的【Option】菜单项。显示界面如下图所示。
图2.4 VC++7.0 的Standard Include Paths
以上讨论的是#include ""语句,如果是 #include <>,则对头文件的查找顺序将是上面的第 3、4步。
建议:
1、如果是自己写的头文件,请使用#include ""语句,并使用相对路径;
2、如果是系统的头文件,如:stdio.h,请使用#include <>语句。
2.3 重复包含
假如1.cpp里包含了头文件A.h和B.h,这两个头文件又都包含了C.h。显然编译1.cpp时,C.h将被包含两次。C.h里的结构、类定义也会出现两次,编译器会认为这些结构、类被重复定义了,从而导致编译失败。
解决办法有两个
1、增加#ifndef ... #define ... #endif
如:在C.h里增加如下代码:
//C.h #ifndef __C_H__ #define __C_H__ //以下是C.h的文件内容 ... ... ... //最后别忘了#endif #endif //__C_H__ |
编译器编译1.cpp,第二次包含C.h的时候,因为__C_H__已经被定义了,因此会舍弃第二次包含的内容。
该方法要保证每个头文件里的__C_H__都不相同,否则会导致某两个头文件不能同时被包含。
2、增加#pragma once语句
这个方法比较简单,直接在C.h里增加#pragma once即可。美中不足的是:并不是所有的编译器都支持这条指令。所以,采用其它C/C++编译器的时候要注意这一点。
2.4 预编译头文件
假如有多个源文件都包含了windows.h文件。这个头文件非常大,每次编译都比较耗时。有几个源文件包含了它,它就要被编译几次。显然这样的编译效率是相当低的。能不能只编译一次这样的头文件?答案是肯定,那就是使用预编译头文件。即在编译源文件之前,先把windows.h编译出来生成pch文件。在编译源文件的时候,编译器遇到windows.h时将不再编译,而是直接使用pch文件。这个pch就是预编译头文件。
2.4.1 创建
正如前面所说的,编译器是不会直接编译头文件的。因此要预编译头文件,需要指定一个源文件来完成编译并生成pch文件。这里假定要预编译windows.h。请创建一个源文件,如:pch.cpp。其内容很简单,就是#include <windows.h>。然后设置pch.cpp的属性,使其创建pch文件。VC++6.0的设置界面如下:
图2.5 VC++6.0创建预编译头文件设置
在VC++7.0的Solution Explorer里,右键单击pch.cpp,然后单击【Properties】菜单项,设置界面如下图所示
图2.6 VC++7.0创建预编译头文件设置
2.4.2 使用
创建PCH文件后,需要设置其它的源文件使用它。
VC++6.0的设置界面如下。
图2.7 VC++6.0使用预编译头文件设置
在VC++7.0的Solution Explorer里,选择要使用预编译头文件的源文件,然后右键单击某一选中文件,或直接右键单击项目然后单击【Properties】菜单项,设置界面如下图所示。注意下图里的my.pch要和图2.6的保持一致,即:创建什么pch文件就使用什么pch文件。
图2.8 VC++7.0使用预编译头文件设置
设置多个源文件的属性与设置项目属性的区别:项目就好像国家,源文件就好像各个省。国家出台了政策后,各个省要遵循;但是各个省可以根据自己的情况对政策进行修改。一个源文件的某个属性没有进行设置,则它使用项目的该属性。项目的属性改变后,这个源文件的属性也随之改变。但是如果单独对这个源文件的属性进行了设置,则项目属性的改变对它没有影响。
2.4.3 说明
需要说明以下几点:
1、VC++7.0项目里可以使用多个预编译头文件;VC++6.0项目使用多个预编译头文件好像比较困难;
2、创建MFC程序后,预编译头文件设置已经完成,一般无需再设置;
3、一个源文件一旦使用了预编译头文件A.h,则该源文件的第一行一定要包含该头文件。换句话说#include "A.h"之前的代码将被忽略;
4、编译预编译头文件出错后,很难调试。因为不知道编译到哪一行出错了?解决方法就是使用#error或#pragma message跟踪编译进度,精确定位编译出错的位置。
第3章编译资源
Windows程序需要资源,如:图标、光标、对话框……VC++6.0里编译资源就是调用C:\Program Files\Microsoft Visual Studio\COMMON\MSDev98\Bin\rc.exe将资源文件(*.rc)编译为res文件。连接程序负责将res文件嵌入exe文件。
3.1 编译
使用记事本创建资源文件1.rc,其内容如下所示。只有一个 ID 为 100 的字符串。注意:1.rc的编码必须为ANSI。
STRINGTABLE DISCARDABLE { 100 "This is a string" } |
在DOS命令窗口,执行如下命令
"C:\Program Files\Microsoft Visual Studio\COMMON\MSDev98\Bin\rc.exe" C:\1.rc |
rc.exe将编译1.rc,并生成1.res。可以使用VC++6.0打开1.res,查看资源内容。
3.1.1 输出文件
默认情况下,1.res与1.rc在同一目录。可以通过/fo命令开关更改输出文件,如下命令将更改输出文件为D:\t.res。
"C:\Program Files\Microsoft Visual Studio\COMMON\MSDev98\Bin\rc.exe" /fo"D:\t.res" C:\1.rc |
在集成开发环境下,Resource file name 对应的就是/fo命令开关。集成开发环境编译时,会将dsp、vcproj文件所在目录设置为当前目录,所以如果Resource file name不是绝对路径,那么它就是相对dsp文件目录的相对路径。
图3.1 VC++6.0资源设置
图3.2 VC++7.0资源设置
3.1.2 语言
将1.rc文件修改成如下内容:
STRINGTABLE DISCARDABLE { 100 "这是一个字符串" } |
使用VC++6.0打开编译生成的res文件,会发现字符串是乱码。因为rc.exe默认使用的语言是美国英语,语言ID为0x409。需要将语言ID更改为简体中文的0x804。具体命令如下:
"C:\Program Files\Microsoft Visual Studio\COMMON\MSDev98\Bin\rc.exe" /fo"D:\t.res" /l804 C:\1.rc |
命令开关/l804相当于在1.rc的第一行插入如下语句:
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
即明确指定语言为简体中文。
如果1.rc的第一行为 LANGUAGE 9,1。即已经指定语言为美国英语,则命令开关/l804是不起任何作用的。
在集成开发环境下,可以通过修改"Language"或"Culture"修改/l命令开关。
3.2 include语句
假定资源文件1.rc里有语句#include "1.rc2",而1.rc2里又有语句#include "2.rc2"。按如下命令进行编译:
图3.3 编译资源
则查找2.rc2的顺序如下:
1、包含它的文件(1.rc2)所在目录;
2、1.rc2被1.rc包含,所以如果上一步未找到,继续在1.rc所在目录查找;
3、在当前目录查找。上图中,当前目录就是C:\User\yhf;
4、由命令开关/i指定的目录。上图中,有两项/i。会依次在D:\inc1、D:\inc2中查找。集成开发环境中,可以设定这些目录。
5、如果在集成环境编译,还会在设置的Include目录中查找。至于它的实现,笔者猜测:集成开发环境调用rc.exe时,将Include目录添加至/i命令开关。
3.3 引用文件
有些资源,如图标,其实就是引用的图标文件,如下面的语句:
IDR_MAINFRAME ICON DISCARDABLE "res\\t6.ico" |
假如上面一行语句位于2.rc2,那么查找res\t6.ico的顺序如下:
1、被编译资源文件所在目录。注意:rc.exe不会查找2.rc2和1.rc2所在目录,它会认为这条语句位于被编译的资源文件(1.rc)内。
2、当前目录;
3、由命令开关/i指定的目录。上图中,有两项/i。会依次在D:\inc1、D:\inc2中查找。集成开发环境中,可以设定这些目录。
4、如果在集成环境编译,还会在设置的Include目录中查找。
第4章连接
VC++6.0里连接就是调用C:\Program Files\Microsoft Visual Studio\VC98\Bin\link.exe将编译生成的obj、res合成为一个exe文件或dll文件。
连接的时候可能还需要其他程序员生成的obj文件和lib文件,使用这些文件的方法有三种。
1、增加该文件至VC项目
图4.1 增加文件至项目
该方法的优点就是简单,缺点是不灵活。如这个方法很难处理这个要求:对于Debug版本需要连接TestD.lib,对于Release版需要连接Test.lib。
2、修改项目设置
首先增加文件,如图4.2和图4.3所示。
如果需要,还可以指定 Additional library path。如图4.2和图4.4所示。VC++6.0连接程序时,首先在项目目录(即dsp、vcp、vcproj文件所在目录)下查找 Func.obj 和 Test.lib,其次在Additional library path下查找,最后在标准目录下查找。标准库文件目录的设置请参考图2.3和图2.4。
图4.2 VC++6.0增加库文件及设置库文件目录
图4.3 VC++7.0增加库文件
图4.4 VC++7.0设置库文件目录
3、在头文件或源文件中,插入如下代码:
#pragma comment(lib, "Test.lib") //连接时使用 Test.lib。
接着指定 Additional library path,请参考方法2里的描述。连接器查找 Test.lib 时,其查找顺序与第 2 种方法相同。
这种方法非常灵活,请见下面的代码。其含义为:Debug版本下连接TestD.lib,Release版本下连接Test.lib。
#ifdef _DEBUG #pragma comment(lib, "TestD.lib") #else #pragma comment(lib, "Test.lib") #endif |
说明:
1、这种方法不能指定obj文件;
2、#pragma comment还可指定绝对路径和相对路径,但是建议不要这样做。
第5章其它
5.1 编译目录
编译、连接的时候会产生大量的文件,可以指定这些文件的存放路径。
临时目录用来存放编译过程中产生的文件,如:pch、obj、res文件。输出目录用来存放最终生成的exe或dll文件。这两个目录的设置如下图所示。注意它们可以是绝对路径,也可以是相对于dsp、vcp、vcproj文件的相对路径。
图5.1 VC++6.0临时、输出目录设置
图5.2 VC++7.0临时、输出目录设置
建议将临时、输出目录设置为Temp,便于查找、删除这些文件。
还可以指定目标文件,请参考下图。
图5.3 VC++6.0设置目标文件
图5.4 VC++7.0设置目标文件
5.2 编译事件
编译的时候有三个事件:
1、Pre-Build
开始编译、连接的时候发生该事件。VC++7.0以上的版本才支持此事件。
2、Pre-Link
编译完毕,准备连接的时候发生该事件。
3、Post-Build
编译、连接都结束的时候发生该事件。
假如需要在程序编译完成的时候将exe文件移动到c:\,可在Post-Build里增加DOS命令:move $(TargetPath) c:\。具体的设置请参考下图。
图5.5 VC++6.0增加编译后处理命令
图5.6 VC++7.0增加编译后处理命令
$(TargetPath)是编译时产生的环境变量,其它的环境变量及含义见下表
环境变量 |
说明 |
$(MSDEVDIR) |
Microsoft Developer目录,如:C:\Program Files\Microsoft Visual Studio\COMMON\MSDev98 |
$(IntDir) |
临时目录 |
$(OutDir) |
输出目录 |
$(TargetPath) |
目标文件,包括路径 |
$(TargetDir) |
目标文件所在目录 |
$(TargetName) |
目标文件名,不包括路径和扩展名 |
5.3 自定义编译
有没有想过在VC++6.0里编译VC#代码?有了Custom Build一切皆有可能。假定有如下VC#代码,存在于文件1.cs。
using System; using System.Windows.Forms; static class Program { static void Main() { MessageBox.Show("Hello VC#"); } } |
请将1.cs添加至VC++6.0工程,并设置其Custom Build属性:
Commands有两条命令,第一行为:
C:\WINDOWS\Microsoft.NET\Framework\v3.5\Csc.exe /out:$(InputDir)\$(InputName).exe $(InputPath)
其实就是调用csc程序,编译1.cs文件,生成1.exe文件。环境变量$(InputPath)就是1.cs相对于dsp文件的路径(包括文件名)。环境变量$(InputDir)是$(InputPath)的目录部分。环境变量$(InputName)是1.cs的文件名,不包括扩展名,其实就是1了。$(InputDir)\$(InputName).exe其实就是与1.cs在同一目录下的文件1.exe。
第二行命令为$(InputDir)\$(InputName).exe,其实就是编译结束后运行这个程序。
Outputs里为输出文件,这里就是$(InputDir)\$(InputName).exe了。
编译后,可以看到运行结果。
图5.7 编译VC#
可以猜测到Custom Build的编译步骤:
1、删除输出文件,这里就是1.exe;
2、设置当前目录为dsp所在目录;
3、设置环境变量;
4、调用Commands里面的命令。命令的输出全部被重定向至VC++6.0的Output窗口,这样就可以在IDE里查看编译结果。
5.4 预定义宏
对于VC++而言,预定义宏分为两大类:ANSI C 预定义宏、微软C++预定义宏。前者被绝大多数C++编译器支持,后者只被微软的VC++支持。
5.4.1 ANSI C 预定义宏
宏名 |
说明 |
示例 |
__DATE__ |
文件的修改日期(本地时间)。asctime的返回值 |
"Oct 31 2009" |
__FILE__ |
文件名,ANSI编码。编译时/FC控制是否含目录。 下面的__WFILE__将是Unicode编码的文件名 #define WIDEN2(x) L ## x #define WIDEN(x) WIDEN2(x) #define __WFILE__ WIDEN(__FILE__) |
"c:\\vcproj\\a.c" |
__LINE__ |
所在的行号,是10进制的整数。可以被#line预编译指令修改 |
5 |
__STDC__ |
表示编写的C代码必须符合ANSI C标准。不能编译C++代码。 |
|
__TIME__ |
文件的修改时刻(本地时间) |
"12:48:06" |
__TIMESTAMP__ |
文件的修改日期时刻(本地时间) |
"Sat Oct 31 12:48:06 2009" |
5.4.2 微软C++预定义宏
宏名 |
说明 |
_ATL_VER |
ATL版本 |
_CHAR_UNSIGNED |
char为无符号。指定/J后,该宏被定义 |
__CLR_VER |
CLR(common language runtime)版本,其格式为Mmmbbbbb M 是主版本号 mm 是次版本号 bbbbb 是编译版本号 |
__cplusplus_cli |
Defined when compiling with /clr, /clr:pure, or /clr:safe. Value of __cplusplus_cli is 200406. __cplusplus_cli is in effect throughout the translation unit. |
__COUNTER__ |
从0开始,出现一次增加1。可以用来产生读一无二的变量名: // pre_mac_counter.cpp #include <stdio.h> #define FUNC2(x,y) x##y #define FUNC1(x,y) FUNC2(x,y) #define FUNC(x) FUNC1(x,__COUNTER__)
int FUNC(my_unique_prefix); int FUNC(my_unique_prefix); int main() { my_unique_prefix0 = 0; printf_s("\n%d",my_unique_prefix0); my_unique_prefix0++; printf_s("\n%d",my_unique_prefix0); } |
__cplusplus |
如果使用C++编译器,则该宏被定义 |
_CPPLIB_VER |
Defined if you include any of the C++ Standard Library headers; reports which version of the Dinkumware header files are present. |
_CPPRTTI |
编译开关/GR被打开时,该宏被定义,表示支持运行时类型信息Run-Time Type Information |
_CPPUNWIND |
Defined for code compiled with /GX (Enable Exception Handling). |
_DEBUG |
Defined when compiling with /LDd, /MDd, and /MTd. |
_DLL |
Defined when /MD or /MDd (Multithread DLL) is specified. |
__FUNCDNAME__ |
Valid only within a function and returns the decorated name of the enclosing function (as a string). __FUNCDNAME__ is not expanded if you use the /EP or /P compiler option. |
__FUNCSIG__ |
Valid only within a function and returns the signature of the enclosing function (as a string). __FUNCSIG__ is not expanded if you use the /EP or /P compiler option. On a 64-bit operating system, the calling convention is __cdecl by default. |
__FUNCTION__ |
Valid only within a function and returns the undecorated name of the enclosing function (as a string). __FUNCTION__ is not expanded if you use the /EP or /P compiler option. |
_INTEGRAL_MAX_BITS |
Reports the maximum size (in bits) for an integral type. |
_M_ALPHA |
Defined for DEC ALPHA platforms (no longer supported). |
_M_CEE |
Defined for a compilation that uses any form of /clr (/clr:oldSyntax, /clr:safe, for example). |
_M_CEE_PURE |
Defined for a compilation that uses /clr:pure. |
_M_CEE_SAFE |
Defined for a compilation that uses /clr:safe. |
_M_IX86 |
Defined for x86 processors. See Values for _M_IX86 for more details. _M_IX86 = 300 /G3 80386 _M_IX86 = 400 /G4 80486 _M_IX86 = 500 /G5 Pentium _M_IX86 = 600 /G6 Pentium Pro, Pentium II, and Pentium III _M_IX86 = 600 /GB Blend(Default. Future compilers will emit a different value to reflect the dominant processor.) |
_M_IA64 |
Defined for Itanium Processor Family 64-bit processors. |
_M_IX86_FP |
Expands to a value indicating which /arch compiler option was used: 0 if /arch was not used. 1 if /arch:SSE was used. 2 if /arch:SSE2 was used. See /arch (Minimum CPU Architecture) for more information. |
_M_MPPC |
Defined for Power Macintosh platforms (no longer supported). |
_M_MRX000 |
Defined for MIPS platforms (no longer supported). |
_M_PPC |
Defined for PowerPC platforms (no longer supported). |
_M_X64 |
Defined for x64 processors. |
_MANAGED |
Defined to be 1 when /clr is specified. |
_MFC_VER |
MFC的版本 0x0600 VC++6.0/EVC3.0/4.0 0x0700 VC++7.0(VC2002) 0x0710 VC++7.1(VC2003) 0x0800 VC++8.0(VC2005) 0x0900 VC++9.0(VC2008) |
_MSC_EXTENSIONS |
This macro is defined when compiling with the /Ze compiler option (the default). Its value, when defined, is 1. |
_MSC_VER |
VC++的版本 1100 VC++5.0 1200 VC++6.0/EVC3.0 1201 EVC4.0 1300 VC++7.0(VC2002) 1310 VC++7.1(VC2003) 1400 VC++8.0(VC2005) 1500 VC++9.0(VC2008) |
__MSVC_RUNTIME_CHECKS |
Defined when one of the /RTC compiler options is specified. |
_MT |
Defined when /MD or /MDd (Multithreaded DLL) or /MT or /MTd (Multithreaded) is specified. |
_NATIVE_WCHAR_T_DEFINED |
Defined when /Zc:wchar_t is used. |
_OPENMP |
Defined when compiling with /openmp, returns an integer representing the date of the OpenMP specification implemented by Visual C++. |
_VC_NODEFAULTLIB |
Defined when /Zl is used; see /Zl (Omit Default Library Name) for more information. |
_WCHAR_T_DEFINED |
Defined when /Zc:wchar_t is used or if wchar_t is defined in a system header file included in your project. |
_WIN32 |
Defined for applications for Win32 and Win64. Always defined. |
_WIN64 |
Defined for applications for Win64. |
_Wp64 |
Defined when specifying /Wp64. |
UNDER_CE _WIN32_WCE |
WinCE的版本 |
5.4.3 应用实例
下面的预处理指令将区分EVC++3.0、EVC++4.0、VC++6.0、VC++9.0
#ifdef _WIN32_WCE //智能设备
#if _MSC_VER==1200 //EVC3.0
#elif _MSC_VER==1201 //EVC4.0
#elif _MSC_VER==1500 //VC++9.0,即VC2008
#endif
#else
#if _MSC_VER==1200 //VC++6.0
#elif _MSC_VER==1500 //VC++9.0,即VC2008
#endif
#endif
5.5 预处理器操作符
5.5.1 #
增加双引号,如下面的预编译指令
#define STR(x) #x
编译时,STR(123)将被预处理器替换为 "123"
5.5.2 #@
增加单引号,如下面的预编译指令
#define CHR(x) #@x
编译时,CHR(A)将被预处理器替换为 'A'
5.5.3 ##
连接字符串,如下面的预编译指令
#define CAT(x,y) x##y
编译时,CAT(ABC,123) 将被预处理器替换为 ABC123
5.5.4 defined
必须与条件编译语句#if……#elif……#endif一起使用。
如:#ifdef _WIN32_WCE 与 #if defined(_WIN32_WCE) 等价。
如:#if defined(_WIN32_WCE) && _MSC_VER==1200 表示 EVC3.0 编译器。