C/C++预编译指令整理收藏

C/C++语言中,并没有任何内在的机制来完成如下一些功能:在编译时包含其他源文件、定义宏、根据条件决定编译时是否包含某些代码。要完成这些工作,就需要使用预处理程序。尽管在目前绝大多数编译器都包含了预处理程序,但通常认为它们是独立于编译器的。预处理过程读入源代码,检查包含预处理指令的语句和宏定义,并对源代码进行响应的转换。预处理过程还会删除程序中的注释和多余的空白字符。预处理指令是以#号开头的代码行。#号必须是该行除了任何空白字符外的第一个字符。#后是指令关键字,在关键字和#号之间允许存在任意个数的空白字符。整行语句构成了一条预处理指令,该指令将在编译器进行编译之前对源代码做某些转换。下面是部分预处理指令:

指令用途

#空指令,无任何效果
#include
包含一个源代码文件
#define
定义宏
#undef
取消已定义的宏
#if
如果给定条件为真,则编译下面代码
#ifdef
如果宏已经定义,则编译下面代码
#ifndef
如果宏没有定义,则编译下面代码
#elif
如果前面的#if给定条件不为真,当前条件为真,则编译下面代码
#endif
结束一个#if……#else条件编译块
#error
停止编译并显示错误信息

#pragma设置编译器

#import导入文件(C++
一、文件包含
 
#include
预处理指令的作用是在指令处展开被包含的文件。包含可以是多重的,也就是说一个被包含的文件中还可以包含其他文件。标准C编译器至少支持八重嵌套包含。

预处理过程不检查在转换单元中是否已经包含了某个文件并阻止对它的多次包含。这样就可以在多次包含同一个头文件时,通过给定编译时的条件来达到不同的效果。例如:

#define AAA
#include "t.c"
#undef AAA
#include "t.c"


为了避免那些只能包含一次的头文件被多次包含,可以在头文件中用编译时条件来进行控制。例如:
/*my.h*/
#ifndefMY_H
#defineMY_H
……
#endif


在程序中包含头文件有两种格式:
#include<my.h>
#include"my.h"
一种方法是用尖括号把头文件括起来。这种格式告诉预处理程序在编译器自带的或外部库的头文件中搜索被包含的头文件。第二种方法是用双引号把头文件括起来。 这种格式告诉预处理程序在当前被编译的应用程序的源代码文件中搜索被包含的头文件,如果找不到,再搜索编译器自带的头文件。

采用两种不同包含格式的理由在于,编译器是安装在公共子目录下的,而被编译的应用程序是在它们自己的私有子目录下的。一个应用程序既包含编译器提供的公共头文件,也包含自定义的私有头文件。采用两种不同的包含格式使得编译器能够在很多头文件中区别出一组公共的头文件。

二、宏 
定义了一个代表特定内容的标识符。预处理过程会把源代码中出现的宏标识符替换成宏定义时的值。宏最常见的用法是定义代表某个值的全局符号。宏的第二种用法是定义带参数的宏,这样的宏可以象函数一样被调用,但它是在调用语句处展开宏,并用调用时的实际参数来代替定义中的形式参数。

1.#define指令 
#define
预处理指令是用来定义宏的。该指令最简单的格式是:首先神明一个标识符,然后给出这个标识符代表的代码。在后面的源代码中,就用这些代码来替代该标识符。这种宏把程序中要用到的一些全局值提取出来,赋给一些记忆标识符。

#defineMAX_NUM10
intarray[MAX_NUM];
for(i=0;i<MAX_NUM;i++)/*……*/

这个例子中,对于阅读该程序的人来说,符号 MAX_NUM 就有特定的含义,它代表的值给出了数组所能容纳的最大元素数目。程序中可以多次使用这个值。作为 一种约定,习惯上总是全部用大写字母来定义宏,这样易于把程序红的宏标识符和一般变量标识符区别开来。如果想要改变数组的大小,只需要更改宏定义并重新编 译程序即可。
宏表示的值可以是一个常量表达式,其中允许包括前面已经定义的宏标识符。例如:
#define ONE1
#define TWO2
#define THREE  (ONE+TWO)

注意上面的宏定义使用了括号。尽管它们并不是必须的。但出于谨慎考虑,还是应该加上括号的。例如:
six=THREE*TWO;

预处理过程把上面的一行代码转换成:
six=(ONE+TWO)*TWO;

