· 比較大的應用程序都由很多模塊組成,這些模塊分別完成相對獨立的功能,它們彼此協(xié)作來完成整個軟件系統(tǒng)的工作。可能存在一些模塊的功能較為通用,在構(gòu)造其它軟件系統(tǒng)時仍會被使用。在構(gòu)造軟件系統(tǒng)時,如果將所有模塊的源代碼都靜態(tài)編譯到整個應用程序 EXE 文件中,會產(chǎn)生一些問題:一個缺點是增加了應用程序的大小,它會占用更多的磁盤空間,程序運行時也會消耗較大的內(nèi)存空間,造成系統(tǒng)資源的浪費;另一個缺點是,在編寫大的 EXE 程序時,在每次修改重建時都必須調(diào)整編譯所有源代碼,增加了編譯過程的復雜性,也不利于階段性的單元測試。
Windows 系統(tǒng)平臺上提供了一種完全不同的較有效的編程和運行環(huán)境,你可以將獨立的程序模塊創(chuàng)建為較小的 DLL (Dynamic Linkable Library) 文件,并可對它們單獨編譯和測試。在運行時,只有當 EXE 程序確實要調(diào)用這些 DLL 模塊的情況下,系統(tǒng)才會將它們裝載到內(nèi)存空間中。這種方式不僅減少了 EXE 文件的大小和對內(nèi)存空間的需求,而且使這些 DLL 模塊可以同時被多個應用程序使用。Windows 自己就將一些主要的系統(tǒng)功能以 DLL 模塊的形式實現(xiàn)。
一般來說,DLL 是一種磁盤文件,以.dll、.DRV、.FON、.SYS 和許多以 .EXE 為擴展名的系統(tǒng)文件都可以是 DLL。它由全局數(shù)據(jù)、服務函數(shù)和資源組成,在運行時被系統(tǒng)加載到調(diào)用進程的虛擬空間中,成為調(diào)用進程的一部分。如果與其它 DLL 之間沒有沖突,該文件通常映射到進程虛擬空間的同一地址上。DLL 模塊中包含各種導出函數(shù),用于向外界提供服務。DLL 可以有自己的數(shù)據(jù)段,但沒有自己的堆棧,使用與調(diào)用它的應用程序相同的堆棧模式;一個 DLL 在內(nèi)存中只有一個實例;DLL 實現(xiàn)了代碼封裝性;DLL 的編制與具體的編程語言及編譯器無關(guān)。
在 Win32 環(huán)境中,每個進程都復制了自己的讀/寫全局變量。如果想要與其它進程共享內(nèi)存,必須使用內(nèi)存映射文件或者聲明一個共享數(shù)據(jù)段。DLL 模塊需要的堆棧內(nèi)存都是從運行進程的堆棧中分配出來的。Windows 在加載 DLL 模塊時將進程函數(shù)調(diào)用與 DLL 文件的導出函數(shù)相匹配。Windows 操作系統(tǒng)對 DLL 的操作僅僅是把 DLL 映射到需要它的進程的虛擬地址空間里去。DLL 函數(shù)中的代碼所創(chuàng)建的任何對象(包括變量)都歸調(diào)用它的線程或進程所有。
調(diào)用方式
1、靜態(tài)調(diào)用方式:由編譯系統(tǒng)完成對 DLL 的加載和應用程序結(jié)束時 DLL 卸載的編碼(如還有其它程序使用該 DLL,則 Windows 對 DLL 的應用記錄減1,直到所有相關(guān)程序都結(jié)束對該 DLL 的使用時才釋放它,簡單實用,但不夠靈活,只能滿足一般要求。
隱式的調(diào)用:需要把產(chǎn)生動態(tài)連接庫時產(chǎn)生的 .LIB 文件加入到應用程序的工程中,想使用 DLL 中的函數(shù)時,只須說明一下。隱式調(diào)用不需要調(diào)用 LoadLibrary() 和 FreeLibrary()。程序員在建立一個 DLL 文件時,鏈接程序會自動生成一個與之對應的 LIB 導入文件。該文件包含了每一個 DLL 導出函數(shù)的符號名和可選的標識號,但是并不含有實際的代碼。LIB 文件作為 DLL 的替代文件被編譯到應用程序項目中。
當程序員通過靜態(tài)鏈接方式編譯生成應用程序時,應用程序中的調(diào)用函數(shù)與 LIB 文件中導出符號相匹配,這些符號或標識號進入到生成的 EXE 文件中。LIB 文件中也包含了對應的 DL L文件名(但不是完全的路徑名),鏈接程序?qū)⑵浯鎯υ?span lang=EN-US> EXE 文件內(nèi)部。
當應用程序運行過程中需要加載 DLL 文件時,Windows 根據(jù)這些信息發(fā)現(xiàn)并加載 DLL,然后通過符號名或標識號實現(xiàn)對 DLL 函數(shù)的動態(tài)鏈接。所有被應用程序調(diào)用的 DLL 文件都會在應用程序 EXE 文件加載時被加載在到內(nèi)存中。可執(zhí)行程序鏈接到一個包含 DLL 輸出函數(shù)信息的輸入庫文件(.LIB文件)。操作系統(tǒng)在加載使用可執(zhí)行程序時加載 DLL??蓤?zhí)行程序直接通過函數(shù)名調(diào)用 DLL 的輸出函數(shù),調(diào)用方法和程序內(nèi)部其它的函數(shù)是一樣的。
2、動態(tài)調(diào)用方式:是由編程者用 API 函數(shù)加載和卸載 DLL 來達到調(diào)用 DLL 的目的,使用上較復雜,但能更加有效地使用內(nèi)存,是編制大型應用程序時的重要方式。
顯式的調(diào)用:是指在應用程序中用 LoadLibrary 或 MFC 提供的 AfxLoadLibrary 顯式的將自己所做的動態(tài)連接庫調(diào)進來,動態(tài)連接庫的文件名即是上面兩個函數(shù)的參數(shù),再用 GetProcAddress() 獲取想要引入的函數(shù)。自此,你就可以象使用如同本應用程序自定義的函數(shù)一樣來調(diào)用此引入函數(shù)了。在應用程序退出之前,應該用 FreeLibrary 或 MFC 提供的 AfxFreeLibrary 釋放動態(tài)連接庫。直接調(diào)用 Win32 的 LoadLibary 函數(shù),并指定 DLL 的路徑作為參數(shù)。LoadLibary 返回 HINSTANCE 參數(shù),應用程序在調(diào)用 GetProcAddress 函數(shù)時使用這一參數(shù)。GetProcAddress 函數(shù)將符號名或標識號轉(zhuǎn)換為 DLL 內(nèi)部的地址。程序員可以決定 DLL 文件何時加載或不加載,顯式鏈接在運行時決定加載哪個 DLL 文件。使用 DLL 的程序在使用之前必須加載(LoadLibrary)加載DLL從而得到一個DLL模塊的句柄,然后調(diào)用 GetProcAddress 函數(shù)得到輸出函數(shù)的指針,在退出之前必須卸載DLL(FreeLibrary)。
Windows將遵循下面的搜索順序來定位 DLL:
1. 包含EXE文件的目錄
2. 進程的當前工作目錄
3. Windows系統(tǒng)目錄
4. Windows目錄
5. 列在 Path 環(huán)境變量中的一系列目錄
MFC中的DLL
o Non-MFC DLL:指的是不用 MFC 的類庫結(jié)構(gòu),直接用 C 語言寫的 DLL,其輸出的函數(shù)一般用的是標準 C 接口,并能被 非 MFC 或 MFC 編寫的應用程序所調(diào)用。
o Regular DLL:和下述的 Extension DLLs 一樣,是用 MFC 類庫編寫的。明顯的特點是在源文件里有一個繼承 CWinApp 的類。其又可細分成靜態(tài)連接到 MFC 和動態(tài)連接到 MFC 上的。
靜態(tài)連接到 MFC 的動態(tài)連接庫只被 VC 的專業(yè) 版和企業(yè)版所支持。該類 DLL 應用程序里頭的輸出函數(shù)可以被任意 Win32 程序使用,包括使用 MFC 的應用程序。輸入函數(shù)有如下形式:
extern "C" EXPORT YourExportedFunction();
如果沒有 extern "C" 修飾,輸出函數(shù)僅僅能從 C++ 代碼中調(diào)用。
DLL 應用程序從 CWinApp 派生,但沒有消息循環(huán)。
動態(tài)鏈接到 MFC 的 規(guī)則 DLL 應用程序里頭的輸出函數(shù)可以被任意 Win32 程序使用,包括使用 MFC 的應用程序。但是,所有從 DLL 輸出的函數(shù)應該以如下語句開始:
AFX_MANAGE_STATE(AfxGetStaticModuleState( ))
此語句用來正確地切換 MFC 模塊狀態(tài)。
Regular DLL能夠被所有支持 DLL 技術(shù)的語言所編寫的應用程序所調(diào)用。在這種動態(tài)連接庫中,它必須有一個從 CWinApp 繼承下來的類,DLLMain 函數(shù)被 MFC 所提供,不用自己顯式的寫出來。
o Extension DLL:用來實現(xiàn)從 MFC 所繼承下來的類的重新利用,也就是說,用這種類型的動態(tài)連接庫,可以用來輸出一個從 MFC 所繼承下來的類。它輸出的函數(shù)僅可以被使用 MFC 且動態(tài)鏈接到 MFC 的應用程序使用??梢詮?span lang=EN-US> MFC 繼承你所想要的、更適于你自己用的類,并把它提供給你的應用程序。你也可隨意的給你的應用程序提供 MFC 或 MFC 繼承類的對象指針。Extension DLL使用 MFC 的動態(tài)連接版本所創(chuàng)建的,并且它只被用 MFC 類庫所編寫的應用程序所調(diào)用。Extension DLLs 和 Regular DLLs 不一樣,它沒有從 CWinApp 繼承而來的類的對象,所以,你必須為自己 DLLMain 函數(shù)添加初始化代碼和結(jié)束代碼。
和規(guī)則 DLL 相比,有以下不同:
1、它沒有從 CWinApp 派生的對象;
2、它必須有一個 DLLMain 函數(shù);
3、DLLMain 調(diào)用 AfxInitExtensionModule 函數(shù),必須檢查該函數(shù)的返回值,如果返回0,DLLMmain 也返回 0;
o 4、如果它希望輸出 CRuntimeClass 類型的對象或者資源,則需要提供一個初始化函數(shù)來創(chuàng)建一個 CDynLinkLibrary 對象。并且,有必要把初始化函數(shù)輸出;
5、使用擴展 DLL 的 MFC 應用程序必須有一個從 CWinApp 派生的類,而且,一般在InitInstance 里調(diào)用擴展 DLL 的初始化函數(shù)。
DLL入口函數(shù)
1、每一個 DLL 必須有一個入口點,DLLMain 是一個缺省的入口函數(shù)。DLLMain 負責初始化和結(jié)束工作,每當一個新的進程或者該進程的新的線程訪問 DLL 時,或者訪問 DLL 的每一個進程或者線程不再使用DLL或者結(jié)束時,都會調(diào)用 DLLMain。但是,使用 TerminateProcess 或 TerminateThread 結(jié)束進程或者線程,不會調(diào)用 DLLMain。
DLLMain的函數(shù)原型:
BOOL APIENTRY DLLMain(HANDLE hModule,DWORD ul_reason_for_call,LPVOID
lpReserved)
{
switch(ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
.......
case DLL_THREAD_ATTACH:
.......
case DLL_THREAD_DETACH:
.......
case DLL_PROCESS_DETACH:
.......
return TRUE;
}
}
參數(shù):
hMoudle:是動態(tài)庫被調(diào)用時所傳遞來的一個指向自己的句柄(實際上,它是指向_DGROUP段的一個選擇符);
ul_reason_for_call:是一個說明動態(tài)庫被調(diào)原因的標志。當進程或線程裝入或卸載動態(tài)連接庫的時候,操作系統(tǒng)調(diào)用入口函數(shù),并說明動態(tài)連接庫被調(diào)用的原因。它所有的可能值為:
DLL_PROCESS_ATTACH: 進程被調(diào)用;
DLL_THREAD_ATTACH: 線程被調(diào)用;
DLL_PROCESS_DETACH: 進程被停止;
DLL_THREAD_DETACH: 線程被停止;
lpReserved:是一個被系統(tǒng)所保留的參數(shù);
2、_DLLMainCRTStartup
為了使用 "C" 運行庫 (CRT,C Run time Library) 的 DLL 版本(多線程),一個 DLL 應用程序必須指定 _DLLMainCRTStartup 為入口函數(shù),DLL 的初始化函數(shù)必須是 DLLMain。
_DLLMainCRTStartup 完成以下任務:當進程或線程捆綁(Attach) 到 DLL 時為 "C" 運行時的數(shù)據(jù) (C Runtime Data) 分配空間和初始化并且構(gòu)造全局 "C++"對象,當進程或者線程終止使用DLL(Detach) 時,清理 C Runtime Data 并且銷毀全局 "C++" 對象。它還調(diào)用 DLLMain 和 RawDLLMain 函數(shù)。
RawDLLMain 在 DLL 應用程序動態(tài)鏈接到 MFC DLL 時被需要,但它是靜態(tài)鏈接到 DLL 應用程序的。在講述狀態(tài)管理時解釋其原因。
關(guān)于調(diào)用約定
動態(tài)庫輸出函數(shù)的約定有兩種:調(diào)用約定和名字修飾約定。
1)調(diào)用約定(Calling convention):決定函數(shù)參數(shù)傳送時入棧和出棧的順序,由調(diào)用者還是被調(diào)用者把參數(shù)彈出棧,以及編譯器用來識別函數(shù)名字的修飾約定。
函數(shù)調(diào)用約定有多種,這里簡單說一下:
1、__stdcall 調(diào)用約定相當于16位動態(tài)庫中經(jīng)常使用的 PASCAL 調(diào)用約定。在32位的 VC++5.0 中PASCAL 調(diào)用約定不再被支持(實際上它已被定義為__stdcall。除了__pascal 外,__fortran 和__syscall也不被支持),取而代之的是 __stdcall 調(diào)用約定。兩者實質(zhì)上是一致的,即函數(shù)的參數(shù)自右向左通過棧傳遞,被調(diào)用的函數(shù)在返回前清理傳送參數(shù)的內(nèi)存棧,但不同的是函數(shù)名的修飾部分(關(guān)于函數(shù)名的修飾部分在后面將詳細說明)。
_stdcall 是 Pascal 程序的缺省調(diào)用方式,通常用于 Win32 API 中,函數(shù)采用從右到左的壓棧方式,自己在退出時清空堆棧。VC 將函數(shù)編譯后會在函數(shù)名前面加上下劃線前綴,在函數(shù)名后加上 "@" 和參數(shù)的字節(jié)數(shù)。
2、C 調(diào)用約定(即用__cdecl 關(guān)鍵字說明)按從右至左的順序壓參數(shù)入棧,由調(diào)用者把參數(shù)彈出棧。對于傳送參數(shù)的內(nèi)存棧是由調(diào)用者來維護的(正因為如此,實現(xiàn)可變參數(shù)的函數(shù)只能使用該調(diào)用約定)。另外,在函數(shù)名修飾約定方面也有所不同。
_cdecl 是 C 和 C++ 程序缺省的調(diào)用方式。每一個調(diào)用它的函數(shù)都包含清空堆棧的代碼,所以產(chǎn)生的可執(zhí)行文件大小會比調(diào)用 _stdcall 函數(shù)的大。函數(shù)采用從右到左的壓棧方式。VC 將函數(shù)編譯后會在函數(shù)名前面加上下劃線前綴。它是 MFC 缺省調(diào)用約定。
3、__fastcall 調(diào)用約定是 "人" 如其名,它的主要特點就是快,因為它是通過寄存器來傳送參數(shù)的(實際上,它用 ECX 和 EDX 傳送前兩個雙字(DWORD)或更小的參數(shù),剩下的參數(shù)仍舊自右向左壓棧傳送,被調(diào)用的函數(shù)在返回前清理傳送參數(shù)的內(nèi)存棧),在函數(shù)名修飾約定方面,它和前兩者均不同。
_fastcall方式的函數(shù)采用寄存器傳遞參數(shù),VC 將函數(shù)編譯后會在函數(shù)名前面加上"@"前綴,在函數(shù)名后加上"@"和參數(shù)的字節(jié)數(shù)。
4、thiscall 僅僅應用于 "C++" 成員函數(shù)。this 指針存放于 CX 寄存器,參數(shù)從右到左壓。thiscall 不是關(guān)鍵詞,因此不能被程序員指定。
5、naked call采用 1-4 的調(diào)用約定時,如果必要的話,進入函數(shù)時編譯器會產(chǎn)生代碼來保存ESI,EDI,EBX,EBP寄存器,退出函數(shù)時則產(chǎn)生代碼恢復這些寄存器的內(nèi)容。
naked call不產(chǎn)生這樣的代碼。naked call不是類型修飾符,故必須和_declspec 共同使用。
關(guān)鍵字 __stdcall、__cdecl 和 __fastcall 可以直接加在要輸出的函數(shù)前,也可以在編譯環(huán)境的 Setting...\C/C++ \Code Generation 項選擇。當加在輸出函數(shù)前的關(guān)鍵字與編譯環(huán)境中的選擇不同時,直接加在輸出函數(shù)前的關(guān)鍵字有效。它們對應的命令行參數(shù)分別為/Gz、/Gd 和 /Gr。缺省狀態(tài)為/Gd,即__cdecl。
要完全模仿 PASCAL 調(diào)用約定首先必須使用 __stdcall 調(diào)用約定,至于函數(shù)名修飾約定,可以通過其它方法模仿。還有一個值得一提的是 WINAPI 宏,Windows.h 支持該宏,它可以將出函數(shù)翻譯成適當?shù)恼{(diào)用約定,在 WIN32 中,它被定義為 __stdcall。使用 WINAPI 宏可以創(chuàng)建自己的 APIs。
2)名字修飾約定
1、修飾名(Decoration name)
"C" 或者 "C++" 函數(shù)在內(nèi)部(編譯和鏈接)通過修飾名識別。修飾名是編譯器在編譯函數(shù)定義或者原型時生成的字符串。有些情況下使用函數(shù)的修飾名是必要的,如在模塊定義文件里頭指定輸出"C++"重載函數(shù)、構(gòu)造函數(shù)、析構(gòu)函數(shù),又如在匯編代碼里調(diào)用"C""或"C++"函數(shù)等。
修飾名由函數(shù)名、類名、調(diào)用約定、返回類型、參數(shù)等共同決定。
2、名字修飾約定隨調(diào)用約定和編譯種類(C或C++)的不同而變化。函數(shù)名修飾約定隨編譯種類和調(diào)用約定的不同而不同,下面分別說明。
a、C編譯時函數(shù)名修飾約定規(guī)則:
__stdcall 調(diào)用約定在輸出函數(shù)名前加上一個下劃線前綴,后面加上一個"@"符號和其參數(shù)的字節(jié)數(shù),格式為 _functionname@number。
__cdecl調(diào)用約定僅在輸出函數(shù)名前加上一個下劃線前綴,格式為 _functionname。
__fastcall調(diào)用約定在輸出函數(shù)名前加上一個"@"符號,后面也是一個"@"符號和其參數(shù)的字節(jié)數(shù),格式為@functionname@number。
它們均不改變輸出函數(shù)名中的字符大小寫,這和PASCAL調(diào)用約定不同,PASCAL約定輸出的函數(shù)名無任何修飾且全部大寫。
b、C++編譯時函數(shù)名修飾約定規(guī)則:
__stdcall調(diào)用約定:
1、以"?"標識函數(shù)名的開始,后跟函數(shù)名;
2、函數(shù)名后面以"@@YG"標識參數(shù)表的開始,后跟參數(shù)表;
3、參數(shù)表以代號表示:
X——void,
D——char,
E——unsigned char,
F——short,
H——int,
I——unsigned int,
J——long,
K——unsigned long,
M——float,
N——double,
_N——bool,
....
PA——表示指針,后面的代號表明指針類型,如果相同類型的指針連續(xù)出現(xiàn),以"0"代替,一個"0"代表一次重復;
4、參數(shù)表的第一項為該函數(shù)的返回值類型,其后依次為參數(shù)的數(shù)據(jù)類型,指針標識在其所指數(shù)據(jù)類型前;
5、參數(shù)表后以"@Z"標識整個名字的結(jié)束,如果該函數(shù)無參數(shù),則以"Z"標識結(jié)束。
其格式為"?functionname@@YG*****@Z"或"?functionname@@YG*XZ",
例如
int Test1(char *var1,unsigned long)-----“?Test1@@YGHPADK@Z”
void Test2() -----“?Test2@@YGXXZ”
__cdecl調(diào)用約定:
規(guī)則同上面的_stdcall調(diào)用約定,只是參數(shù)表的開始標識由上面的"@@YG"變?yōu)?span lang=EN-US>"@@YA"。
__fastcall調(diào)用約定:
規(guī)則同上面的_stdcall調(diào)用約定,只是參數(shù)表的開始標識由上面的"@@YG"變?yōu)?span lang=EN-US>"@@YI"。
VC++對函數(shù)的省缺聲明是"__cedcl",將只能被C/C++調(diào)用。
關(guān)于DLL的函數(shù)
動態(tài)鏈接庫中定義有兩種函數(shù):導出函數(shù)(export function)和內(nèi)部函數(shù)(internal function)。導出函數(shù)可以被其它模塊調(diào)用,內(nèi)部函數(shù)在定義它們的DLL程序內(nèi)部使用。
輸出函數(shù)的方法有以下幾種:
1、傳統(tǒng)的方法
在模塊定義文件的 EXPORT 部分指定要輸入的函數(shù)或者變量。語法格式如下:
entryname[=internalname] [@ordinal[NONAME]] [DATA] [PRIVATE]
其中:
entryname 是輸出的函數(shù)或者數(shù)據(jù)被引用的名稱;
internalname 同 entryname;
@ordinal 表示在輸出表中的順序號(index);
NONAME 僅僅在按順序號輸出時被使用(不使用 entryname );
DATA 表示輸出的是數(shù)據(jù)項,使用 DLL 輸出數(shù)據(jù)的程序必須聲明該數(shù)據(jù)項為 _declspec(DLLimport)。
上述各項中,只有 entryname 項是必須的,其他可以省略。
對于"C"函數(shù)來說,entryname 可以等同于函數(shù)名;但是對 "C++" 函數(shù)(成員函數(shù)、非成員函數(shù))來說,entryname 是修飾名。可以從 .map 映像文件中得到要輸出函數(shù)的修飾名,或者使用DUMPBIN /SYMBOLS 得到,然后把它們寫在 .def 文件的輸出模塊。DUMPBIN 是VC提供的一個工具。
如果要輸出一個 "C++" 類,則把要輸出的數(shù)據(jù)和成員的修飾名都寫入 .def 模塊定義文件。
2、在命令行輸出
對鏈接程序 LINK 指定 /EXPORT 命令行參數(shù),輸出有關(guān)函數(shù)。
3、使用 MFC 提供的修飾符號 _declspec(DLLexport)
在要輸出的函數(shù)、類、數(shù)據(jù)的聲明前加上 _declspec(DLLexport) 修飾符表示輸出。__declspec(DLLexport) 在 C 調(diào)用約定、C 編譯情況下可以去掉輸出函數(shù)名的下劃線前綴。extern "C" 使得在 C++ 中使用 C 編譯方式成為可能。在"C++"下定義"C"函數(shù)需要加 extern "C" 關(guān)鍵詞。用 extern "C" 來指明該函數(shù)使用 C 編譯方式。輸出的 "C" 函數(shù)可以從 "C" 代碼里調(diào)用。
例如,在一個 C++ 文件中,有如下函數(shù):
extern "C" {void __declspec(DLLexport) __cdecl Test(int var);}
其輸出函數(shù)名為: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 應用程序的實現(xiàn)中,則表示輸出(因為_AFX_EXT被定義,通常是在編譯器的標識參數(shù)中指定該選項 /D_AFX_EXT);如果用于使用DLL的應用程序中,則表示輸入(_AFX_EXT沒有定義)。
要輸出整個的類,對類使用_declspec(_DLLexpot);要輸出類的成員函數(shù),則對該函數(shù)使用_declspec(_DLLexport)。如:
class AFX_EXT_CLASS CTextDoc : public CDocument
{
…
}
extern "C" AFX_EXT_API void WINAPI InitMYDLL();
這幾種方法中,最好采用第三種,方便好用;其次是第一種,如果按順序號輸出,調(diào)用效率會高些;最次是第二種。
模塊定義文件(.DEF)
模塊定義文件(.DEF)是一個或多個用于描述 DLL 屬性的模塊語句組成的文本文件,每個DEF文件至少必須包含以下模塊定義語句:
o 第一個語句必須是LIBRARY語句,指出DLL的名字;
o EXPORTS 語句列出被導出函數(shù)的名字;將要輸出的函數(shù)修飾名羅列在 EXPORTS 之下,這個名字必須與定義函數(shù)的名字完全一致,如此就得到一個沒有任何修飾的函數(shù)名了。
o 可以使用DESCRIPTION語句描述DLL的用途(此句可選);
o ";"對一行進行注釋(可選)。 DLL程序和調(diào)用其輸出函數(shù)的程序的關(guān)系
1、DLL與進程、線程之間的關(guān)系
o DLL模塊被映射到調(diào)用它的進程的虛擬地址空間。
o DLL使用的內(nèi)存從調(diào)用進程的虛擬地址空間分配,只能被該進程的線程所訪問。
o DLL的句柄可以被調(diào)用進程使用;調(diào)用進程的句柄可以被DLL使用。
o DLL使用調(diào)用進程的棧。
2、關(guān)于共享數(shù)據(jù)段
DLL定義的全局變量可以被調(diào)用進程訪問;DLL可以訪問調(diào)用進程的全局數(shù)據(jù)。使用同一DLL的每一個進程都有自己的DLL全局變量實例。如果多個線程并發(fā)訪問同一變量,則需要使用同步機制;對一個DLL的變量,如果希望每個使用DLL的線程都有自己的值,則應該使用線程局部存儲(TLS,Thread Local Strorage)。
在程序里加入預編譯指令,或在開發(fā)環(huán)境的項目設置里也可以達到設置數(shù)據(jù)段屬性的目的.必須給這些變量賦初值,否則編譯器會把沒有賦初始值的變量放在一個叫未被初始化的數(shù)據(jù)段中。