靜態鏈接庫(Lib)和動態鏈接庫(DLL)的問題困擾了我很長時間,而當中關鍵的問題是兩者有何聯系?又有何區別呢?怎么創建?怎么使用?使用的過程中要注意什么?一直想把這個問題總結一下。
在windows下一般可以看到后綴為dll和后綴為lib的文件,但這兩種文件可以分為三種庫,分別是動態鏈接庫(Dynamic-Link Libraries),目標庫(Object Libraries)和導入庫(Import Libraries),下面一一解釋這三種庫。
用SDK創建一個簡單的dll文件
在VC++中選擇新建一個Win32 Dynamic-Link Library。需要建立一個c/c++ head file和一個c/c++ source file并加入工程。頭文件中內容為輸出函數的聲明,源文件中內容為DllMain函數和輸出函數的定義。下面是一個最簡單的例子。

頭文件代碼如下:
代碼
#ifdef TEST_CREATE_DLL_EXPORTS
#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中調用);
→可將資源打包;
→可在應用程序間共享內存
→......
杭州京都醫院http://www.fjzzled.com/京都醫院