MFC 预处理器相关知识

时间:2022-09-05 20:00:05

预处理器是一种处理源文件文本的文本处理器,它是翻译起始阶段的一个组成部分。

预处理器并不在语法上分析处理源文本,但出于定位宏调用的目的,它将源文本分开语言符号。虽然编译器一般在初次编译时启动预处理器,但预处理器也可以不经编译,单独地处理文本。

Microsoft特殊处

/E/EP编译器选项进行预处理之后,你可以得到一个源代码的列表。在多数情况下,启动预处理器和输出结果文本到输出设备,这两种选项都是控制台指令,这两种选项的区别在于/E包括了#line指令,/EP没有这些指令。

Microsoft特殊处结束

--------------------------------------------------------------------------------

特殊术语

在本书中,名词“参量”指的是传送给一个函数的实体。有时候,它用“actual”或“formal”修饰,它们分别用于表示函数调用时的参量表达式和在函数定义时的参量说明。名词“变量”指的是一种简单的C类型数据对象,名词“对象”指的是C++对象和变量;它是一个含义广泛的名词。

--------------------------------------------------------------------------------

翻译阶段

CC++程序由一个或多个源文件组成,它们都包含了程序的某些文本,一个不包含代码部分的源文件和它的包含文件(#indude预处理器指令包含的文件),若被条件编译指令(比如#if)调用,则称其为一个“转换单元”。

源文件可被翻译多次,翻译过去的文件事实上是很正常的。已经翻译了的翻译单元可保存在单独的对象文件或对象代码库里,这些单个的转换单元可被连接形成一个可执行文件或动态链接库(DLL)

转换单元可采用下列形式通信:

* 调用具有外部连接的函数。

* 调用具有外部连接的类成员函数。

* 直接更改具有外部连接的对象。

* 文件的直接更改。

* 内部外理通信(仅限于基于Microsoft Windows的应用程序)。以下是编译器翻译文件的各个阶段:

字符映射

源文件中的字符被映射为内部源代码的形式。此阶段三字母序列被转换为单字符的内部表现形式。

行拼接

在此阶段,源文件中所有以反斜杠(/)结尾且其后紧跟一换行符的行,将与下一行连接,从而由物理行生成逻辑行。所有非空源文件结束于一个前面没有反斜杠的换行符。

语言符号化

此阶段源文件被分为预处理语言符号和空白字符。源文件中每个注释被用一个空白字符代替。换行符被保留。

预处理

此阶段执行预处理指令并将宏扩展至源文件,#include语句调用对所有包括文本启动前面三个翻译步骤开头的翻译过程。

字符集映射

所有的源字符集成员和转义序列将转换为执行字符集中的等价形式,对于Microsoft CC++来说,源字符集和执行字符集都是ASCII码。

字符串合并

所有相邻的字符串和宽字符文字都将被合并。例如:String”“concatenation”合并为“Stringconcatenation”。

翻译

所有的语言符号将按语法和语义规则进行分析;这些语言符号被转换为目标代码。

链接

此阶段所有的外部引用被分解以生成一个可执行程序或一个动态链接库。

编译器在翻译过程中遇到语法错误时,将发出一个警告或错误信息。

链接器分解所有的外部引用,并把一个或多个分开处理的转换单元和标准库联接起来,以生成一个可执行程序或动态链接库(DLL)

--------------------------------------------------------------------------------

预处理器指令

预处理器指令如#define#ifdef,一般被用在不同的运行环境下,使源程序易于更改和编译。源文件中的指令指示预处理器执行特有的行为。例如,预处理器可替换文本中的语言符号,将其它的文件内容插入源文件中,或移走文本的一部分以抑制文件中某部分的编译。预处理器行在宏扩展之前被识别且执行。不过,如果宏扩展看起来象一个预处理器指令,该命令将不能被预处理器识别。

除转义序列之外,预处理器语句采用与源文件语句相同的字符集。在预处理器语句中的字符集和可执行程序的字符集是一样的。预处理器也可识别负字符值。预处理器可识别如下指令:

#define #error #import #undef

#elif #if #include

#else #ifdef #line

#endif #ifndef #pragma

数字符号(#)是包含预处理器指令的行中的第一个非空白字符。空白字符可出现在数字符号和指令的第一个字母之间。某些指令包含参量和值。指令之后的任何文本(除作为指令一部分的参量或值之外)必须放在单行注释分界符(//)之后或注释分界符(/* */)之间。

预处理器指令可出现在源文件的任何地方,但它们仅用于源文件的剩余部分。

#define指令

可以用#define指令给程序中的常量取一个有意义的名称,其语法的两种形式如下:

语法

#define 标识符 语言符号字符串

opt#define 标识符[(标识符opt,...,标识符opt)]语言符号字符串opt

#define指令用语言符号字符串替换源文件中一个标识符的所有出现,标识符仅在它形成一个语言符号时被替换(参见“Microsoft Visual C++ 6.0参考库”的“Microsoft Visual C++ 6.0语言参考手册”卷的第1章“词法规定”中的“语言符号”)。例如,若标识符出现在一个注释、一个字符串或作为一个长标识符的一部分之中,它就不被替换。

一个不带语言符号字符串的#define指令将移走源文件中每次标识符的出现。标识符保留其定义且能用#defined#ifdef测试。

语言符号字符串参量由一系列语言符号组成,如关键字、常量或完整的语句。一个或多个空白字符可将语言符号字符串和标识符分开。空白字符不会被认为是被替换文本的一部分,文本最后语言符号之后的空白也不会认为是替换文本的一部分。

形式参数名称出现在语言符号字符串中以标志出实际值被替换的位置,每个参数名称可在语言符号字符串中出现多次,且可以以任何次序出现。调用时参量的数目必须与宏定义的参数数目相匹配。圆括号的*运用可确保正确地说明复杂的实际参量。

用第二种语法形式可创建类似函数的宏。这种形式接受一个用圆括号括起的可选参数表。在最初定义之后引用该标识符,可以使用实际参量替代形式参数的语言符号字符串参量的形式,来替换标识符(标识符opt,...,标识符opt)的每次出现。

表中的形式参数必须用逗号隔开。该表中的每个名称都必须是唯一的,且此参量表必须包括在圆括号中,标识符和左边的圆括号之间不能有空格。对于占用多行的长指令可使用行连接,把反斜杠(/)放在换行符前。形式参数名称的范围延伸到结束语言符号字符串的换行符。

当一个宏以第二种语法形式定义时,参量表后的文本实例就构成一个宏调用。在源文件中,一个标识符实例后的实际参量必须与宏定义的相应形式参数匹配。每个语言符号字符串之前无字符串化(#)、字符化(#@)或语言符号粘贴(##)操作符,或其后无##操作符的形式参量,都被相应的实际参量所替换。在指令替换形式参数之前,实际参量中的任何宏都将被扩展(本章之后的“预处理器操作符”中将介绍这些操作符)

以下带参量宏的例子说明了#define语法的第二种形式:

//定义光标行的宏

#define CURSOR(top,bottom) ((top) << 8) | bottom))

//获取指定范围中的一个随机整数的宏

#define getrandom(min,max) /

((rand()%(int)(((max)+1)-(min)))+(min))

有副作用的参量有时会导致宏产生不希望的结果。一个给定的形式参量在语言符号字符串中可能出现多次。如果该形式参数被一个有副作用的表达式所替换,则该表达式及其副作用,可能被求值多次(参见本章后面“语言符号粘贴操作符##”中的例子)

#undef指令可使一个标识符的预处理器定义失效。有关的更多信息参见#undef指令。若一个被定义的宏名称出现在语言符号字符串中(即使是另一个宏扩展的结果),它将不被扩展。

除非第二次定义(#define)宏与原定义完全相同,否则重定义一个已定义过的宏将产生一个错误信息。

Microsoft特殊处

Microsoft C/C++允许一个宏的重定义,但会产生一个警告信息,说明新的定义与原定义相同。ANSI C认为宏的重定义是错误的。例如,下面的宏对C/C++是相同的,但会产生一个警告信息:

#define test(f1,f2) (f1*f2)

#define test(a1,a2) (a1*a2

)Microsoft特殊处结束

这个例子用于说明#define指令:

#define WIDTH 80

#define LENGTH (WIDTH+10)

第一个说明定义标识符WIDTH为整形常量80,且用WIDTH和整形常量10定义LENGTHLENGTH的每次出现都用(WIDTH+10)所替换,接着,WIDTH+10的每次出现都用表达式(80+10)替换。WIDTH+10的圆括号非常重要,因为它们决定着如下语句的解释。

var=LENGTH*20

经预处理后该语句变为:

var=(80+10)*20

求得值为1800,若无括号,结果为:

var=80+10*20其值为280

Microsoft特殊处

在文件开头用/D编译器选项定义宏和常量,和用一个#define预处理指令效果是一样的。能用/D选项定义的宏可达30个。

Microsoft特殊处结束

#error指令

采用error指令可产生编译错误信息。

语法

#error 语言符号字符串

错误消息包括语言符号字符串参量,且从属于宏扩展。这些指令对于检测程序的前后矛盾和预处理时的违犯约束是非常有用的,以下例子说明了预处理时的出错处理:

#if !defined(__cplusplus)

#error C++ complier required.

#endif

当遇到#error指令时,编译终止。

#if#elif#else#endif指令

#if#elif#else#endif指令控制源文件中某部分的编译。如果表达式(#if之后)有一个非0,则紧跟在#if指令之后的行组将保留在转换单元中。

语法

条件的:

if部分 elif部分opt else部分opt endif

if部分:

if

if:

#if 常量表达式

#ifdef 标识符

#ifndef 标识符

elif部分:

elif 文本

elif部分 elif 文本

elif:

#elif 常量表达式

else部分:

else 文本

else:

#else

endif:

#endif

源文件中每个#if指令都必须与最近的一个#endif相匹配。在#if#endif指令之前的#elif指令的数目是不限的,但最多只能有一个#else指令。

#else必须是#endif之前的最后一个指令。#if#elif#else#endif指令可嵌套在其它#if指令的文本部分。每个嵌套的#else#elif#endif指令应属于前面最近的一个#if指令。

所有的条件编译指令,#if#ifdef,必须与文件结束前最近的#endif指令匹配;否则,将产生一个错误消息。当条件编译指令包括在包含文件中时,他们必须满足相同的条件:在包含文件结尾没有不匹配的条件编译指令。

宏替换应在#elif命令后的命令行部分内进行,因此一个宏调用可用在常量表达式中。

预处理器选择一个给定文本的出现之一作进一步的处理。文本中指定的一个块可以是文本的任何序列。它可能占用一行以上。通常该文本是对于编译和预处理器有意义的程序文本。

预处理器处理选择文本并将其传送给编译器。若该文本包含预处理器指令,预处理器将执行这些指令。编译器只编译预处理器选定的文本块。

预处理器通过求值每个#if#elif指令之后的常量表达式直到找到一个为真(0)的常量表达式来选择单个文本项。预处理器选择所有的文本(包括以#开头的其它预处理器指令)直到它关联的#elif#else#endif

如果常量表达式的所有出现都为假,或者如果没有#elif指令,预处理器将选择#else后的文本块。如果#else被忽略,且所有#if块中的常量表达式都为假,则不选择任何文本块。

常量表达式是一个有以下额外限制的整型常量表达式:

* 表达式必须是整型且可以包括整型常量,字符常量和defined操作符。

* 表达式不能使用sizeof或一个类型造型操作符。

* 目标环境不能表示整数的所有范围。

* 在翻译表示中,int类型和long类型以及unsigned int类型和unsigned long类型是相同的。

* 翻译器可将字符常量翻译成一组不同于目标环境的代码值。为了确定目标环境的属性, 应在为目标环境建立的应用程序中检测LIMITS.H的宏值。

* 表达式不需执行所有的环境查询,但需与目标计算机的执行过程细节隔离开。

预处理器操作符defined可用于特殊的常量表达式,语法如下:

语法

defined(标识符)

defined 标识符

若此标识符当前已定义,则该常量表达式被认为是真(0);否则,条件为假(0)。一个定义为空文本的标识符可认为已定义。defined指令只能用于#if#endif指令。

在如下例子中,#if#endif指令控制着三个函数调用中某一个的编译:

#if defined (CREDIT)

credit();

#elif defined(DEBIT)

debit();

#else

printerror();

#endif

若标识符CREDIT已定义,则对于credit的函数调用被编译。若标识符DEBIT被定义,则对于debit的函数调用被编译。若未定义任何标识符,将编译对于printerror的函数调用。

注意,在CC++,CREDITcredit是不同的标识符,因为它们的大小写不一样。

如下例子中的条件编译语句给出了一个名称为DLEVEL的已定义的符号常量:

#if DLEVEL > 5

#define SIGNAL 1

#if STACKUSE == 1

#define STACK 200

#else

#define STACK 100

#endif#else

#define SIGNAL 0

#if STACKUSE==1

#define STACK 100

#else

#define STACK 50

#endif

#endif

#if DLEVEL==0

#define STACK 0

#elif DLEVEL==1

#define STACK 100

#elif DLEVEL > 5

display(debugptr)

;#else

#define STACK 200

#endif

第一个#if块中有两组嵌套的#if#else#endif指令。第一组指令仅当DLEVELl>5为真时执行;否则,执行#else之后的语句。

第二组中的#elif#else指令选择基于DLEVEL值的四个选项之一。常量STACK依据DLEVEL定义为0100200。若DLEVEL大于5,则编译语句:

#elif DLEVEL > 5

display(debugptr);

且此时不定义STACK

条件编译一般用于防止同一头文件的多重包含。C++中在头文件内经常定义类的位置,可使用如下结构来防止多次定义。

//EXAMPLE.H例子头文件

#if !defined(EXAMPLE_H)

#define ExampleE_H

class Example

{

...

};

#endif //!defined(EXAMPLE_H)

上面的代码用于检查符号常量EXAMPLE_H是否已定义。若已定义,该文件就已被包括且不需再处理;如果未定义,常量EXAMPLE_H将被定义,以标记EXAMPLE.H为已经处理。

Microsoft特殊处

条件编译表达式被看作为signed long,且这些表达式与C++中的表达式采用相同的规则求值。例如,表达式:

#if 0xFFFFFFFFL > 1UL

为真。

Microsoft特殊处结束

#ifdefifndef指令

#ifdef#ifndef指令与使用defined(标识符)操作符的作用是一样的。

语法

#ifdef 标识符

#ifndef 标识符

等同于

#if defined 标识符

#if !defined 标识符

#if指令能用的任何地方都可以用#ifdef#ifndef指令。当标识符已被定义时,#ifdef标识符语句等同于#if 1;而当标识符未定义或用#undef指令对其反定义时,该语句等同于#if 0。这些指令仅用于检查CC++源代码中是否出现该标识符,而不是用于检查CC++源程序中该标识符的说明。

提供这几个指令只为了与该语言的老版本兼容。目前的趋势是偏向于采用defined(标识符)定义常量表达式的#if指令。

#ifndef指令测试与#ifdef相反的条件。若标识符未定义(或已用#undef反定义),其条件为真(0);反之,条件为假(0)

Microsoft特殊处

可以使用/D选项从命令行传送标识符,采用/D选项至多可以指定30个宏。检查一个定义是否存在是非常有用的,因为定义可从命令行传送。例如:

//prog.cpp

#ifndef test //这三个语句放在你的代码中

#define final

#endif

CL /Dtest prog.cpp //这是编译的命令

Microsoft特殊处结束

#import 指令

C++特殊处

#import指令用于从一个类型库中结合信息。该类型库的内容被转换为C++,主要用于描述COM界面。

语法

#import "文件名" [属性]

#import <文件名> [属性]

属性:

属性1,属性2,...

属性1 属性2 ...

文件名是一个包含类型库信息的文件的名称。一个文件可为如下类型之一:

* 一个类型库(.TLB.ODL)文件。

* 一个可执行(.EXE)文件。

* 一个包含类型库资源(.OCX)的库文件(.DLL)

* 一个包含类型库的复合文档。

* 其它可被LoadTypeLib API支持的文件格式。

文件名之前可以有一个目录规格。文件名必须是一个已存在文件的名称。两种格式的区别是当路径未完全说明时,预处理器检索类型库文件的顺序不同。

动作

语法格式

引号格式 这种格式让预处理器首先搜索与包含#import语句的文件同一目录的类型库文件,然后在所有包括(#include)该文件的目录中搜索,最后在如下路径中搜索

尖括号格式 这种格式指示预处理器沿以下路径搜索类型库文件

编译器在以下目录中搜索已命名的文件:

1. PATH环境变量路径表。

2. LIB环境变量路径表。

3. /I(额外的包括目录)编译器选项指定的路径。#import可以任选地包含一个或多个属性。这些属性使编译器改变类型库头文件的内容。一个反斜杠(/)符可用在一个单一的#import语句中包含额外的行,例如:

#import "test.lib" no_namespace /

rename("OldName","NewName")

#import属性列出如下:

exclude high_method_prefix

high_property_prefixes implementation_only

include(...) inject_statement

named_guids no_auto_exclude

no_implementation no_namespace

raw_dispinterfaces raw_interfaces_only

raw_method_prefix raw_native_types

raw_property_prefixes rename

rename_namespace  

#import指令可创建两个在C++源代码中重构类型库内容的头文件,第一个头文件和用Microsoft接口定义语言(MIDL)编译器生成的头文件类似,但有额外的编译器生成代码和数据。第一个头文件与类型库有相同的基本名,其扩展名为.TLH。第二个头文件也有与类型库相同的基本名,其扩展名为.TLI。它包括了编译器生成成员函数的实现,且被包含在(#include)的第一个头文件内。

两个头文件都在用/Fo(命名对象文件)选项指定的输出目录中。随后它们被读出和编译,就像第一个头文件被#include指令命名一样。

以下是伴随#import指令的编译器优化:

* 头文件被创建时,将被分配与类库相同的时间标志。

* 处理#import,编译器首先测试头文件是否存在,是否过期。若条件为真,就不需重新创建。

* 编译器延迟对于OLE子系统的初始化,直到碰到第一个#import命令。

#import指令也可参与最小重建且可被置于一个预编译头文件中。

基本类型库头文件

基本类型库头文件由七个部分组成:

1. 头部固定正文:由注释、COMDEF.H(定义用在头部的一些标准宏)#include语句和其它繁杂的安装信息组成。

2.前向引用和类型定义:由象struct IMyinterface之类的结构说明和用于一些TKIND_ALIAS项的类型定义组成。

3.灵敏指针说明:模块类_com_ptr_t是一个封装接口指针和消除调用AddRefRelease QueryInterface函数需求的灵敏指针实现。此外,它隐藏了创建一个新COM对象中的CoCreateInstance调用。此部分采用宏语句_COM_SMARTPTR_TYPEDEFCOM接口的类型定义创建为_com_ptr_t模板类的模板特例化。例如,对于界面IFoo,.TLH文件包含有:

_COM_SMARTPTR_TYPEDEF(IFoo,_ _uuidof(IFoo));

编译器将其扩展为:type def _com_ptr_t<_com_IIID<IFoo,_ _uuidof(IFoo) >> IFooPtr;

类型IFooPtr可以用在原始的界面指针IFoo*的地方。结果,就不需调用各种IUnknown成员函数。

4. 类型信息(typeinfo)说明:主要由类定义和其它项组成,这些项说明由ITyptLib:GetTypeInfo返回的单个的信息类型项目。在这部分,每个来自于类型库的信息类型都以一种依赖于TYPEKIND信息的格式反映在该头部。

5. 任选旧式GUID定义:包含命名的GUID常量的初始化过程,这些定义是格式CLSID_CoClassIID_Interface的名称,与那些由MIDL编译器产生的类似。

6. 用于第二个类型库头部的#include语句。

7. 结尾固定正文:目前包括#pragma pack(pop)

以上这些部分除头部固定正文和结尾固定正文部分之外,都被包括在原来的IDL文件中以library语句指定其名称的名称空间中。你可以通过用名称空间显式限定或包括如下语句从类型库头部使用该名称。

using namespace MyLib

在源代码的#import语句之后立即

名称空间可用#import指令的no_namespace属性来阻止。但阻止的名称空间可能导致名称冲突。名称空间也可用rename_namespace属性重新换名。

编译器提供完全路径给需要依赖当前正在处理的类型库的任何类型库。路径以注释格式写入到由编译器为每个处理的类型库生成的类型库头部(.TLH)

如果一个类型库包含了对其它类型库定义的类型引用,.TLH文件将包括以下注释:

//

//Cross-referenced type libraries:

//

//#import "c:/path/typelib0.tlb"

//

#import注释中的实际文件名是存储在寄存器中交叉引用的类型库全路径。如果你遇到由于省略类型定义的错误时,检查.TLH头部的注释,看哪一种依赖类型库需要先输入。在编译该.TLI文件时可能的错误有语法错误(例如C2143,C2146,C2321)C2501(缺少说明指示符)C2433(在数据说明中禁止′inline)

你必须确定哪些依赖注释是不被系统头部给出的,而是在依赖类型库的#import指令前的某处给出一个#import指令以消除这些错误。

exclude属性exclude(“称 1 [,“名称 2 ,...])

名称1

被排斥的第一个项

名称2

被排斥的第二个项(如有必要)

类型库可能包含在系统头部或其它类型库内定义的项的定义。该属性可用于从生成的类型库头文件中排斥这些项。这个属性可带任意数目的参量,每个参量是一个被排斥的高级类型库项目:

high_method_prefix属性

high_method_prefix("Prefix")

Prefix

被使用的前缀

在缺省的情况下,高级错误处理属性和方法用一个无前缀命名的成员函数来展示。这个名称来自于类型库。high_method_prefix属性说明一个前缀以用于命名这些高级属性和方法。

high_property_prefixes属性

high_property_prefixes("GetPrefix""PutPrefix""PutRefPrefix")

GetPrefix

用于propget方法的前缀

PutPrefix

用于propput方法的前缀

PutRefPrefix

用于propputref方法的前缀

在缺省情况下,高级错误处理方法,propgetpropputpropputref,分别采用以前缀GetPutPutRef命名的成员函数来说明。high_property_prefixes属性用于分别说明这三种属性方法的前缀。

implementation_only属性

implementation_only属性禁止.TLH头文件(基本头文件)的生成。这个文件包括了所有用于展示类型库内容的说明。该.TLI头文件和wrapper成员函数的实现,将被生成且包含在编译过程中。

当指定该属性时,.TLI头部的内容将和用于存放普通.TLH头部的内容放在相同的名称空间。此外,该成员函数不会作为联编说明。implementation_only属性一般希望与no_implementation属性配对使用,以跟踪预编译头文件(PCH)之外的实现。一个有no_implementation属性的#import语句被置于用来创建pch的源区域中,结果PCH将被一些源文件所用。一个带implementation_only属性的#import语句随后被用在PCH区域之外。在一个源文件里只需用一次这种语句。这将生成不需对每个源文件进行额外重编译的所有必要的wrapper成员函数。

注意:一个#import语句中的implementation_only属性必须和相同类型库中no_implementation属性的另一个#import语句配套使用。否则,将产生编译错误。这是因为带no_implementation属性的#import语句生成的wrapper类定义需要编译implementation_only属性生成的语句实现。

include(...)属性

Include(名称1[,名称2,...])

名称1

第一个被强制包含的项

名称2

第二个被强制包含的项(如果必要)

类型库可能包含在系统头部或其它类型库中定义的项的定义。#import指令试图用自动排斥这些项来避免多重定义错误。若这些项已经被排斥,象警告C4192所指出的那样,且它们不应该被排斥,则这个属性可用于禁止自动排斥。该属性可带任意数目的参量,每个参量应是被包括的类型库项的名称。

inject_statement属性

inject_statement("source_text")

source_text

被插入到类型库头文件的源文本。

inject_statement属性将其参量作为源文本插入类型库头部。此文本被置于包括头文件中类型库内容的名称空间说明的起始处。

named_guids属性

named_guids属性让编译器定义和初始化模板LIBID_MyLibCLSID_MyCoClassIID_MyInterfaceDIID_MyDispInterface的旧式格式的GUID变量。

no_implementation属性

该属性阻止.TLI头文件的生成,这个文件包含wrapper成员函数的实现。如果指定这个属性,则展示类型库项说明的.TLH头将生成没有一个#include语句包括该.TLI头文件。

该属性与implementation_only属性配套使用。

no_auto_exclude属性

类型库可能包括在系统头部或其它类型库中定义的项的定义。#import试图通过自动排斥这些项来避免多重定义错误。当这样做时,每个被排斥的项都将生成一个C4192警告信息。你可禁止这个属性使用自动排斥。

no_namespace属性

#import头文件中的类型库内容一般定义在一个名称空间里。名称空间的名称在原来IDL文件的library语句中指定。如果指定no_namespace属性,编译器就不会生成这个名称空间。

如果你想使用一个不同的名称空间,应代替使用rename_namespace属性。

raw_dispinterfaces属性

raw_dispinterfaces属性让编译器生成一个低级wrapper函数。该函数用于调用IDispatch::Invoke和返回HRESULT错误代码的dispinterface方法和属性。如果未指定此属性,则只生成高级wrapper,它在失败时丢弃该C++异常。

raw_interfaces_only属性

raw_interfaces_only属性禁止生成错误处理wrapper函数以及使用这些wrapper函数的_ _declspec(属性)说明。

raw_interfaces_only属性也导致删除在命名non__property函数中的缺省前缀。通常该前缀是raw_。若指定此属性,函数名称将直接从类型库中生成。该属性只允许展示类型库的低级内容。

raw_method_prefix属性

raw_method_prefix("Prefix")

Prefix

被使用的前缀

raw_作为缺省前缀的成员函数展示低层属性和方法,以避免与高级错误处理成员函数的名称冲突。raw_method_prefix属性用于指定一个不同的前缀。注意: raw_method_prefix属性的效果不会因raw_method_prefix属性的存在而改变。在说明一个前缀时,raw_method_prefix总是优先于raw_interfaces_only。若两种属性用在同一个#import语句中时,则采用raw_method_prefix指定的前缀。

raw_native_types属性

在缺省情况下,高级错误处理方法在BSTRVARIANT数据类型和原始COM界面指针的地方使用COM支持类_bctr_t_variant_t。这些类封装了分配和取消分配这些数据类型的存储器存储的细节,并且极大地简化了类型造型和转换操作。raw_native_types属性在高级wrapper函数中禁止使用这些COM支持类,并强制替换使用低级数据类型。

raw_property_prefix属性

raw_property_prefix("GetPrefix","PutPrefix","PutRefPrefix")

GetPrefix

用于propget方法的前缀

PutPrefix

用于propput方法的前缀

PutRefPrefix

用于propputref方法的前缀

在缺省情况下,低级方法propgetpropputpropputref分别用后缀为get_put_putref_的成员函数来展示。这些前缀与MIDL生成的头文件中的名称是兼容的。raw_property_prefixes属性分别用于说明这三个属性方法的前缀。

rename属性

rename("OldName""NewName")

OldName

类型库中的旧名

NewName

用于替换旧名的名称

rename属性用于解决名称冲突的问题。若该属性被指定,编译器将在类型库中的OldName的所有出现处用结果头文件中用户提供的NewName替换。

此属性用于类型库中的一个名称和系统头文件中的宏定义重合时。若这种情况未被解决,则将产生大量语法错误,C2059C2061

注意:这种替换用于类型库的名称,而不是用于结果头文件中的名称。

这里有一个例子:假设类型库中有一个名称为MyParent的属性,且头文件中定义了一个用在#import之前的宏GetMyParent。由于GetMyParent是用于错误处理属性get的一个wrapper函数的缺省名称,所以将产生一个名称冲突。为解决这个问题,使用#import语句中的以下属性:

rename("MyParent","MyParentX")

该语句将重新命名类型库中的名称MyParent,而试图重新命名GetMyParentwrapper名称将会出错:

rename("GetMyParent","GetMyParentX")

这是因为名称GetMyParent只出现在结果类型库头文件中。

rename_namespace属性

rename_namespace("NewName")

NewName

名称空间的新名称

rename_namespace属性用于重新命名包含类型库内容的名称空间。它带有一个指定名称空间新名newname的参量。

消除名称空间可以使用no_namespace属性。

C++特殊处结束

#include指令

#include指令告诉预处理器处理一个指定文件的内容,就象这些内容以前就在这条指令出现的源程序中。你可以把常量和宏定义放在包含文件中,然后用#include指令把这些定义加到任何源文件中。包含文件对于外部变量和复杂数据类型结合的说明也是有用的。

你只需在为此目的创建的一个包含文件中定义和命名这些类型一次。

语法

#include "path-spec"

#include

path_spec是一个前面有目录说明的任选文件名。这个文件名必须命名一个现存文件。

path_spec的语法依赖于编译该程序的操作系统。

这两种语法格式都导致用已说明的包含文件的全部内容来替换该指令。两种格式的区别在于路径未完整指定时预处理器搜索头文件的顺序。

语法格式 动作

引号格式 这种格式指示预处理器先在包含#include语句的文件的相同目录内搜索,然后在任何包括该文件的目录中搜索。随后预处理器沿着/I编译器选项指定的路径搜索,最后是在INCLUDE环境变量说明的路径搜索

尖括号格式 这种格式指示预处理器首先在/I编译器选项指定的路径中搜索包含文件。然后在INCLUDE环境变量说明的路径中搜索

一旦预处理器找到指定文件,它就立即停止搜索。如果用双引号给出一个明确完整的包含文件的路径,预处理器将只搜索该路径规格而忽略标准目录。

如果在双引号间的文件名不是一个完整的路径规格,预处理器将先搜索“父”文件的目录。父文件是一个包含#include指令的文件。例如,如果你把名称为file2的文件包括在一个名称为file1的文件中,file1就是父文件。

包含文件可被嵌套;这指的是一个#include指令出现在以另一个#include指令命名的文件里。例如,以上的文件file2,可包含文件file3,在这种情况下,file1file2的父文件,而且是file3的祖父文件。

当包含文件嵌套时,目录搜索首先由父文件的目录开始,然后,搜索祖父文件的目录。

因此,搜索从包含当前处理源文件的目录开始,若文件未找到,搜索就转到/I编译器选项指定的目录,最后搜索include环境变量指定的目录。

下面的例子给出使用尖括号的文件包括:

#include

这个例子把名称为STDIO.H的文件内容加入到源程序中。尖括号指示预处理器在搜索完/I编译器选项说明的目录之后,搜索STDIO.H的环境变量指定的目录。下面的例子给出用引号格式的文件包括:#include "defs.h"

这个例子把DEFS.H指定的文件内容加入源程序。双引号标记意味着预处理器首先搜索包含父源文件的目录。

包含文件的嵌套可高达10,只要在处理嵌套的#include指令时,预处理器就会不断地把包含文件加入到最初的源文件中。

Microsoft特殊处

为了定位可包括源文件,预处理器首先搜索/I编译器选项指定的目录。若/I选项未给定或已失败,预处理器就用INCLUDE环境变量搜索尖括号内的包含文件。INCLUDE环境变量和/I编译器选项可包含用分号分开的多个路径。若在/I选项的部分或在INCLUDE环境变量里有多于一个的目录,预处理器将以它们出现的顺序对它们进行搜索。

例如,命令:

CL /ID:/MSVC/INCLUDE MYPROG.C

导致预处理器在目录D:/MSVC/INCLUDE中搜索诸如STDIO.H的包含文件。命令:SET INCLUDE=D:/MSVC/INCLUDE

CL MYPROG.C

有相同的作用。如果所有搜索都失败了,将产生一个致命编译错误。

如果用包括一个冒号的路径(例如,F:/MSVC/SPECIAL/INCL/TEST.H)来完整地说明一个包含文件的文件名,预处理器将沿此路径搜索。

对于指定为#include "path_spec"的包含文件,目录搜索将从父文件的目录开始,然后搜索祖父文件的目录。因此,搜索将从包含当前处理的#include指令的源文件的目录开始,如果没有祖父文件或文件未找到,搜索将继续,就像文件名包括在尖括号中一样。

Microsoft特殊处结束

#line指令

#line指令告诉预处理器将编译器内部存储的行号和文件名转变为一个给定的行号和文件名。编译器使用该行号和文件名指出编译过程中发现的错误。行号一般指的是当前输入行,文件名指当前输入文件。每处理一行,行号就增1

语法

#line

数字序列 “文件名”opt

数字序列的值可以是任何整型常数。宏替换可在预处理语言符号中执行,但结果必须求值为正确的语法。文件名可以是任意字符的组合,且应括在双引号(“”)间。如果省略文件名,则前面的文件名保持不变。

你可以通过编写一个#line指令来改动源行号和文件名。翻译器使用行号和文件名来确定预定义宏__FILE_ __ _LINE_ _的值。你可以使用这些宏把自描述错误消息加入到程序文本中。有关这些宏的更多信息参见预定义的宏。

__FILE_ _宏扩展成内容为用双引号(“”)括起的文件名的一个字符串。

如果你改变行号和文件名,编译器将忽略原有的值,用新值继续处理。#line指令通常被程序生成器用来生成指向最初源程序的错误消息,而不是生成程序。下面的例子用于说明#line以及_ _LINE_ __ _FILE_ _宏。在这个语句中,内部存储的行号设置为151,文件名改为copy.c

#line 151 "copy.c"

在这个例子中,若一个给定的“断言”(assertion)不为真,则宏ASSERT使用预定义宏__LINE_ __ _FILE_ _打印出一个关于源文件的错误消息。

#define ASSERT(cond)

if( !(cond) ) /

{ printf("assertion error line %d, file(%s)/n",/

__LINE_ _,_ _FILE_ _); }

Null指令

空预处理器指令是一行中一个单独的数字标号(#),无任何作用。

语法

#

#undef指令

正如其名所隐含的,#undef指令取消(反定义)一个原来由#define指令创建的名称。

语法

#undef

标识符

#undef指令取消标识符的当前定义。其结果是,标识符的每次出现都将被预处理器所忽略。为取消一个用#undef的宏定义,只须给出宏的标识符,不须给出参数表。

你也可以将#undef指令用于一个原来未定义的标识符。这将确认这个标识符是未定义的。宏替换不能在#undef语句中执行。

#undef指令通常和一个#define指令匹配,以在源程序中创建一个区域,在这个区域中一个标识符有其特定的含义。例如,源程序的一个特有函数可以使用显式常量定义不影响程序余下部分的环境特定值。#undef指令也可与#if指令配对以控制源程序的条件编译过程。有关更多信息参见“#if#elif#else#endif指令”。

下面的例子中,#undef指令取消了一个符号常量和一个宏的定义,注意该指令只给出了宏的标识符。

#define WIDTH 80

#define ADD(X,Y) (X)+(Y)

...#undef WIDTH

#undef ADD

Microsoft特殊处

宏可通过采用/U选项的命令行反定义,此命令行后跟反定义的宏名称。此命令与在文件开头处的#undef 宏名称语句序列的作用是相等的。

Microsoft特殊处结束

--------------------------------------------------------------------------------

预处理器操作符

#define指令的文本中有四种预处理器特有的操作符(它们的总结参见下面的表)

字符化、字符串化和语言符号粘贴操作符将在下面三章中讨论。defined操作符的信息参见“#if#elif#else#endif指令”。

运算符 动作

字符串化操作符(#) 将相应实参置于双引号内

字符化操作符(#@) 将相应的参量置于单引号内,且将其作为字符处理(Microsoft特殊处)

语言符号粘贴操作符(##) 可将语言符号作为实参使用,且将其合并为其它 的语言符号

续表

定义的操作符 简化在某特定宏指令中复合表达式的写法

字符串化操作符(#)

数字符号或“字符串化”操作符(#)将宏参数(扩展后)转化为字符串常量。它只用于带参量的宏。如果它在宏定义中的一个形式参量之前,宏调用传给的实际参量就被括在双括号中,且被看作为一个字符串文字。然后该字符串文字将替换该宏定义中操作符和形参组合的每次出现。

实参的第一个语言符号之前和最后一个语言符号之后的空白被忽略。实参中语言符号之间的所有空白在结果字符串语义中都被看作为一个空格。因此,若实参中的一个注解出现在两个语言符号之间,它将被看作为一个空格。结果字符串文字自动地与任何仅用空格分开的相邻字符串文字连接。

此外,如果一个包含在参量里的字符在用作一个字符串文字(例如,双引号(")或反斜杠(/)字符)时通常需要一个转义序列,必要的转义反斜杠被自动地插入字符之前。下面的例子给出了一个包含字符串化操作符的宏定义和一个调用该宏的main函数:

#define stringer(x) printf(#x "/n")

void main( )

{

   stringer(In quotes in the printf function call/n);

   stringer("In quotes when printed to the screen"/n);

stringer("This:/" prints an escaped double quote");

}

这种调用在预处理时会被扩展,产生如下代码:

void main()

{

   printf("In quotes in the printf function call/n" "/n");

   printf("/"In quotes when printed to the screen/"/n" "/n");

   printf("/"This; ///" prints an escaped double quote /"" "/n");

}

当运行该程序时,每行的屏幕输出如下:

In quotes in the printf function call

"In quotes when printed to the screen"

"This; /" prints an escaped double quotation mark"

Microsoft特殊处

Microsoft C(版本6.0及更早版本)扩展ANSI C的标准,ANSI C扩展在字符串文字和字符常量中出现的宏形式参量不再被支持。依赖于此扩展的代码应该使用字符串化操作符(#)重写。

Microsoft特殊处结束

字符化操作符(#@)

Microsoft特殊处

字符化操作符只可用于宏参量,若宏定义中#@在一个形参前,则实参应被放在单引号中,在宏扩展时作为一个字符处理。例如:

#define makechar(x) #@x

将语句:

a=makechar(b);

扩展为:

a='b';

单引号字符不能用于字符化操作符。

Microsoft特殊处结束语

言符号粘贴操作符(##)

双数字语言符号或“语言符号粘贴”操作符(##),有时称作“合并”操作符,用于类对象宏和类函数宏中。它允许将分开的语言符号加入一个单个语言符号中,因此不能是宏定义的第一个语言符号或最后一个语言符号。

如果一个宏定义中的形参在语言符号粘贴操作符的前后,则形参将立即被未扩展的实参替换。在替换之前不对参量执行宏扩展。

然后,语言符号字符串中语言符号粘贴操作符的每次出现将被删除,其前后的语言符号将被合并。其结果语言符号必须是一个有效的语言符号。若其有效,如果该语言符号代表一个宏名称,则扫描它以发现可能的替换。该标识符表示在替换前程序中己知合并的语言符号的名称。每个语言符号都代表一个在程序中或在编译器命令行中定义的语言符号。

该操作符前后的空白是任意的。

如下例子说明了程序输出中字符串化操作符和语言符号粘贴操作符的用法:#define paster(n) printf("token" #n "=%d",taken##n)

int token9=9;

若一个宏用一个类似于下面的数值参量调用:

paster(9);

宏将生成:

printf("token" "9" "=%d",token9);

它变成为:

printf("token9 = %d", token9 );

--------------------------------------------------------------------------------

对宏扩展的预处理在所有那些不是预处理指令的行(第一个非空白字符不是#的行),以及其指令并未作为条件编译的一部分而忽略的行中进行。“条件编译”指令允许通过检测一个常量表达式或标识符以决定在预处理过程中哪个文本块送入编译器、哪个文本块从源文件中删除,并以此种方式控制一个源文件中某部分的编译。

#define指令通常使用有意义的标识符与常量、关键字、常用语句和表达式关联。表示常量的标识符有时被称作“符号常量”或“显式”常量。表示语句或表达式的常量称为“宏”。在本预处理器文档中,只使用术语“宏”。

当宏的名称在程序源文本或在某些其它预处理器命令的参量中被识别时,它被处理为对该宏的调用。宏名称被宏体的一个拷贝所替换。若该宏接受参量,宏名称后的实参就会替换宏体中的形参。用宏体中处理的拷贝来替换一个宏调用的过程,称为宏调用的“扩展”。

实际的术语中有两种类型的宏。“类对象”宏不带参量,而“类函数”宏可定义为带参量。因此它们的形式和功能都象函数调用,由于宏不生成实际的函数调用,所以有时可用宏替代函数调用使程序运行得更快,(C++,inline函数通常是一个好方法),然而,如果不小心的定义和使用宏,也可能造成麻烦。在带参量的宏定义时,你必须使用括号以保持一个表达式中正常的优先级,同时宏也不能正确地处理具有副作用的表达式。有关更多的信息参见“#define指令”中的例子getrandom

一旦你定义了一个宏,你不能不经取消该宏原有定义,而重新定义它为一个不同的值。但可用正好相同的定义来重定义该宏,因此,一个程序中宏的相同定义可出现多次。

#undef指令用于取消宏的定义。一旦取消该宏的定义,就可重新定义该宏为一个不同的值。#define#undef两节分别详细讨论了#define#undef指令。

宏和C++

C++提供了一些新的功能。其中有些功能替代了原来由ANSI C所提供的功能。这些新的功能增强了类型安全性和该语言的可预测性:

* C++,const说明的对象可用于常量表达式中,这使程序说明有类型和值信息的常量,以及能被调试器逐个字符检查的枚举值的常量。使用预处理器指令#define定义常量并不精确。除非在程序中找到一个带地址的表达式,否则一个const对象将不分配任何存储。

* C++联编函数替代了函数类型宏,相对于宏来说使用联编函数的优势在于:

* 类型安全性。联编函数和一般函数一样需进行相同的类型检测,宏无类型安全性检测。

* 纠正具有副作用的参量处理。联编函数在进入函数体之前对参量的表达式求值。因此,一个有副作用的表达式将是安全的。

对于联编函数的更多信息参见inline_ _inline节。为了向下兼容,Microsoft C++保留了所有在ANSI C和更早C++规格中的预处理器功能。

预定义宏

编译器可识别六种预定义的ANSI C(参见表1.1),Microsoft C++实现提供更多的预定义宏(参见表1.2)。这些宏不带参量,但不能被重定义。它们的值(__LINE_ __ _FILE_ _)必须是经过编译的常量。下面列出的一些预定义宏须用多个值来定义,它们的值可在Visual C++开发环境中选择相应的菜单选项来设置或采用命令行开关。更多的信息参见下表。

1.1 ANSI 预定义宏

说明

__DATE _ _当前源文件的编译日期。日期是格式为Mmm dd yyyy的字符串文字。月份名称Mmm与在TIME.H中说明的库函数asctime产生的日期一样

__FILE_ _ 当前源文件名称。__FILE_ _扩展为用双引号括起的一个字符串

__LINE_ _ 当前源文件的行号。该行号是一个十进制整型常量。可用一个#line指令修改

__STDC_ _ 指出与ANSI C标准的完全一致性。仅当给出/Za编译器选项且不编译C++代码时定义为整型量1;否则是不确定的

__TIME_ _ 当前文件的最近编译时间。该时间是格式为hh:mm:ss的字符串文字

__TIMESTAMP_ _ 当前源文件的最近修改日期。日期是格式为Ddd Mmm Datehh:mm:ss yyyy的字符串文字,这里Ddd是星期几的简写,Date是从131的一个整数表

1.2 Microsoft特殊预定义的宏

说明

__CHAR_UNSIGNED 缺省char类型是无符号的,当指定/J时定义的

__cplusplus 仅为C++程序定义

__CPPRTTI 定义为用/GR编译的代码(允许运行时类型信息)

__CPPUNWIND 定义为用/GX编译的代码(允许异常处理)

__DLL 指定/MD/MDd(多线程DLL)时定义的

__M_ALPHA DEC ALPHA平台定义,使用ALPHA编译器时定义为1,若使用另一个编译器时不定义

__M_IX86 x86处理器定义,参见表1.3

__M_MPPC Power Macintosh平台定义,缺省为601(/QP601)参见表1.4

__M_MRX000 MIPS平台定义,缺省为4000(/QMR4000),参见表1.5

__M_PPC PowerPC平台定义,缺省为604(/QP604),参见表1.6__MFC_VERMFC版本定义,Microsoft Founndation类库4.21定义为0x0421,它总是定义的

__MSC_EXTENSIONS 该宏在使用/Ze编译选项(缺省值)时定义,定义时其值总为1

__MSC_VER 定义编译器版本,对于Microsoft Visual C++ 6.0定义为1200,它总是定义的

__MT 当指定/MD/MDd(多线程DLL)/MT/MTd(多线程)选项时定义

__WIN32 Win32应用程序而定义。它总是定义的

如下表所示,编译器对反映处理器选项的预处理器标识符产生一个值。

1.3 _M_IX86的值

开发者的选项 命令行选项 返回值

Blend /GB _M_IX86=500(缺省值。将来的编译器将给出一个不同的值以影响主处理器)

Pentium /G5 _M_IX86=500

Pentiumpro /G6 _M_IX86=600

80386 /G3 _M_IX86=300

80486 /G4 _M_IX86=400

1.4 _M_MPPC的值

开发者的选项 命令行选项 返回值

PowerPC 601 /QP601 _M_MPPC=601(缺省值)

PowerPC 603 /QP603 _M_MPPC=603

PowerPC 604 /QP604 _M_MPPC=604

PowerPC 620 /QP620 _M_MPPC=620

1.5 _M_MRX000的值

开发者选项 命令行选项 返回值

R4000 /QMR4000 _M_MRX000=4000(缺省值)

R4100 /QMR4100 _M_MRX000=4100

R4200 /QMR4200 _M_MRX000=4200

R4400 /QMR4400 _M_MRX000=4400

R4600 /QMR4600 _M_MRX000=4600

R10000 /QMR10000 _M_MRX000=10000

1.6 _M_PPC的值

开发者选项 命令行选项 返回值

R4000 /QMR4000 _M_MRX000=4000(缺省值)

R4100 /QMR4100 _M_MRX000=4100

R4200 /QMR4200 _M_MRX000=4200

R4400 /QMR4400 _M_MRX000=4400

R4600 /QMR4600 _M_MRX000=4600

R10000 /QMR10000 _M_MRX000=10000