原帖位址:http://www.cppblog.com/amyvmiwei/archive/2008/01/01/40157.html
dll是現在常見的檔案,它內建了程式的很多功能在裡面。一般情況下,它不能直接被執行,常見的使用方法是用其他的*.exe調用其執行,以使其内部功能表現出來。還有*.ocx檔案也與之類似,也就是人們常說的com
1. 簡要
Windows API中所有的函數都包含在dll中,其中有3個最重要的DLL。
(1) Kernel32.dll
它包含那些用于管理記憶體、程序和線程的函數,例如CreateThread函數;
(2) User32.dll
它包含那些用于執行使用者界面任務(如視窗的建立和消息的傳送)的函數,例如CreateWindow函數;
(3) GDI32.dll
它包含那些用于畫圖和顯示文本的函數。
2. 靜态庫和動态庫
(1) 靜态庫
函數和資料被編譯進一個二進制檔案(通常擴充名為.LIB)。在使用靜态庫的情況下,在編譯連結可執行檔案時,連結器從庫中複制這些函數和資料并把它們和應用程式的其他子產品組合起來建立最終的可執行檔案(.Exe檔案).當釋出産品時,隻需要釋出這個可執行檔案,并不需要釋出被使用的靜态庫。
(2) 動态庫
在使用動态庫的時候,往往提供兩個檔案:一個引入庫(.lib)檔案和一個DLL(.dll)檔案。雖然引入庫的字尾名也是”lib”,但是動态庫的引入庫檔案和靜态庫檔案有着本質上的差別,對一個DLL來說,其引入庫檔案(.lib)包含該DLL導出的函數和變量的符号名,而.dll檔案包含該DLL實際的函數和資料。在使用動态庫的情況下,在編譯連結可執行檔案時,隻需要連結該DLL的引入庫檔案,該DLL中的函數代碼和資料并不複制到可執行檔案中,直到可執行程式運作時,才去加載所需的DLL,将該DLL映射到程序的位址空間外,然後通路DLL中導出的函數。這時,釋出産品時,除了釋出可執行檔案以外,同時還要釋出該程式将要調用的動态連結庫。
3. 在導出庫頭檔案中的标準寫法:
#ifdef LIBDAQ_EXPORTS
#define LIBDAQ_API __declspec(dllexport)
#else
#define LIBDAQ_API __declspec(dllimport)
#endif
将該頭檔案添加到某客戶代碼中時,會自動展開。如果客戶代碼沒有定義LIBDAQ_EXPORTS,那麼LIBDAQ_EXPORTS會被定義為__declspec(dllimport)表示有LIBDAQ_EXPORTS頭的函數都是從該DLL中導入的。
4. 名字改編和”extern “C””
C++編譯器在生成DLL時,會對導出的函數進行名字改編,并且不同的編譯器使用的改變規則不一樣,是以改編後的名字會不一樣。這樣,如果利用不同的編譯器分别生成DLL和通路該DLL的用戶端代碼程式的話,後者在通路該DLL的導出函數時會出現問題。為了實作通用性,需要加上限定符:extern “C”。
但是利用限定符extern “C”可以解決C++和C之間互相調用時函數命名的問題,但是這種方法有一個缺陷,就是不能用于導出一個類的成員函數,隻能用于導出全局函數。
5. 顯示加載方式加載DLL
使用動态方式來加載動态連結庫時,需要用到LoadLibrary函數。該函數的作用就是将指定的可執行子產品映射到調用程序的位址空間。調用原型為:
HMODULE LoadLibrary(LPCTSTR lpFileName);
LoadLibrary函數不僅可以加載DLL,還可以加載可執行子產品(Exe)。當加載可執行子產品時,主要是為了通路該子產品内的一些資源,例如對話框資源、位圖資源或圖示資源等。LoadLibrary函數有一個字元串類型(LPCTSTR)的參數,該參數指定了可執行子產品的名稱,既可以是一個dll檔案,也可以是一個exe檔案。如果調用成功,LoadLibrary函數将傳回所加載的那個子產品的句柄。傳回類型HMODULE和HINSTANCE可以通用。
當加載到動态連結庫子產品的句柄後,接下來就要想辦法擷取該動态連結庫中導出函數的位址,這可以通過調用GetProcAddress函數來實作。該函數用來擷取DLL導出函數的位址,其原型聲明如下所示:
FARPROC GetProcAddress(HMODULE hModule, LPCSTR lpProcName);
參數hModule:指定動态連結庫子產品的句柄,即LoadLibrary函數的傳回值。
參數lpProcName:一個指向常量的字元指針,指定DLL導出函數的名字或函數的序号。如果是序号,則序号必須在低位位元組中,高位位元組必須是0。
如果調用成功,GetProcAddress函數将傳回指定導出函數的位址;否則傳回NULL。
例如:
HINSTANCE hInst;
hInst = LoadLibrary(“DllTest.dll”);
typedef int (*ADDPROC)(int a, int b);
ADDPROC add = (ADDPROC)GetProcAddress(hInst, “add”);
if (!add)
print(“Failure”);
else
process next events
FreeLibrary(hInst);
調用文法:
BOOL FreeLibrary(HMODULE hModule);
6. 加載DLL的兩種方式優缺點:
采用動态加載方式,那麼可以在需要時才加載DLL,而隐式連結方式實作起來比較簡單,在編寫用戶端代碼時就可以把連結工作做好,在程式中可以随時調用DLL導出的函數。但是如果程式需要通路十多個DLL時,如果都采用隐式連結方式加載它們的話,那麼在該程式啟動時,這些DLL都需要被加載到記憶體中,并映射到調用程序的位址空間,這樣将加大程式的啟動時間。而且一般來說,在程式運作過程中隻是在某個條件滿足時才需要通路某個DLL中的某個函數,其它情況下都不需要通路這些DLL中的函數。但是這時所有的DLL都已經被加載到記憶體中,資源浪費是比較嚴重的。這個時候就需要采用顯示加載的方式來通路DLL,在需要時才加載所需的DLL。也就是說在需要時才被加載到記憶體中,并被映射到調用程序的位址控件中。需要說明的是,隐式連結方式通路DLL時,在程式啟動時也是通過LoadLibrary函數加載該程序需要的動态連結庫的。
7. DllMain函數
如果提供了DllMain函數(該函數是可以選擇存在的),那麼在此函數中不要進行太複雜的調用。因為在加載該動态連結庫時,可能還有一些核心動态連結庫沒有被加載。例如Use32.dll或GDI32.dll。我們自己編寫的DLL會比較靠前地被加載。
8. .def的應用
在不同語言間調用約定的不同,甚至有的編譯器對名字的修飾也不是完全相同,是以,要使DLL能廣泛應用加上一個.def檔案。