如果没有那个括号,就转换成 six=ONE+TWO*TWO; 了。
宏还可以代表一个字符串常量,例如:
#define VERSION  "Version1.0Copyright(c)2003"

2.带参数的#define指令 
带参数的宏和函数调用看起来有些相似。看一个例子:

#define Cube(x)   ((x)*(x)*(x))

可以时任何数字表达式甚至函数调用来代替参数 x 。这里再次提醒大家注意括号的使用。宏展开后完全包含在一对括号中,而且参数也包含在括号中,这样就保证了宏和参数的完整性。看一个用法:
intnum=8+2;
volume=Cube(num);

展开后为 (8+2)*(8+2)*(8+2);
如果没有那些括号就变为 8+2*8+2*8+2 了。
下面的用法是不安全的:
volume=Cube(num++);
如果 Cube 是一个函数,上面的写法是可以理解的。但是,因为 Cube 是一个宏,所以会产生副作用。这里的擦书不是简单的表达式,它们将产生意想不到的结果。它们展开后是这样的:
volume=(num++)*(num++)*(num++);

很显然,结果是 10*11*12, 而不是 10*10*10;
那么怎样安全的使用 Cube 宏呢?必须把可能产生副作用的操作移到宏调用的外面进行:
intnum=8+2;
volume=Cube(num);
num++;

3.#运算符 
出现在宏定义中的#运算符把跟在其后的参数转换成一个字符串。有时把这种用法的#称为字符串化运算符。例如:

#define   PASTE(n)  "adhfkj"#n
int main(void)
{
     printf("%s/n",PASTE(15));
}

宏定义中的 # 运算符告诉预处理程序,把源代码中任何传递给该宏的参数转换成一个字符串。所以输出应该是 adhfkj15

 

4.##运算符 
##
运算符用于把参数连接到一起。预处理程序把出现在##两侧的参数合并成一个符号。看下面的例子:

#define  NUM(a,b,c)    a##b##c
#define  STR(a,b,c)     a##b##c

int main(void)
{
     printf("%d/n",NUM(1,2,3));
     printf("%s/n",STR("aa","bb","cc"));
}

最后程序的输出为 :
123
aabbcc
千万别担心,除非需要或者宏的用法恰好和手头的工作相关,否则很少有程序员会知道 ## 运算符。绝大多数程序员从来没用过它。

三、条件编译指令

条件编译指令将决定那些代码被编译,而哪些是不被编译的。可以根据表达式的值或者某个特定的宏是否被定义来确定编译条件。

 

1.#if指令 
#if
指令检测跟在制造另关键字后的常量表达式。如果表达式为真,则编译后面的代码,知道出现#else#elif#endif为止;否则就不编译。

 

2.#endif指令 
#endif
用于终止#if预处理指令。

#define  DEBUG0
int main(void)
{
     #if  DEBUG
     printf("Debugging/n");
     #endif
     printf("Running/n");
}

由于程序定义 DEBUG 宏代表 0 ,所以 #if 条件为假,不编译后面的代码直到 #endif ,所以程序直接输出 Running
如果去掉 #define 语句,效果是一样的。

 

3.#ifdef#ifndef 

#define   DEBUG

int main(void)
{
     #ifdef  DEBUG
     printf("yes/n");
     #endif
     #ifndef   DEBUG
     printf("no/n");
     #endif
}


#ifdefined 等价于 #ifdef;#if!defined 等价于 #ifndef

 

4.#else指令 
#else
指令用于某个#if指令之后,当前面的#if指令的条件不为真时,就编译#else后面的代码。#endif指令将中指上面的条件块。

#define   DEBUG

int main(void)
{
       #ifdef   DEBUG
       printf("Debugging/n");
       #else
       printf("Notdebugging/n");
       #endif
       printf("Running/n");
}


5.#elif指令  
#elif
预处理指令综合了 #else #if 指令的作用。
#define   TWO

int main(void)
{
      #ifdef  ONE
      printf("1/n");
      #elif defined TWO
      printf("2/n"); 
      #else
      printf("3/n");
      #endif
}

程序很好理解,最后输出结果是 2


6. 
#error指令

#error指令将使编译器显示一条错误信息,然后停止编译。

#error message :编译器遇到此命令时停止编译,并将参数message输出。该命令常用于程序调试。

#error指令语法格式如下: 
#error token-sequence

 

