Windows系統平臺提供了壹個完全不同的有效的編程和運行環境。您可以將獨立的程序模塊創建為較小的DLL(動態鏈接庫)文件,並單獨編譯和測試它們。在運行時,只有當EXE程序真正想要調用它們時,系統才會將這些DLL模塊加載到內存空間中。這種方法不僅減少了EXE文件的大小和對內存空間的需求,還使這些DLL模塊能夠被多個應用程序同時使用。Windows本身以DLL模塊的形式實現了壹些主要的系統功能。
壹般來說,DLL是壹種磁盤文件,可以。dll,。DRV。豐,。SYS和許多帶有。EXE作為擴展名。它由全局數據、服務函數和資源組成,在運行時被系統加載到調用進程的虛擬空間中,成為調用進程的壹部分。如果與其他dll沒有沖突,文件通常被映射到進程的虛擬空間中的同壹個地址。DLL模塊包含各種導出函數,向外界提供服務。DLL可以有自己的數據段,但不能有自己的堆棧,使用與調用它的應用程序相同的堆棧模式;壹個DLL在內存中只有壹個實例;DLL實現代碼封裝;DLL的編譯與具體的編程語言和編譯器無關。
在Win32環境中,每個進程都復制自己的讀/寫全局變量。如果要與其他進程共享內存,必須使用內存映射文件或聲明壹個* * *共享數據段。DLL模塊所需的堆棧內存是從運行進程的堆棧中分配的。Windows在加載DLL模塊時將進程函數調用與DLL文件的導出函數相匹配。Windows操作系統對DLL的操作只是將DLL映射到需要它的進程的虛擬地址空間。DLL函數中的代碼創建的任何對象(包括變量)都屬於調用它的線程或進程。
呼叫模式
1.靜態調用方式:編譯器在應用程序結束時完成加載DLL和卸載DLL的編碼(如果有其他程序使用該DLL,Windows對該DLL的應用記錄將減少1,直到所有相關程序都使用完該DLL後才釋放。這個簡單實用,但是不夠靈活,只能滿足壹般要求。
隱式調用:您需要添加。生成應用程序項目的動態鏈接庫時生成的LIB文件。當妳想使用DLL中的函數時,妳只需要解釋它。隱式調用不需要調用LoadLibrary()和FreeLibrary()。當程序員創建壹個DLL文件時,鏈接器會自動生成壹個對應的LIB導入文件。該文件包含每個DLL導出函數的符號名和可選標識號,但不包含實際代碼。LIB文件作為DLL的替代文件被編譯到應用程序項目中。
程序員通過靜態鏈接編譯生成應用程序時,應用程序中的調用函數與LIB文件中導出的符號相匹配,這些符號或標識號進入生成的EXE文件。LIB文件還包含相應的DL L文件名(但不是完整的路徑名),該文件名由鏈接器存儲在EXE文件中。
當應用程序需要加載DLL文件時,Windows根據這些信息找到並加載DLL,然後通過符號名或標識號動態鏈接DLL函數。加載應用程序EXE文件時,應用程序調用的所有DLL文件都將被加載到內存中。可執行程序鏈接到輸入庫文件(。lib文件),包含DLL輸出函數的信息。操作系統在加載可執行程序時加載DLL。可執行程序通過函數名直接調用DLL的輸出函數,調用方法與程序內部其他函數相同。
2.動態調用方式:程序員用API函數加載和卸載DLL,達到調用DLL的目的,使用起來比較復雜,但可以更有效地利用內存,是編譯大型應用程序的重要方式。
顯式調用:指用MFC提供的LoadLibrary或AfxLoadLibrary在應用程序中顯式調用自己制作的動態鏈接庫,動態鏈接庫的文件名就是上述兩個函數的參數,然後用GetProcAddress()獲取要引入的函數。從那時起,您可以調用這個傳入函數,就像它是這個應用程序的自定義函數壹樣。在應用程序退出之前,應該使用MFC提供的FreeLibrary或者AfxFreeLibrary來釋放動態鏈接庫。直接調用Win32的LoadLibary函數,並將DLL的路徑指定為參數。LoadLibary返回應用程序在調用GetProcAddress函數時使用的提示參數。GetProcAddress函數將符號名或標識號轉換成DLL中的地址。程序員可以決定何時加載或不加載DLL文件,顯式鏈接決定在運行時加載哪個DLL文件。使用DLL的程序在使用前必須加載DLL以獲取某個DLL模塊的句柄,然後調用GetProcAddress函數獲取輸出函數的指針,退出前必須卸載DLL (Free LoadLibrary)。
Windows將按照下列搜索順序查找DLL:
包含EXE文件的目錄
進程的當前工作目錄
Windows系統目錄
Windows目錄
Path環境變量中列出的目錄列表。
MFC中的DLL
非MFC DLL:指不使用MFC類庫結構,直接用C語言編寫的DLL,其輸出函數壹般使用標準C接口,可以被非MFC或MFC編寫的應用程序調用。
常規DLL:和下面描述的擴展DLL壹樣,是用MFC類庫寫的。明顯的特點是源文件中有壹個繼承CWinApp的類。可以細分為靜態連接MFC和動態連接MFC。
靜態連接MFC的動態連接庫只有VC的專業版和企業版支持。任何Win32程序都可以使用這種DLL應用程序中的輸出函數,包括使用MFC的應用程序。輸入函數具有以下形式:
extern " C " EXPORT yourportedfunction();
如果沒有extern“C”修飾,輸出函數只能從C++代碼中調用。
DLL應用程序是從CWinApp派生的,但是沒有消息循環。
動態鏈接到MFC的常規DLL應用程序中的輸出函數可由任何Win32程序使用,包括使用MFC的應用程序。但是,DLL的所有函數輸出都應該以下面的語句開始:
AFX _ MANAGE _ STATE(AfxGetStaticModuleState())
該語句用於正確切換MFC模塊狀態。
用支持DLL技術的所有語言編寫的應用程序都可以調用常規DLL。在這個動態鏈接庫中,它必須有壹個繼承自CWinApp的類,而DLLMain函數是MFC提供的,所以不需要顯式編寫。
擴展DLL:用來復用從MFC繼承的類,也就是這種類型的動態鏈接庫可以用來輸出從MFC繼承的類。它輸出的函數只能由使用MFC並動態鏈接到它的應用程序使用。妳可以從MFC中繼承妳想要的,更適合自己使用的東西,提供給妳的應用。也可以隨意為應用程序提供MFC或MFC繼承類對象指針。擴展DLL是通過使用MFC的動態鏈接版本創建的,它僅由使用MFC類庫編寫的應用程序調用。擴展dll不同於常規dll,因為它沒有從CWinApp繼承的類對象,所以必須為DLLMain函數添加初始化代碼和結束代碼。
與常規DLL相比,有以下不同之處:
1,它沒有從CWinApp派生的對象;
2.它必須有壹個DLLMain函數;
3.當DLLMIN調用AfxInitExtensionModule函數時,需要檢查函數的返回值。如果它返回0,DLLMmain也返回0;
4.如果它想要輸出CRuntimeClass類型的對象或資源,它需要提供壹個初始化函數來創建壹個CDynLinkLibrary對象。而且,需要輸出初始化函數;
5.使用擴展dll的MFC應用程序必須有壹個從CWinApp派生的類,壹般在InitInstance中調用擴展dll的初始化函數。
DLL入口函數
1,每個DLL都必須有壹個入口點,DLLMain是默認的入口函數。DLLMain負責初始化和結束。每當壹個新進程或該進程的壹個新線程訪問該DLL時,或者每個訪問該DLL的進程或線程不再使用該DLL或結束時,將調用DLLMain。但是,使用TerminateProcess或TerminateThread結束進程或線程不會調用DLLMain。
DLLMain的函數原型;
BOOL API entry DLLMain(HANDLE hModule,DWORD ul_reason_for_call,LPVOID
LP保留)
{
開關(ul_reason_for_call)
{
案例DLL_PROCESS_ATTACH:
.......
案例DLL_THREAD_ATTACH:
.......
案例DLL_THREAD_DETACH:
.......
案例DLL_PROCESS_DETACH:
.......
返回TRUE
}
}
參數:
HMoudle:是調用動態庫時指向自身的句柄(其實是指向_DGROUP段的選擇器);
Ul_reason_for_call:是壹個標誌,解釋為什麽調用動態庫。當壹個進程或線程加載或卸載動態鏈接庫時,操作系統調用入口函數並解釋為什麽調用動態鏈接庫。它所有可能的值是:
DLL_PROCESS_ATTACH:進程被調用;
DLL_THREAD_ATTACH:線程被調用;
DLL_PROCESS_DETACH:進程已停止;
DLL_THREAD_DETACH:線程被停止;
LpReserved:系統保留的參數;
2 、_DLLMainCRTStartup
為了使用“C”運行時庫(CRT)的DLL版本(多線程),DLL應用程序必須指定_ DLLMainCRTSTARTUP作為入口函數,並且DLL的初始化函數必須是DLLMIN。
_DLLMainCRTStartup完成以下任務:當壹個進程或線程附加到壹個DLL時,它為“C”運行時數據(C Runtime Data)分配空間,並初始化和構造壹個全局“C++”對象;當壹個進程或線程停止使用DLL(分離)時,它會清除C運行時數據並銷毀全局“C++”對象。它還調用DLLMain和RawDLLMain函數。
當DLL應用程序動態鏈接到MFC DLL,但它靜態鏈接到DLL應用程序時,需要RawDLLMain。講狀態管理的時候說明原因。
關於呼叫約定
動態庫輸出函數有兩種約定:調用約定和名稱修改約定。
1)調用約定:確定函數參數傳遞時進出堆棧的順序,是調用者還是被調用者將參數彈出堆棧,以及編譯器用來標識函數名的修飾約定。
函數調用約定有很多種,這裏簡單介紹壹下:
1和__stdcall的調用約定相當於16位動態庫中常用的PASCAL調用約定。32位VC++5.0中不再支持PASCAL調用約定(實際上已經定義為__stdcall。除了__pascal,還不支持__fortran和__syscall。)而是使用__stdcall調用約定。兩者本質上是壹樣的,都是函數的參數從右向左通過堆棧傳遞,被調用的函數在返回前會清理傳遞參數的內存堆棧,不同的是函數名的裝飾部分(後面會詳細解釋函數名的裝飾部分)。
_stdcall是Pascal程序的默認調用方法,通常在Win32 API中使用。該函數采用從右向左的壓棧方式,退出時清空棧。編譯完函數後,VC會在函數名前面加壹個下劃線,在函數名後面加上“@”和參數的字節數。
2.C調用約定(也就是用關鍵字__cdecl解釋)從右向左將參數壓入堆棧,調用者將參數彈出堆棧。用於傳遞參數的內存堆棧由調用者維護(因此,實現可變參數的函數只能使用這種調用約定)。此外,函數名修飾的約定也有差異。
_cdecl是C和C++程序的默認調用方法。每個調用它的函數都包含清除堆棧的代碼,所以生成的可執行文件的大小會比調用_stdcall函數的大。函數從右向左堆疊。VC在編譯函數後會在函數名前加壹個下劃線前綴。這是MFC的默認調用約定。
3.__fastcall的調用約定顧名思義是“人”,主要特點是快,因為它通過寄存器傳遞參數(實際上是用ECX和EDX傳遞前兩個DWORD或者更小的參數,剩下的參數還是從右向左傳遞,被調用的函數在返回前清理傳遞參數的內存棧)。就函數名修改約定而言,它與前兩者不同。
_fastcall函數使用寄存器來傳遞參數。VC在編譯完函數後會在函數名前面加上“@”前綴,在函數名後面加上“@”和參數的字節數。
4.這個調用只適用於“C++”成員函數。該指針存儲在CX寄存器中,參數從右向左按下。這個調用不是壹個關鍵字,所以程序員不能指定它。
5.當裸調用采用1-4的調用約定時,如果需要,編譯器會在進入函數時生成代碼保存ESI、EDI、EBX和EBP寄存器,在退出函數時生成代碼恢復這些寄存器的內容。
裸調用不會生成這樣的代碼。裸調用不是類型修飾符,因此必須與_declspec ***壹起使用。
關鍵字__stdcall、__cdecl和__fastcall可以直接添加到要輸出的函數之前,也可以在設置中選擇...\ c/c++\編譯環境的代碼生成項。當在輸出函數前添加的關鍵字與編譯環境中的選擇不同時,直接在輸出函數前添加的關鍵字有效。它們對應的命令行參數分別是/Gz、/Gd和/Gr。默認狀態是/Gd,即__cdecl。
要完全模仿PASCAL調用約定,首先要使用__stdcall調用約定。至於函數名修飾約定,我們可以用其他方法模仿。另壹個值得壹提的宏是WINAPI,Windows支持。它可以將輸出函數轉換成適當的調用約定。在WIN32中,它被定義為__stdcall。您可以使用WINAPI宏創建自己的API。
2)名稱修改約定
1,裝修名稱
“C”或“C++”函數由修飾名在內部(編譯和鏈接)標識。修飾名是編譯器在編譯函數定義或原型時生成的字符串。在某些情況下,需要使用函數的修飾名,比如在模塊定義文件中指定輸出“C++”重載函數、構造函數和析構函數,在匯編代碼中調用“C”或“C++”函數。
修飾名由函數名、類名、調用約定、返回類型、參數等決定。
2.名稱修改約定因調用約定和編譯類型(C或C++)而異。函數名修飾約定因編譯類型和調用約定而異,下面將分別進行解釋。
a、c編譯時函數名修飾約定規則:
__stdcall調用約定在輸出函數名前添加壹個下劃線前綴,後跟壹個“@”符號及其參數的字節數,格式為_functionname@number。
__cdecl調用約定只在輸出函數名前添加壹個下劃線前綴,格式為_functionname。
__fastcall約定在輸出函數名前添加壹個“@”符號,後跟壹個“@”符號及其參數的字節數,格式為@functionname@number。
它們不改變輸出函數名中字符的大小寫,這與PASCAL調用約定不同,PASCAL調用約定輸出函數名不做任何修改,全部大寫。
b、C++編譯期函數名修飾約定規則:
__stdcall調用約定:
1, "?"標識函數名的開頭,後跟函數名;
2.函數名後面加“@@YG”標識參數表的開頭,後面加參數表;
3.參數列表由代碼表示:
x——無效,
D——char,
e——無符號字符,
f——短,
H——int,
I——無符號整數,
j——龍,
k——無符號長整型,
M——float,
n——雙精度,
_ N——布爾,
....
pa-表示指針,後面的代碼表示指針類型。如果同類型的指針連續出現,則替換為“0”,壹個“0”代表重復;
4.參數列表中第壹項是函數的返回值類型,後面是參數的數據類型,指針在它引用的數據類型之前標識;
5.在參數表後,用“@Z”標記整個名稱的結尾。如果函數沒有參數,用“z”標記。
它的格式是“?function name @ @ YG * * * * * @ Z yg * * * * @ Z " or "?functionname@@YG*XZ”,
例如
int Test1(char *var1,無符號長整型)-"?Test1@@YGHPADK@Z "
void Test2() -"?Test2@@YGXXZ "
__cdecl調用約定:
規則與above _stdcall約定相同,只是參數列表的起始標識符從上面的“@@YG”改為“@@YA”。
__fastcall呼叫約定:
規則與above _stdcall約定相同,只是參數列表的起始標識符由“@@YG”改為“@@YI”。
VC++對函數的默認聲明是“__cedcl”,只有C/C++才會調用。
淺談動態鏈接庫的功能
動態鏈接庫中定義了兩種函數:導出函數和內部函數。導出函數可以被其他模塊調用,內部函數在定義它們的DLL程序中使用。
輸出函數有幾種方式:
1,傳統方法
指定要在模塊定義文件的導出部分輸入的函數或變量。語法格式如下:
entry name[= internal name][@ ordinal[NONAME]][DATA][PRIVATE]
其中包括:
Entryname是輸出函數或引用數據的名稱;
Internalname與entryname相同;
@ordinal表示輸出表中的序號(索引);
NONAME只在按序號輸出時使用(不使用條目名);
DATA表示輸出是數據項,使用DLL輸出數據的程序必須將數據項聲明為_declspec(DLLimport)。
以上各項中,只有entryname項是必選的,其他可以省略。
對於“c”函數,entryname可以等同於函數名;但是對於“C++”函數(成員函數和非成員函數),entryname是修飾名。您可以從。映射圖像文件,或者使用DUMPBIN /SYMBOLS來獲取它們,然後將它們寫入。def文件。DUMPBIN是VC提供的工具。
如果希望輸出壹個“C++”類,則將要輸出的數據和成員的修飾名寫入。定義模塊定義文件。
2.命令行輸出
為鏈接器鏈接指定/EXPORT命令行參數,並輸出相關函數。
3.使用MFC提供的modifier _declspec(DLLexport)
在要輸出的函數、類和數據的聲明之前添加_declspec(DLLexport)修飾符以指示輸出。__declspec(DLLexport)在C調用約定和C編譯的情況下,可以去掉輸出函數名的下劃線前綴。extern“C”使得在C++中使用C編譯成為可能。需要添加關鍵字“C”來定義“C++”下的“C”函數。使用extern“C”指示函數使用C編譯方法。輸出“c”函數可以從“c”代碼中調用。
例如,在C++文件中,有以下函數:
extern " C " { void _ _ declspec(dll export)_ _ cdecl Test(int var);}
它的輸出函數叫做:Test。
MFC提供了壹些宏,有這樣的功能。
AFX _ CLASS _ IMPORT:_ _ declspec(dll export)
AFX _ API _ IMPORT:_ _ declspec(dll export)
AFX _ DATA _ IMPORT:_ _ declspec(dll export)
AFX _ CLASS _ EXPORT:_ _ declspec(dll EXPORT)
AFX _ API _ EXPORT:_ _ declspec(dll EXPORT)
AFX _ DATA _ EXPORT:_ _ declspec(dll EXPORT)
AFX_EXT_CLASS: #ifdef _AFXEXT
AFX _ CLASS _導出
#否則
AFX_CLASS_IMPORT
AFX_EXT_API:#ifdef _AFXEXT
AFX_API_EXPORT
#否則
AFX_API_IMPORT
AFX_EXT_DATA:#ifdef _AFXEXT
AFX _數據_導出
#否則
AFX _數據_導入
像AFX_EXT_CLASS這樣的宏,如果用在DLL應用程序的實現中,表示輸出(因為定義了_AFX_EXT,所以這個選項通常在編譯器的標識參數/d _ afx _ ext中指定);如果在使用DLL的應用程序中使用,則意味著輸入(_AFX_EXT未定義)。
若要輸出整個類,請對該類使用_ declspec(_ dllexpot);若要輸出類的成員函數,請對該函數使用_declspec(_DLLexport)。比如:
class AFX_EXT_CLASS CTextDoc:公共CDocument
{
…
}
extern " C " AFX _ EXT _ API void WINAPI InitMYDLL();
這幾種方法中,最好用第三種,方便好用;其次,第壹種,如果輸出是順序號,調用效率會更高;第二個是最後壹個。
模塊定義文件(。def)
模塊定義文件(。def)是由壹個或多個用於描述DLL屬性的模塊語句組成的文本文件,每個DEF文件必須至少包含以下模塊定義語句:
第壹條語句必須是庫語句,指明DLL的名稱;
EXPORTS語句列出導出函數的名稱;要輸出的函數的修飾名列在EXPORTS下,它必須與定義的函數名完全相同,以便獲得沒有任何修飾的函數名。
可以用DESCRIPTION語句描述DLL的用途(這句話可選);
";"註釋壹行(可選)。DLL程序和調用其輸出函數的程序之間的關系
1、DLL、進程和線程之間的關系
DLL模塊被映射到調用它的進程的虛擬地址空間。
DLL使用的內存是從調用進程的虛擬地址空間分配的,只能由進程的線程訪問。
調用進程可以使用DLL的句柄;DLL可以使用調用進程的句柄。
DLL使用調用進程的堆棧。
2.關於* * *享受數據段
調用進程可以訪問DLL定義的全局變量;DLL可以訪問調用進程的全局數據。每個使用相同DLL的進程都有自己的DLL全局變量實例。如果多個線程並發訪問同壹個變量,就需要使用同步機制;對於DLL的變量,如果希望使用DLL的每個線程都有自己的值,應該使用線程本地存儲(TLS)。
在程序中添加預編譯指令,或者在開發環境的項目設置中設置數據段的屬性,也可以達到設置數據段屬性的目的。這些變量必須被賦予初始值,否則編譯器會把沒有初始值的變量放在壹個叫做未初始化的數據段中。