轉載:
| 分類: c、vc、cpp |
在windows下一般可以看到后綴為dll和后綴為lib的文件,但這兩種文件可以分為三種庫,分別是動態鏈接庫(Dynamic-Link Libraries),目標庫(Object Libraries)和導入庫(Import Libraries),下面一一解釋這三種庫。
目標庫(Object Libraries)
目標庫又叫靜態鏈接庫,是擴展名為.LIB的文件,包括了用戶程序要用到 的各種函數。它在用戶程序進行鏈接時,“靜態鏈接”到可執行程序文件當中。例如,在VC++中最常使用到的C運行時目標庫文件就是LIBC.LIB。在鏈 接應用程序時常使用所謂“靜態鏈接”的方法,即將各個目標文件(.obj)、運行時函數庫(.lib)以及已編譯的資源文件(.res)鏈接到一起,形成 一個可執行文件(.exe)。使用靜態鏈接時,可執行文件需要使用的各種函數和資源都已包含到文件中。這樣做的缺點是對於多個程序都使用的相同函數和資源 要重復鏈接到exe文件中,使程序變大、占用內存增加。
導入庫(Import Libraries)
導入庫是一種特殊形式的目標庫文件形式。和目標庫文件一樣,導入庫文件的擴展名也是.LIB,也是在用戶程序被鏈接時,被“靜態鏈接”到可執行文件當中。但是不同的是,導入庫文件中並不包含有程序代碼。相應的,它包含了相關的鏈接信息,幫助應用程序在可執行文件中建立起正確的對應於動態鏈接庫的重定向表。比如KERNEL32.LIB、USER32.LIB和GDI32.LIB就是我們常用到的導入庫,通過它們,我們就可以調用Windows提供的函數了。 如果我們在程序中使用到了Rectangle這個函數,GDI32.LIB就可以告訴鏈接器,這個函數在GDI32.DLL動態鏈接庫文件中。這樣,當用 戶程序運行時,它就知道“動態鏈接”到GDI32.DLL模塊中以使用這個函數。其實說白了導入庫就是一個索引,一個dll動態鏈接庫的索引表,這是我的 理解。
動態鏈接庫(Dynamic-Link Libraries)
“動態鏈接”是將一些公用的函數或資源組織成動態鏈接庫文件(.dll),當某個需要使用dll中的函數或資源的程序啟動時(准確的說是初始化時),系統將該 dll映射到調用進程的虛擬地址空間、增加該dll的引用計數值,然后當實際使用到該dll時操作系統就將該dll載入內存;如果使用該dll的所有程序 都已結束,則系統將該庫從內存中移除。使用同一dll的各個進程在運行時共享dll的代碼,但是對於dll中的數據則各有一份拷貝(當然也有在dll中共享數據的方法)。 動態鏈接庫中可以定義兩種函數:輸出函數和內部函數。輸出函數可以被其他模塊調用,內部函數只能被動態鏈接庫本身調用。動態鏈接庫也可以輸出數據,但這些數據通常只被它自己的函數所使用。
如我們所知,Windows程序都是一些可執行文件,它們可以創建並顯示一個或多個窗體,使用消息循環來接收用戶的輸入。但是動態鏈接庫並不能直接被執 行,它們一般也不會接收消息。它們只是一些包含着函數的獨立文件,這些函數可以被Windows程序或者其它DLL調用以完成某項任務。
“動態鏈接”是指Windows程序在運行時才把自己需要存在於某個庫中的函數鏈接進來。“靜態鏈接”是指Windows程序在編譯階段就把各種對象模塊(.OBJ)、運行時庫(.LIB)和資源文件(.RES)鏈接到一起以創建一個可執行文件(.EXE)。
DERNAL32.DLL,USER32.DLL,GDI32.DLL,各種驅動程序如KEYBOARD.DRV,SYSTEM.DRV和MOUSE.DRV,顯卡和打印機驅動程序等都是動態鏈接庫。這些庫可以被所有的Windows程序共同使用。
有某些動態鏈接庫(如字體文件)稱為“resource-only”。它們只包括數據,而不包括代碼。因此,動態鏈接庫的目的之一就是為許多不同的程序提供 函數和資源。在傳統的操作系統里,用戶程序在運行時只能調用操作系統自身的某些函數。而在Windows操作系統下,模塊或程序調用另一個模塊中的函數來 執行是一種非常普遍的操作。因此,從某種角度看,對DLL進行編程,其實是在對Windows操作系統作擴展,也可以看作是在對用戶程序作擴展。
動態鏈接庫模塊可以有其它的擴展名,但是標准的擴展名是.DLL。只有具有標准擴展句的動態鏈接庫模塊才可以被Windows自動加載。而如果是其它擴展名的動態鏈接庫模塊,程序必須使用LoadLibrary或者LoadLibraryEx函數來顯示加載。
我們可以發現,在大型的應用軟件中,會常常使用到動態鏈接庫技術。舉個例子,假如我們要寫一個大型的應用軟件,其中包括了多個程序。我們可以發現很多程 可能都會使用到一些同樣的通用的函數。我們可以把這些通用的函數放到某個目標庫文件中(.LIB),然后在鏈接是把它加到每個程序中進行靜態鏈接。但是 這是一種非常浪費的方法,因為每個程序模塊中都會包括這些通用函數的獨立拷貝。另外,如果我們要改變庫文件中的某個函數,就必須把所有使用到這個函數的程 序都重新編譯一遍。但是,如果我們使用動態鏈接庫的技術,把所有這些通用函數都放到一個動態鏈接庫文件當中,我們就可以解決以上提到的各種問題。首先,動 態鏈接庫在硬盤上只保留一個拷貝,程序只是在運行時才會調用其中使用到的函數,這樣我們就可以節省大量的程序存儲和運行空間。其次,如果要修改某個通用函 數時,只要調用接口沒有改變,只是改變它的實現方法,那么我們就不必對每個用到它的程序都進行重新編譯,而只要把動態鏈接庫模塊重新編譯一遍就可以了。
動態鏈接庫模塊也可以作為一個單獨的產品來發布。這樣程序開發人員就可以使用第三方的模塊來開發自己的應用程序,提高了程序的復用程序,也節省了大量的時間和精力。
目標庫和導入庫都是在程序開發過程中才使用到的,而動態鏈接庫是在程序運行時才使用的。在程序運行時,相應的動態鏈接庫文件必須已經保存在硬盤上了。另 外,如果要使用動態鏈接庫文件,該文件必須要保存在同.EXE文件同一個目錄下,或者保存在當前目錄、Windows系統目錄、Windows目錄或環境 變量中PATH參數指定的目錄下。程序也是按照這個順序來搜尋它需要的動態鏈接庫文件的。
創建靜態鏈接庫(Lib)
創建靜態鏈接庫比較簡單,創建win32控制台程序,選擇靜態庫,這里我沒有選擇上預編譯頭。生成工程以后就像定義一般的函數般,定義放在頭文件,然后實現放在cpp文件里頭,直接build就出來一個靜態的lib了,發布時附上頭文件給使用者就可以。