编译程序时,只要遇到 #error就会跳出一个编译错误,既然是编译错误,要它干嘛呢?其目的就是保证程序是按照你所设想的那样进行编译的。

下面举个例子:
程序中往往有很多的预处理指令

#ifdef XXX
...
#else

#endif

当程序比较大时,往往有些宏定义是在外部指定的(如 makefile ),或是在系统头文件中指定的,当你不太确定当前是否定义了 XXX 时,就可以改成如下这样进行编译:

#ifdef XXX
...
#error "XXX has been defined"
#ifdef XXX
...
#else

#endif
#else

#endif



这样 , 如果编译时出现错误 , 输出了 XXX has been defined, 表明宏 XXX 已经被定义了。



其实就是在编译的时候输出编译错误信息token-sequence,从方便程序员检查程序中出现的错误。 

简单的例子 

#include "stdio.h" 
int main(int argc, char* argv[]) 
{ 
<span style="white-space:pre">	</span>#define CONST_NAME1 "CONST_NAME1" 
<span style="white-space:pre">	</span>printf("%s/n",CONST_NAME1); 
<span style="white-space:pre">	</span>#undef CONST_NAME1 
<span style="white-space:pre">	</span>#ifndef CONST_NAME1 
<span style="white-space:pre">	</span>#error No defined Constant Symbol CONST_NAME1 
<span style="white-space:pre">	</span>#endif 
<span style="white-space:pre">	</span>...... 

<span style="white-space:pre">	</span>return 0; 
} 


在编译的时候输出如编译信息  
fatal error C1189: #error : No defined Constant Symbol CONST_NAME1

 

7.#pragma指令 
#pragma
指令没有正式的定义。编译器可以自定义其用途。典型的用法是禁止或允许某些烦人的警告信息。


   
在所有的预处理指令中,#pragma指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。
#pragma
指令对每个编译器给出了一个方法,在保持与CC++语言完全兼容的情况下,给出主机或操作系统专有的特征。
依据定义,编译指示是机器或操作系统专有的,且对于每个编译器都是不同的。  
    其格式一般为: #pragma  para  
    其中para为参数,下面来看一些常用的参数。  
 

(1)message 参数

    message参数是我最喜欢的一个参数,它能够在编译信息输出窗口中输出相应的信息,
这对于源代码信息的控制是非常重要的。其使用方法为:  
    #pragma  message("消息文本")  
    当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。  
    当我们在程序中定义了许多宏来控制源代码版本的时候,我们自己有可能都会忘记有没有正确的设置这些宏,
此时我们可以用这条指令在编译的时候就进行检查。假设我们希望判断自己有没有在源代码的什么地方定义了_X86这个宏,
可以用下面的方法:
   

#ifdef  _X86  
#pragma  message("_X86  macro activated!")  
#endif  

    我们定义了 _X86 这个宏以后,应用程序在编译时就会在编译输出窗口里显示 "_86  macro activated!"
我们就不会因为不记得自己定义的一些特定的宏而抓耳挠腮了。   
      

(2)另一个使用得比较多的pragma参数是code_seg

    格式如:  

     #pragma  code_seg( ["section-name" [,"section-class"] ] )  

    它能够设置程序中函数代码存放的代码段,当我们开发驱动程序的时候就会使用到它。   
 

(3)#pragma once  (比较常用

    只要在头文件的最开始加入这条指令就能够保证头文件被编译一次,这条指令实际上在VC6中就已经有了,
但是考虑到兼容性并没有太多的使用它。 

       
(4)#pragma  hdrstop

    表示预编译头文件到此为止,后面的头文件不进行预编译。BCB可以预编译头文件以加快链接的速度,
但如果所有头文件都进行预编译又可能占太多磁盘空间,所以使用这个选项排除一些头文件。    
    有时单元之间有依赖关系,比如单元A依赖单元B,所以单元B要先于单元A编译。
你可以用#pragma startup指定编译优先级,如果使用了#pragma package(smart_init)
BCB
就会根据优先级的大小先后编译。   

       
(5)#pragma  resource  "*.dfm"

    表示把*.dfm文件中的资源加入工程。*.dfm中包括窗体  
