最近在搞一些小項目,由于要涉及到跟其它語言進行互動,動态連結庫變成了不二的選擇。為此也查閱了很多資料,将動态連結庫的相關知識在此做一個整理。
一、動态連結庫概述
動态連結庫(Dynamic Link Library )是一種不可執行的二進制程式檔案,它允許多個程式共享執行特殊任務所必需的代碼和其他資源。Windows 中,DLL 多數情況下是帶有 ".dll" 擴充名的檔案,但也可能是 ".ocx"或其他擴充名;Linux系統中常常是 ".so" 的檔案。動态連結提供了一種方法,使程序可以調用不屬于其可執行代碼的函數。函數的可執行代碼位于一個 DLL 檔案中,該 DLL 包含一個或多個已被編譯、連結并與使用它們的程序分開存儲的函數。DLL 還有助于共享資料和資源。多個應用程式可同時通路記憶體中單個 DLL 副本的内容。使用動态連結庫可以更為容易地将更新應用于各個子產品,而不會影響該程式的其他部分。是開發大型項目必不可少的部分。
二、優缺點
優點:(1)節省記憶體和代碼重用:當多個程式使用同一個函數庫時,DLL可以減少在磁盤和實體記憶體中加載代碼的重複量,且有助于代碼的重用。
(2)子產品化:DLL有助于促進子產品式程式開發。子產品化允許僅僅更改幾個應用程式共享使用的一個DLL中的代碼和資料而不需要更改應用程式自身。适用于大規模的軟體開發,使開發過程獨立、耦合度小,便于不同開發者和開發組織之間進行開發和測試。這種子產品化的基本形式允許如Microsoft Office、Microsoft Visual Studio、甚至windows自身這樣大的應用程式使用較為緊湊的更新檔和服務包。
(3)擴充了應用程式的特性,使用dll檔案可以使得應用程式能很友善的進行功能的擴充,很多程式的插件機制就是通過dll檔案實作的。
(4)可以用多種語言來編譯和調用,由于各種語言都有自己獨特的開發優勢,在處理某類事務方面具有着獨特的優勢,是以在多種語言程式設計的過程中,可以利用dll檔案作為橋梁,可以發揮多種語言的優點。
缺點:DLL Hell:即DLL地獄,指幾個應用程式在使用同一個共享的DLL庫時發生版本沖突。
究其原因,八個字:成也共用,敗也共用。因為DLL Hell正是由于動态連結庫可與其他程式共用函數、資源所導緻。
主要有兩種情況:
設想這樣一個場景:程式A會使用1.0版本的動态連結庫X,則在程式A安裝到系統時,會同時安裝該1.0版本的動态連結庫X。假設另一個程式B也會使用到動态連結庫X,那麼程式B直接複制到硬碟中即可正常運作,因為動态連結庫已經存在于系統中。然而有一天,另一程式C也要使用動态連結庫X,但是由于程式C開發的時間較晚,其需要較新版本---2.0版本的動态連結庫X。則在程式C被安裝到系統時,2.0版本的動态連結庫X 也必須随之安裝到系統中,此時系統中1.0版本的動态連結庫将被2.0版本所取代(替換)。
情況1:新版本的動态連結庫不相容舊版本。如,A何B需要X所提供的功能,在更新到2.0後,新版本的X竟然把此功能取消了(很難想象吧,呵呵但有時候就是如此....)。則此時雖然C能正常運作,但A和B均無法工作了。
情況2:新版本的動态連結庫相容舊版本,但是存在一個bug。
三、入口點
就跟應用程式的main函數一樣,dll檔案也有入口函數,叫做DllMain(),它的原型是這樣的:
1 BOOL APIENTRY DllMain(
2 HANDLE hModule, // DLL子產品的句柄
3 DWORD ul_reason_for_call, // 調用本函數的原因
4 LPVOID lpReserved // 保留
5 ) {
6 switch (ul_reason_for_call)
7 {
8 case DLL_PROCESS_ATTACH:
9 //程序正在加載本DLL
10 break;
11 case DLL_THREAD_ATTACH:
12 //一個線程被建立
13 break;
14 case DLL_THREAD_DETACH:
15 //一個線程正常退出
16 break;
17 case DLL_PROCESS_DETACH:
18 //程序正在解除安裝本DLL
19 break;
20 }
21 return TRUE; //傳回TRUE,表示成功執行本函數
22 }
入口點函數隻應執行簡單的初始化任務,不應調用任何其他 DLL 加載函數或終止函數。例如,在入口點函數中,不應直接或間接調用 LoadLibrary 函數或LoadLibraryEx 函數。此外,不應在程序終止時調用 FreeLibrary函數。
四、生成DLL檔案
下面來生成一個DLL檔案,為友善起見,隻定義一個簡單函數。
生成DLL檔案需要用到兩個檔案,一個頭檔案,dll_add.h,和一個源檔案,dll_add.c
頭檔案内容:
1 #ifndef _DLL_DEMO_H_
2 #define _DLL_DEMO_H_
3 #ifdef DLLDEMO_EXPORTS
4 #define DLL_DEMO _declspec( dllexport )
5 #else
6 #define DLL_DEMO _declspec(dllimport)
7 #endif
8 extern "C" DLL_DEMO int Add(int a, int b);
9 #endif
源檔案内容:
1 #include "dll_demo.h"
2
3 int Add(int a, int b)
4 {
5 return (a + b);
6 }
這裡因為不需要對函數載入與解除安裝作特殊處理,是以可以不使用入口函數。
使用的是vs2015,在debug模式或者release模式下調試後會在相應目錄下生成dll檔案,即可使用。
五、調用DLL檔案
生成DLL自然是為了調用,調用DLL有兩種方式。
靜态調用:使用.h+.lib+.dll
1 #include <windows.h>
2 #include <iostream>
3 #include "DLL_DEMO.h"
4 using namespace std;
5 #pragma comment(lib, "DLL_DEMO.lib")
6
7 extern "C" _declspec(dllimport) int Add(int a, int b);
8 int main(int argc, char *argv[])
9 {
10 cout << Add(2, 3) << endl;
11 system("pause");
12 return 0;
13 }
把頭檔案和lib檔案、dll檔案都放到跟源檔案同一目錄下即可使用。當然,路徑可以重新設定。
動态調用:僅使用dll檔案
1 #include <windows.h>
2 #include <iostream>
3 using namespace std;
4 typedef int (*AddFunc)(int a, int b);
5 int main(int argc, char *argv[])
6 {
7 HMODULE hDll = LoadLibrary(L"DLL_DEMO.dll");
8 if (hDll != NULL)
9 {
10 AddFunc add = (AddFunc)GetProcAddress(hDll, "Add");
11 if (add != NULL)
12 {
13 cout<<add(2, 3)<<endl;
14 }
15 FreeLibrary(hDll);
16 }
17 }
在字元串前加一個L作用: unicode字元集是兩個位元組組成的。L告示編譯器使用兩個位元組的 unicode 字元集。
也可以使用dll來實作類和變量的共享,還可以實作記憶體共享,因為研究不多,是以這裡暫不介紹。
真正重要的東西,用眼睛是看不見的。