創建動態鏈接庫(DLL)
用SDK創建一個簡單的dll文件
在 VC++中選擇新建一個Win32 Dynamic-Link Library。需要建立一個c/c++ head file和一個c/c++ source file並加入工程。頭文件中內容為輸出函數的聲明,源文件中內容為DllMain函數和輸出函數的定義。下面是一個最簡單的例子。

頭文件代碼如下:
代碼
#define TEST_CREATE_DLL_API __declspec(dllexport)
#else
#define TEST_CREATE_DLL_API __declspec(dllimport)
#endif
// This class is exported from the test_create_dll.dll
class TEST_CREATE_DLL_API Ctest_create_dll {
public:
Ctest_create_dll(void);
// TODO: add your methods here.
};
extern TEST_CREATE_DLL_API int ntest_create_dll;
TEST_CREATE_DLL_API int fntest_create_dll(void);
在創建工程的時候TEST_CREATE_DLL_EXPORTS就已經在預定義處定義過,生成導出dll。
頭 文件預處理中的__declspec是微軟增加的“C擴展類存儲屬性”(C Extended Storage-Class Attributes),它指明一個給出的實例被存儲為一種微軟特定的類存儲屬性,可以為thread,naked,dllimport或 dllexport. [MSDN原文:The extended attribute syntax for specifying storage-class information uses the __declspec keyword, which specifies that an instance of a given type is to be stored with a Microsoft-specific storage-class attribute (thread, naked, dllimport, or dllexport).] 輸出函數必須指明為CALLBACK。 DllMain是dll的入口點函數。也可以不寫它。DllMain必須返回TRUE,否則系統將終止程序並彈出一個“啟動程序時出錯”對話框。 編譯鏈接后,得到動態鏈接庫文件dlldemo.dll和輸入庫文件dlldemo.lib。
_declspec(dllexport)
聲明一個導出函數,是說這個函數要從本DLL導出。我要給別人用。一般用於dll中 。
省掉在DEF文件中手工定義導出哪些函數的一個方法。當然,如果你的DLL里全是C++的類的話,你無法在DEF里指定導出的函數,只能用__declspec(dllexport)導出類。
__declspec(dllimport)
聲明一個導入函數,是說這個函數是從別的DLL導入。我要用。一般用於使用某個dll的exe中 。
不使用 __declspec(dllimport) 也能正確編譯代碼,但使用 __declspec(dllimport) 使編譯器可以生成更好的代碼。編譯器之所以能夠生成更好的代碼,是因為它可以確定函數是否存在於 DLL 中,這使得編譯器可以生成跳過間接尋址級別的代碼,而這些代碼通常會出現在跨 DLL 邊界的函數調用中。但是,必須使用 __declspec(dllimport) 才能導入 DLL 中使用的變量。
相信寫WIN32程序的人,做過DLL,都會很清楚__declspec(dllexport)的作用,它就是為了省掉在DEF文件中手工定義導出 哪些函數的一個方法。當然,如果你的DLL里全是C++的類的話,你無法在DEF里指定導出的函數,只能用__declspec(dllexport)導 出類。但是,MSDN文檔里面,對於__declspec(dllimport)的說明讓人感覺有點奇怪,先來看看MSDN里面是怎么說的:
不使用 __declspec(dllimport) 也能正確編譯代碼,但使用 __declspec(dllimport) 使編譯器可以生成更好的代碼。編譯器之所以能夠生成更好的代碼,是因為它可以確定函數是否存在於 DLL 中,這使得編譯器可以生成跳過間接尋址級別的代碼,而這些代碼通常會出現在跨 DLL 邊界的函數調用中。但是,必須使用 __declspec(dllimport) 才能導入 DLL 中使用的變量。
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"不是一回事。
C++編譯器在生成DLL時,會對導出的函數進行名字改編,並且不同的編譯器使用的改變規則不一樣,因此改編后的名字會不一樣。這樣,如果利用不同 的編譯器分別生成DLL和訪問該DLL的客戶端代碼程序的話,后者在訪問該DLL的導出函數時會出現問題。為了實現通用性,需要加上限定符:extern “C”。
但是利用限定符extern “C”可以解決C++和C之間相互調用時函數命名的問題,但是這種方法有一個缺陷,就是不能用於導出一個類的成員函數,只能用於導出全局函數。 LoadLibrary導入的函數名,對於非改編的函數,可以寫函數名;對於改編的函數,就必須吧@和號碼都寫上,一樣可以加載成功,可以試試看。
解決警告 inconsistent dll linkage
inconsistent dll linkage警告是寫dll時常遇到的一個問題,解決此警告的方法如下:
一般PREDLL_API工程依賴於是否定義了MYDLL_EXPORTS來決定宏展開為__declspec(dllexport)還是 __declspec(dllimport)。展開為__declspec(dllexport)是DLL編譯時的需要,通知編譯器該函數是需要導出供外 部調用的。展開為__declspec(dllimport)是給調用者用的,通知編譯器,該函數是個外部導入函數。
對於工程設置里面的預定義宏,是最早被編譯器看到的。所以當編譯器編譯DLL工程中的MYDLL.cpp時,因為看到前面有工程設置有定義MYDLL_EXPORTS,所以就把PREDLL_API展開為__declspec(dllexport)了。
這樣做的目的是為了讓DLL和調用者共用同一個h文件,在DLL項目中,定義MYDLL_EXPORTS,PREDLL_API就是導出;在調用該DLL的項目中,不定義MYDLL_EXPORTS,PREDLL_API就是導入。
使用靜態鏈接庫(Lib)
使用靜態鏈接庫需要庫的開發者提供庫的頭文件以及lib文件,一般來說lib文件都比較大(相對導入庫來說),靜態鏈接庫是將全部指令都包含入調用程序生成的EXE文件中,並不存在“導出某個函數提供給用戶使用”的情況,就是要么全要,要么都不要。
使用動態鏈接庫(DLL)
方法一: load-time dynamic linking (隱式調用)(又名:靜態綁定)
在要調用dll的應用程序鏈接時,將dll的輸入庫文件(import library,.lib文件)包含進去。具體的做法是在源文件開頭加一句#include ,然后就可以在源文件中調用dlldemo.dll中的輸出文件了,
#pragma comment(lib, "***.lib") //通知編譯器DLL的.lib文件所在路徑及文件名,也可以不采用該語句,在屬性欄——輸入——附加依賴項處添加對應的lib就可以編譯鏈接應用程序了。
extern "C" __declspec(dllimport) foo(); //聲明導入函數
方法二: run-time dynamic linking (顯式調用)(又名:動態綁定)
不必在鏈接時包含輸入庫文件,而是在源程序中使用LoadLibrary或LoadLibraryEx動態的載入dll。
主要步驟為(以demodll.dll為例):
1) typedef函數原型和定義函數指針。
typedef void (CALLBACK* DllFooType)(void) ;
DllFooType pfnDllFoo = NULL ;
2) 使用LoadLibrary載入dll,並保存dll實例句柄
HINSTANCE dllHandle = NULL ;
...
dllHandle = LoadLibrary(TEXT("dlldemo.dll"));
3) 使用GetProcAddress得到dll中函數的指針
pfnDllFoo = (DllFooType)GetProcAddress(dllHandle,TEXT("DllFoo")) ;
注意從GetProcAddress返回的指針必須轉型為特定類型的函數指針。
4)檢驗函數指針,如果不為空則可調用該函數
if(pfnDllFoo!=NULL)
DllFoo() ;
5)使用FreeLibrary卸載dll
FreeLibrary(dllHandle) ;
動態鏈接庫(DLL)的優點
→節約內存;
→使應用程序“變瘦”;
→可單獨修改動態鏈接庫而不必與應用程序重新鏈接;
→可方便實現多語言聯合編程(比如用VC++寫個dll,然后在VB中調用);
→可將資源打包;
→可在應用程序間共享內存
→......