外观的定义。   

         
(6)#pragma  warning( disable: 4507 34; once: 4385; error: 164 )
  
    等价于:  
   

    #pragma  warning( disable: 4507 34 )   //  不显示4507和34号警告信息  
    #pragma  warning( once: 4385)          //  4385号警告信息仅报告一次  
    #pragma  warning( error: 164)          //  把164号警告信息作为一个错误。

    同时这个pragma  warning 也支持如下格式:  
    

    #pragma  warning( push [, n ] )  
    #pragma  warning( pop )  


    这里 n 代表一个警告等级 (1---4)   
    
    #pragma  warning( push )保存所有警告信息的现有的警告状态。  
    #pragma  warning( push, n )保存所有警告信息的现有的警告状态,并且把全局警告等级设定为n。    
    #pragma  warning( pop )向栈中弹出最后一个警告信息,在入栈和出栈之间所作的一切改动取消。例如:  
    #pragma  warning( push )  
    #pragma  warning( disable: 4705 )  
    #pragma  warning( disable: 4706 )  
    #pragma  warning( disable: 4707 )  
    //.......  
    #pragma  warning(  pop  )    


    在这段代码的最后,重新保存所有的警告信息 ( 包括 4705 4706 4707)  


(7)#pragma  comment(...) 

    该指令将一个注释记录放入一个对象文件或可执行文件中。  
常用的lib关键字,可以帮我们连入一个库文件。如:
    #pragma  comment(lib, "comctl32.lib")
    #pragma  comment(lib, "vfw32.lib")
    #pragma  comment(lib, "wsock32.lib")
 
   

   
每个编译程序可以用#pragma指令激活或终止该编译程序支持的一些编译功能。

例如,对循环优化功能:  
#pragma  loop_opt(on)     //  激活  
#pragma  loop_opt(off)    //  终止 

有时,程序中会有些函数会使编译器发出你熟知而想忽略的警告,
“Parameter xxx  is  never  used  in  function  xxx”,可以这样:  
#pragma  warn  —100        //  Turn  off  the  warning  message  for warning  #100  
int  insert_record(REC  *r)  
{  /*  function  body  */  }  
#pragma  warn +100          //  Turn the  warning  message  for  warning  #100 back  on  
函数会产生一条有唯一特征码100的警告信息,如此可暂时终止该警告。 

每个编译器对#pragma的实现不同,在一个编译器中有效在别的编译器中几乎无效。可从编译器的文档中查看。

补充 —— #pragma pack内存对齐问题


   
许多实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地址的值是某个数k
(
通常它为48)的倍数,这就是所谓的内存对齐,而这个k则被称为该数据类型的对齐模数(alignment modulus)

    Win32平台下的微软C编译器(cl.exe for 80x86)在默认情况下采用如下的对齐规则
    任何基本数据类型T的对齐模数就是T的大小,即sizeof(T)。比如对于double类型(8字节)
就要求该类型数据的地址总是8的倍数,而char类型数据(1字节)则可以从任何一个地址开始。

    Linux下的GCC奉行的是另外一套规则(在资料中查得,并未验证,如错误请指正):
   
任何2字节大小(包括单字节吗?)的数据类型(比如short)的对齐模数是2,而其它所有超过2字节的数据类型
(
比如long,double)都以4为对齐模数。

    ANSI C规定一种结构类型的大小是它所有字段的大小以及字段之间或字段尾部的填充区大小之和。
填充区就是为了使结构体字段满足内存对齐要求而额外分配给结构体的空间。那么结构体本身有什么对齐要求吗?
有的,ANSI C标准规定结构体类型的对齐要求不能比它所有字段中要求最严格的那个宽松,可以更严格。


如何使用c/c++中的对齐选项

    vc6中的编译选项有 /Zp[1|2|4|8|16]/Zp1表示以1字节边界对齐,相应的,/Zpn表示以n字节边界对齐。
n
字节边界对齐的意思是说,一个成员的地址必须安排在成员的尺寸的整数倍地址上或者是n的整数倍地址上,取它们中的最小值。
也就是:
    min ( sizeof ( member ),  n)

    实际上,1字节边界对齐也就表示了结构成员之间没有空洞。
    /Zpn
选项是应用于整个工程的,影响所有的参与编译的结构。
   
要使用这个选项,可以在vc6中打开工程属性页,c/c++页,选择Code Generation分类,在Struct member alignment可以选择。

    要专门针对某些结构定义使用对齐选项,可以使用#pragma pack编译指令:


(1) #pragma  pack( [ n ] )

    该指令指定结构和联合成员的紧凑对齐。而一个完整的转换单元的结构和联合的紧凑对齐由/Zp选项设置。
紧凑对齐用pack编译指示在数据说明层设置。该编译指示在其出现后的第一个结构或联合说明处生效。
该编译指示对定义无效。
   
当你使用#pragma  pack ( n ), 这里n124816
   
第一个结构成员之后的每个结构成员都被存储在更小的成员类型或n字节界限内。
如果你使用无参量的#pragma  pack,结构成员被紧凑为以/Zp指定的值。该缺省/Zp紧凑值为/Zp8


(2)
编译器也支持以下增强型语法:
    #pragma  pack( [ [ { push | pop } , ] [ identifier, ] ][ n] )

    若不同的组件使用pack编译指示指定不同的紧凑对齐,这个语法允许你把程序组件组合为一个单独的转换单元。
push参量的pack编译指示的每次出现将当前的紧凑对齐存储到一个内部编译器堆栈中。
   
编译指示的参量表从左到右读取。如果你使用push,则当前紧凑值被存储起来
如果你给出一个n 的值,该值将成为新的紧凑值。若你指定一个标识符,即你选定一个名称
则该标识符将和这个新的的紧凑值联系起来。

    带一个pop参量的pack编译指示的每次出现都会检索内部编译器堆栈顶的值,并且使该值为新的紧凑对齐值。
如果你使用pop参量且内部编译器堆栈是空的,则紧凑值为命令行给定的值,并且将产生一个警告信息。
若你使用pop且指定一个n的值,该值将成为新的紧凑值。若你使用p o p且指定一个标识符
所有存储在堆栈中的值将从栈中删除, 直到找到一个匹配的标识符, 这个与标识符相关的紧凑值也从栈中移出
并且这个仅在标识符入栈之前存在的紧凑值成为新的紧凑值。如果未找到匹配的标识符
将使用命令行设置的紧凑值, 并且将产生一个一级警告。缺省紧凑对齐为8

   pack编译指示的新的增强功能让你编写头文件,确保在遇到该头文件的前后的
紧凑值是一样的。


(3)
栈内存对齐

    vc6中栈的对齐方式不受结构成员对齐选项的影响。它总是保持对齐,而且对齐在4字节边界上。

 

8.#line指令

#line指令可以改变编译器用来指出警告和错误信息的文件号和行号。

 

9.#import 指令

#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属性列出如下:

excludehigh_method_prefix
include(...)high_property_prefixes
 implementation_onlyinject_statement
named_guidsno_auto_exclude
no_implementationno_namespace
raw_dispinterfacesraw_interfaces_only
raw_method_prefixraw_native_types
raw_property_prefixesrename
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是一个封装接口指针和消除调用AddRefReleaseQueryInterface函数需求的灵敏指针实现。此外,它隐藏了创建一个新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 typelibraries:
//
//#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),宏定义(#define),书上已经有了详细的说明,在这里就不详述了。这里主要是对条件编译(#ifdef,#else,#endif,#if等)进行说明。以下分3种情况:

1:情况1 
#ifdef _XXXX
...
程序段1...
#else
...
程序段2...
#endif
这表明如果标识符_XXXX已被#define命令定义过则对程序段1进行编译;否则对程序段2进行编译。

例: 
#define NUM
.............
.............
.............
#ifdef NUM
printf("
之前NUM有过定义啦!:) /n");
#else
printf("
之前NUM没有过定义!:( /n");
#endif
}
如果程序开头有#define NUM这行,即NUM有定义,碰到下面#ifdef NUM的时候,当然执行第一个printf。否则第二个printf将被执行。
我认为,用这种,可以很方便的开启/关闭整个程序的某项特定功能。

2:情况2 
#ifndef _XXXX 

...程序段1... 
#else 
...程序段2... 
#endif
这里使用了#ifndef,表示的是if not def。当然是和#ifdef相反的状况(如果没有定义了标识符_XXXX,那么执行程序段1,否则执行程序段2)。例子就不举了。

3:情况3 
#if
常量 
...
程序段1...
#else
...
程序段2...
#endif 

这里表示,如果常量为真(非0,随便什么数字,只要不是0),就执行程序段1,否则执行程序段2
我认为,这种方法可以将测试代码加进来。当需要开启测试的时候,只要将常量变1就好了。而不要测试的时候,只要将常量变0

 

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值