__cdecl和__stdcall都是函數調用規范(還有一個__fastcall),規定了參數出入棧的順序和方法, 如果只用VC編程的話可以不用關心,但是要在C++和Pascal等其他語言通信的時候就要注意了,只有用相同的方法才能夠調用成功. 另外,像printf這樣接受可變個數參數的函數只有用cdecl才能夠實現.?? ? __declspec主要是用于說明DLL的引出函數的,在某些情況下用__declspec(dllexport)在DLL中生命引出函數,比用傳統的DEF文件方便一些.在普通程序中也可以用__declspec(dllimport)說明函數是位于另一個DLL中的導出函數. 例子不太好舉啊,其實就是在函數聲明的時候多加一個關鍵字,比如很多API函數就是象這樣聲明的:?? ? int?? WINAPI?? MessageBoxA(HWND,LPCSTR,LPSTR,UINT);?? ? 而WINAPI實際上就是__stdcall.?? ? 大多數API都采用__stdcall調用規范,這是因為幾乎所有的語言都支持__stdcall調用.相比之下,__cdecl只有在C語言中才能用.但是__cdecl調用有一個特點,就是能夠實現可變參數的函數調用,比如printf,這用__stdcall調用是不可能的.?? ? __fastcall這種調用規范比較少見,但是在Borland?? C++?? Builder中比較多的采用了這種調用方式.?? ? 如果有共享代碼的需要,比如寫DLL,推薦的方法是用__stdcall調用,因為這樣適用范圍最廣.如果是C++語言寫的代碼供Delphi這樣的語言調用就必須聲明為__stdcall,因為Pascal不支持cdecl調用(或許Delphi的最新版本能夠支持也說不定,這個我不太清楚).在其他一些地方,比如寫COM組件,幾乎都用的是stdcall調用.在VC或Delphi或C++Builder里面都可以從項目設置中更改默認的函數調用規范,當然你也可以在函數聲明的時候加入__stdcall,__cdecl,__fastcall關鍵字來明確的指示本函數用哪種調用規范.?? ? __declspec一般都是用來聲明DLL中的導出函數.這個關鍵字也有一些其他的用法,不過非常罕見. 關于DLL的函數:?? ??? ? 動態鏈接庫中定義有兩種函數:導出函數(export?? function)和內部函數(internal?? function)。?? ? 導出函數可以被其它模塊調用,內部函數在定義它們的DLL程序內部使用。?? ??? ? 輸出函數的方法有以下幾種:?? ??? ? 1、傳統的方法?? ??? ? 在模塊定義文件的EXPORT部分指定要輸入的函數或者變量。語法格式如下:?? ? entryname[=internalname]?? [@ordinal[NONAME]]?? [DATA]?? [PRIVATE]?? ??? ? 其中:?? ??? ? entryname是輸出的函數或者數據被引用的名稱;?? ??? ? internalname同entryname;?? ??? ? @ordinal表示在輸出表中的順序號(index);?? ??? ? NONAME僅僅在按順序號輸出時被使用(不使用entryname);?? ??? ? DATA表示輸出的是數據項,使用DLL輸出數據的程序必須聲明該數據項為_declspec(dllimport)。?? ??? ? 上述各項中,只有entryname項是必須的,其他可以省略。?? ??? ? 對于“C”函數來說,entryname可以等同于函數名;但是對“C++”函數(成員函數、非成員函數)?? ? 來說,entryname是修飾名??梢詮?map映像文件中得到要輸出函數的修飾名,或者使用?? ? DUMPBIN?? /SYMBOLS得到,然后把它們寫在.def文件的輸出模塊。DUMPBIN是VC提供的一個工具。?? ??? ? 如果要輸出一個“C++”類,則把要輸出的數據和成員的修飾名都寫入.def模塊定義文件。?? ??? ? 2、在命令行輸出?? ??? ? 對鏈接程序LINK指定/EXPORT命令行參數,輸出有關函數。?? ??? ? 3、使用MFC提供的修飾符號_declspec(dllexport)?? ??? ? 在要輸出的函數、類、數據的聲明前加上_declspec(dllexport)的修飾符,表示輸出。__declspec?? ? (dllexport)在C調用約定、C編譯情況下可以去掉輸出函數名的下劃線前綴。extern?? "C"使得在C++中?? ? 使用C編譯方式成為可能。在“C++”下定義“C”函數,需要加extern?? “C”關鍵詞。用extern?? "C"來?? ? 指明該函數使用C編譯方式。輸出的“C”函數可以從“C”代碼里調用。?? ??? ? 例如,在一個C++文件中,有如下函數:?? ? extern?? "C"?? {void?? __declspec(dllexport)?? __cdecl?? Test(int?? var);}?? ? 其輸出函數名為:Test???? ??? ? MFC提供了一些宏,就有這樣的作用。?? ??? ? AFX_CLASS_IMPORT:__declspec(dllexport)?? ??? ? AFX_API_IMPORT:__declspec(dllexport)?? ??? ? AFX_DATA_IMPORT:__declspec(dllexport)?? ??? ? AFX_CLASS_EXPORT:__declspec(dllexport)?? ??? ? AFX_API_EXPORT:__declspec(dllexport)?? ??? ? AFX_DATA_EXPORT:__declspec(dllexport)?? ??? ? AFX_EXT_CLASS:?? #ifdef?? _AFXEXT???? ? AFX_CLASS_EXPORT?? ? #else?? ? AFX_CLASS_IMPORT?? ??? ? AFX_EXT_API:#ifdef?? _AFXEXT?? ? AFX_API_EXPORT?? ? #else?? ? AFX_API_IMPORT?? ??? ? AFX_EXT_DATA:#ifdef?? _AFXEXT?? ? AFX_DATA_EXPORT?? ? #else?? ? AFX_DATA_IMPORT?? ??? ? 像AFX_EXT_CLASS這樣的宏,如果用于DLL應用程序的實現中,則表示輸出(因為_AFX_EXT被定義,通?? ? 常是在編譯器的標識參數中指定該選項/D_AFX_EXT);如果用于使用DLL的應用程序中,則表示輸入?? ? (_AFX_EXT沒有定義)。?? ??? ? 要輸出整個的類,對類使用_declspec(_dllexpot);要輸出類的成員函數,則對該函數使用?? ? _declspec(_dllexport)。如:?? ??? ? class?? AFX_EXT_CLASS?? CTextDoc?? :?? public?? CDocument?? ? {?? ? …?? ? }?? ??? ? extern?? "C"?? AFX_EXT_API?? void?? WINAPI?? InitMYDLL();?? ??? ? 這幾種方法中,最好采用第三種,方便好用;其次是第一種,如果按順序號輸出,調用效率會高些;?? ? 最次是第二種。???? ??? ? 六、模塊定義文件(.DEF)?? ??? ? 模塊定義文件(.DEF)是一個或多個用于描述DLL屬性的模塊語句組成的文本文件,每個DEF文件至少必?? ? 須包含以下模塊定義語句:?? ??? ? *?? 第一個語句必須是LIBRARY語句,指出DLL的名字;?? ? *?? EXPORTS語句列出被導出函數的名字;將要輸出的函數修飾名羅列在EXPORTS之下,這個名字必須與?? ? 定義函數的名字完全一致,如此就得到一個沒有任何修飾的函數名了。?? ? *?? 可以使用DESCRIPTION語句描述DLL的用途(此句可選);?? ? *?? ";"對一行進行注釋(可選)。?? ??? ? 七、DLL程序和調用其輸出函數的程序的關系?? ??? ? 1、dll與進程、線程之間的關系?? ??? ? DLL模塊被映射到調用它的進程的虛擬地址空間。?? ? DLL使用的內存從調用進程的虛擬地址空間分配,只能被該進程的線程所訪問。?? ? DLL的句柄可以被調用進程使用;調用進程的句柄可以被DLL使用。?? ? DLL使用調用進程的棧。?? ??? ? 2、關于共享數據段?? ??? ? DLL定義的全局變量可以被調用進程訪問;DLL可以訪問調用進程的全局數據。使用同一DLL的每一個?? ? 進程都有自己的DLL全局變量實例。如果多個線程并發訪問同一變量,則需要使用同步機制;對一個?? ? DLL的變量,如果希望每個使用DLL的線程都有自己的值,則應該使用線程局部存儲(TLS,Thread???? ? Local?? Strorage)。?? ??? ? 在程序里加入預編譯指令,或在開發環境的項目設置里也可以達到設置數據段屬性的目的.必須給?? ? 這些變量賦初值,否則編譯器會把沒有賦初始值的變量放在一個叫未被初始化的數據段中。?? ??? ??? ? extern?? "C"?? 指示編譯器用C語言方法給函數命名。?? ??? ? 在制作DLL導出函數時由于C++存在函數重載,因此?? ? __declspec(dllexport)?? function(int,int)?? 在DLL會被decorate,例如被decorate成為?? function_int_int,而且不同的編譯器decorate的方法不同,造成了在用GetProcAddress取得function地址時的不便,使用extern?? "C"時,上述的decorate不會發生,因為C沒有函數重載,但如此一來被extern"C"修飾的函數,就不具備重載能力,可以說extern?? 和?? extern?? "C"不是以回事。 __declspec(dllexport)?? 聲明一個導出函數,是說這個函數要從本DLL導出。我要給別人用。一般用于dll中 __declspec(dllimport)?? 聲明一個導入函數,是說這個函數是從別的DLL導入。我要用。一般用于使用某個dll的exe中? _declspec(thread)可以降低程序員的負擔,又能做到線程局部存儲的要求。VC++允許一個變量或結構被聲明為“具有線程局部性”。例如,下面的聲明,如果放在一個DLL之中,將產生出一個全局變量,對每一個進程而言獨一無二:?? ? DWORD?? gProgressCounter;?? ? 但是如果這樣聲明,它就是對每一個線程獨一無二:?? ? _declspec(thread)???? DWORD?? gProgressCounter;?? ? 每一個以這種方式聲明對象的EXE和DLL,將在可執行文件中有一個特殊的節區(section),內含所有的線程局部變量。當EXE或DLL被載入時,操作系統會認識這個節區并適當的處理之。這個節區會被操作系統社定為“對每一個線程具有局部性”。?? ? 當一個EXE被載入時,操作系統掃描可執行文件以及所有靜態鏈接(implicity?? linked)的DLLs,以便找出所有的線程局部節區。所有節區的大小被加總在一起以求出每個線程啟動時應該配置的內存數量。 1. __declspec(align(16)) struct SS{ int a,b; }; ?它與#pragma pack()是一對兄弟,前者規定了對齊的最小值,后者規定了對齊的最大值。同時出現時,前者優先級高。 __declspec(align())的一個特點是,它僅僅規定了數據對齊的位置,而沒有規定數據實際占用的內存長度,當指定的數據被放置在確定的位置之后,其后的數據填充仍然是按照#pragma pack規定的方式填充的,這時候類/結構的實際大小和內存格局的規則是這樣的:在__declspec(align())之前,數據按照#pragma pack規定的方式填充,如前所述。當遇到__declspec(align())的時候,首先尋找距離當前偏移向后最近的對齊點(滿足對齊長度為max(數據自身長度,指定值)),然后把被指定的數據類型從這個點開始填充,其后的數據類型從它的后面開始,仍然按照#pragma pack填充,直到遇到下一個__declspec(align())。當所有數據填充完畢,把結構的整體對齊數值和__declspec(align())規定的值做比較,取其中較大的作為整個結構的對齊長度。 特別的,當__declspec(align())指定的數值比對應類型長度小的時候,這個指定不起作用。 2. #pragma section("segname",read) ??? / __declspec(allocate("segname")) int i = 0; ??? / int main(){ return 1;}; ?此關鍵詞必須跟隨code_seg,const_seg,data_seg,init_seg,section關鍵字之后使用,以上例子使用了section關鍵字。使用此關鍵字將告知編譯器,其后的變量間被分配在那個數據段。 3. __declspec(deprecated(MY_TEXT)) void func(int) {} ?與pragma deprecated()相同。此聲明后,如果在同一作用域中使用func(int)函數,將被提醒c4996警告。 4. __declspec( dllimport ) declarator ?? & __declspec( dllexport ) declarator ?無須多說,此二關鍵字用于導入導出外接元素。 5. __declspec(jitintrinsic) ?用于標記一個函數或元素為64位公共語言運行時。具體用法未見到。 6. __declspec( naked ) int func( formal_parameters ) {} ?此關鍵字僅用于x86系統,多用于硬件驅動。此關鍵字可以使編譯器在生成代碼時不包含任何注釋或標記。僅可以對函數的定義使用,不能用于數據聲明、定義,或者函數的聲明。 7. __declspec(restrict) float * init(int m, int n) {}; ?? & __declspec(noalias) void multiply(float * a, float * b, float * c) {};// 優化必用! ?__declspec(restrict)僅適用于返回指針的函數聲明,如 __declspec(restrict) void *malloc(size_t size);restrict declspec 適用于返回非別名指針的函數。此關鍵字用于 malloc 的 C 運行時庫實現,因為它決不會返回已經在當前程序中使用的指針值(除非您執行某個非法操作,如在內存已被釋放之后使用它)。restrict declspec 為編譯器提供執行編譯器優化的更多信息。對于編譯器來說,最大的困難之一是確定哪些指針會與其他指針混淆,而使用這些信息對編譯器很有幫助。有必要指出,這是對編譯器的一個承諾,編譯器并不對其進行驗證。如果您的程序不恰當地使用 restrict declspec,則該程序的行為會不正確。 __declspec(noalias)也是僅適用于函數,它指出該函數是半純粹的函數。半純粹的函數是指僅引用或修改局部變量、參數和第一層間接參數。此 declspec 是對編譯器的一個承諾,如果該函數引用全局變量或第二層間接指針參數,則編譯器會生成將中斷應用程序的代碼。 8. class X { ?? / __declspec(noinline) int mbrfunc() { return 0; /* will not inline*/ }; ?在類中聲明一個函數不需要內聯。 9. __declspec(noreturn) extern void fatal () {} ?不需要返回值。 10. void __declspec(nothrow) __stdcall f2(); ?不存在異常拋出。 11. struct __declspec(novtable) X { virtual void mf(); }; ??? / struct Y : public X {void mf() {printf_s("In Y/n");}}; ?此關鍵字標記的類或結構不能直接實例化,否則將引發AV錯誤(access violation)。此關鍵字的聲明將阻止編譯器對構造和析構函數的vfptr的初始化??蓛灮幾g后代碼大小。 12. struct S {?? int i; ??? / void putprop(int j) {? i = j; } ??? / int getprop() { return i; } ??? / __declspec(property(get = getprop, put = putprop)) int the_prop;}; ?此關鍵字與C#中get & set屬性相同,可定義實現針對一個字段的可讀或可寫。以上例子,可以使用(如果實例化S為ss)如:ss.the_prop = 156;(此時,ss.i == 156)接著如果:cout<< s.the_prop;(此時將調用getprop,使返回156)。 13. __declspec(selectany)(轉) ?在MFC,ATL的源代碼中充斥著__declspec(selectany)的聲明。selectany可以讓我們在.h文件中初始化一個全局變量而不是只能放在.cpp中。比如有一個類,其中有一個靜態變量,那么我們可以在.h中通過類似__declspec(selectany) type class::variable = value; 這樣的代碼來初始化這個全局變量。既是該.h被多次include,鏈接器也會為我們剔除多重定義的錯誤。對于template的編程會有很多便利。 14. __declspec(thread) int in_One_Thread; ?聲明in_One_Thread為線程局部變量并具有線程存儲時限,以便鏈接器安排在創建線程時自動分配的存儲。 15. struct __declspec(uuid("00000000-0000-0000-c000-000000000046")) IUnknown; ?將具有唯一表示符號的已注冊內容聲明為一個變量,可使用__uuidof()調用。 | |