天天看點

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指令對每個編譯器給出了一個方法,在保持與C和C++語言完全相容的情況下,給出主機或作業系統專有的特征。

依據定義,編譯訓示是機器或作業系統專有的,且對于每個編譯器都是不同的。  

    其格式一般為: #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)  

{    }  

#pragma  warn +100          //  Turn the  warning  message  for  warning  #100 back  on  

函數會産生一條有唯一特征碼100的警告資訊,如此可暫時終止該警告。 

每個編譯器對#pragma的實作不同,在一個編譯器中有效在别的編譯器中幾乎無效。可從編譯器的文檔中檢視。

補充 —— #pragma pack與記憶體對齊問題

    許多實際的計算機系統對基本類型資料在記憶體中存放的位置有限制,它們會要求這些資料的首位址的值是某個數k

(通常它為4或8)的倍數,這就是所謂的記憶體對齊,而這個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 )時, 這裡n為1、2、4、8或16。

    第一個結構成員之後的每個結構成員都被存儲在更小的成員類型或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屬性列出如下:

exclude high_method_prefix
include(...) high_property_prefixes
 implementation_only 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是一個封裝接口指針和消除調用AddRef、Release和QueryInterface函數需求的靈敏指針實作。此外,它隐藏了建立一個新COM對象中的CoCreateInstance調用。此部分采用宏語句_COM_SMARTPTR_TYPEDEF将COM接口的類型定義建立為_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_CoClass和IID_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方法的字首

在預設情況下,進階錯誤處理方法,如propget、propput和propputref,分别采用以字首Get、Put和PutRef命名的成員函數來說明。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_MyLib、CLSID_MyCoClass、IID_MyInterface和DIID_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屬性

在預設情況下,進階錯誤處理方法在BSTR和VARIANT資料類型和原始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方法的字首

在預設情況下,低級方法propget、propput和propputref分别用字尾為get_、put_和putref_的成員函數來展示。這些字首與MIDL生成的頭檔案中的名稱是相容的。raw_property_prefixes屬性分别用于說明這三個屬性方法的字首。

rename屬性

rename("OldName,""NewName")

OldName

類型庫中的舊名

NewName

用于替換舊名的名稱

rename屬性用于解決名稱沖突的問題。若該屬性被指定,編譯器将在類型庫中的OldName的所有出現處用結果頭檔案中使用者提供的NewName替換。

此屬性用于類型庫中的一個名稱和系統頭檔案中的宏定義重合時。若這種情況未被解決,則将産生大量文法錯誤,如C2059和C2061。

注意:這種替換用于類型庫的名稱,而不是用于結果頭檔案中的名稱。

這裡有一個例子:假設類型庫中有一個名稱為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。

繼續閱讀