1. DLL的基本概念 應用程序(exe)要引用目標代碼(.obj)外部的函數(shù)時,有兩種實現(xiàn)途徑——靜態(tài)鏈接和動態(tài)鏈接。 1. 靜態(tài)鏈接 鏈接程序搜索對應的庫文件(.lib),然后將這個對象模塊拷貝到應用程序(.exe)中來。Windows之所不使用靜態(tài)鏈接庫,是因為很多基礎庫被很多應用程序使用。如果每個應用程序一份拷貝,將帶來內(nèi)存的極大浪費。 2. 動態(tài)鏈接 鏈接程序搜索到對應的庫文件(.lib),然后根據(jù)函數(shù)名得到對應的函數(shù)入口地址,即可進行編譯鏈接。直到真正運行的時候,應用程序才會從lib文件中記錄的DLL名字去搜索同名的DLL,然后將DLL的執(zhí)行代碼內(nèi)存映射到exe中來。動態(tài)鏈接庫的好處是多個應用程序可以共用一份DLL的代碼段內(nèi)存。但是數(shù)據(jù)段則是每個調(diào)用進程一份拷貝。 2. 靜態(tài)鏈接庫 靜態(tài)鏈接庫的使用比較簡單,一般使用如下方式創(chuàng)建。 然后就像普通工程一樣,添加頭文件的聲明以及源文件的實現(xiàn)。 編譯該工程就可以得到StaticLib.lib文件了。 調(diào)用者調(diào)用.lib庫也非常簡單,只需要包含頭文件聲明以及指明.lib庫路徑即可。如: #include "..\StaticLib\StaticLib.h" #pragma comment (lib, "..\\Lib\\staticlib.lib") 或者在Configuration Properties\Liker\Input\Additional Dependencies中指明.lib庫路徑。 3. 動態(tài)鏈接庫 Visual C++支持三種DLL,它們分別是Non-MFC DLL(非MFC動態(tài)庫)、MFC Regular DLL(MFC規(guī)則DLL)、MFC Extension DLL(MFC擴展DLL)。他們之間的區(qū)別簡單概括如下: 非MFC動態(tài)庫:即Win32DLL,不采用MFC庫函數(shù),其導出函數(shù)為標準的C接口,能被非MFC和MFC編寫的應用程序所調(diào)用。 MFC規(guī)則DLL:包含一個繼承自CWinApp的類,但其無消息循環(huán),可以使用MFC,但是接口不能為MFC。 MFC擴展DLL:采用MFC的動態(tài)鏈接版本創(chuàng)建,它只能被用MFC類庫所編寫的應用程序所調(diào)用。 1. 非MFC動態(tài)庫 創(chuàng)建Win32DLL DLL生成向?qū)峁┮恍┖唵蔚氖纠?,使得建立Win32DLL變得更簡單。
調(diào)用程序有兩種方式來調(diào)用DLL。 1. 隱式鏈接到DLL 需要完成3步,頭文件、.lib文件和DLL。具體實現(xiàn)如下:
或者在Configuration Properties\Liker\Input\Additional Dependencies中指明.lib庫路徑。 DLL的搜索路徑見文末. 2. 顯式鏈接到DLL 首先LoadLibary指定的DLL,然后GetProcAddress得到指定函數(shù)的入口指針,并且通過函數(shù)入口指針來訪問DLL的函數(shù),最后通過FreeLibrary制裁DLL。 1 typedef int (*PADDFUN)(void); 2 3 HINSTANCE hModule = LoadLibrary("Win32DLL.dll"); 4 5 PADDFUN pAddFun = (PADDFUN)GetProcAddress(hModule, "GetValue"); 6 7 pAddFun = (PADDFUN)GetProcAddress(hModule, MAKEINTRESOURCE(5)); 8 9 FreeLibrary(hModule);
如果要保證導出的函數(shù)名是不帶修飾的,一定要將指定函數(shù)為C編譯器編譯。否則函數(shù)名需要以被修飾過的以“?”開始的函數(shù)名來獲取函數(shù)的入口指針。上圖為Dependency Walker查看到的。 除了直接以函數(shù)名獲取入口地址外,還可以用索引獲取函數(shù)入口地址。GetProcAddress獲取的是入口地址,所以除了可以獲取函數(shù)的入口地址,同樣可以獲取變量的地址。 2. MFC規(guī)則DLL MFC規(guī)則的DLL有兩種,一種鏈接MFC動態(tài)庫的,一種是鏈接MFC靜態(tài)庫的。選擇MFC動態(tài)庫還是靜態(tài)庫與調(diào)用者有關系。因為調(diào)用者必須與DLL鏈接MFC庫一致,否則會導致庫調(diào)用的沖突。如果不是追求追求生成的exe和DLL占較小的空間,推薦使用MFC靜態(tài)庫。 MFC規(guī)則DLL的導出基本上同Win32DLL一樣,同樣不允許導出繼承自MFC庫的類。不同點主要體現(xiàn)在MFC規(guī)則DLL中可以使用MFC庫,其實WIN32DLL如果包含了MFC頭文件以及鏈接庫,也是可以使用MFC庫的。 1. 鏈接MFC動態(tài)庫 鏈接MFC動態(tài)庫資源的切換。這一點需要注意,并且VC默認生成的代碼中也用大篇幅的注釋提示了,并且也給出了如下基本的解釋。
2. 鏈接MFC靜態(tài)庫 鏈接MFC動態(tài)庫基本上和鏈接MFC靜態(tài)庫除了上面介紹的不同,導出和添加文件之類的完全一樣。所以下面重點講解鏈接MFC靜態(tài)庫。 DLL導出變量、函數(shù)以及類有兩種方法,前面使用的都是通過關鍵字來導出。另外還有一種方法,即通過模塊定義(.def)文件來導出。 我們來看一下模塊定義(.def)文件的基本格式: ; MFCDLL.def : Declares the module parameters for the DLL. LIBRARY "MFCDLL" EXPORTS ; Explicit exports can go here ShowDlg @2 nDllValue DATA 注釋是通過;來完成的。 關鍵字LIBRARY,描述DLL的名字,并將此信息寫入記錄DLL信息的.lib文件中。所以如果在Linker\Output File中修改了生成的DLL的名字,注意也一定要與LIBRARY中描述的一致。當然也可以直接注釋掉LIBRARY,這樣生成的DLL信息就直接與Linker\Output File中指定的名字相同。另外LIBARAY后面描述的名字,可以加引號,也可以不加引號。ShowDlg,這個是需要導出的函數(shù)名。@2,這里的2是描述方法的地址索引,可以修改,也可以不使用,系統(tǒng)會生成默認的。其實不僅僅函數(shù)有,變量也有。 如果同時使用了.def和__declspec(dllexport)導出,編譯器會優(yōu)先使用.def文件的導出。.def文件的導出默認是C編譯的,即和extern “c” __declspec(dllexport)的導出效果一樣。 導入函數(shù)的方法和前面使用的一樣。下面只說一下導入變量的方法。
pnDLLValue = (int*)GetProcAddress(hModule, MAKEINTRESOURCE(3)); 提供按序號導入的原因是這樣導入的速度更快,不用去按名字比對查找。 這里只介紹了.def文件導出導入的常用的一些方法,但是基本上夠用。如果想更深入的了解.def文件還涉及到很多知識點,可以參考: http://blog.csdn.net/henry000/article/details/6852521 http://msdn.microsoft.com/zh-cn/library/28d6s79h.aspx http://msdn.microsoft.com/zh-cn/library/54xsd65y.aspx http://msdn.microsoft.com/zh-cn/library/d91k01sh.aspx MFC 擴展 DLL 是通常實現(xiàn)從現(xiàn)有 Microsoft 基礎類庫類派生的可重用類的 DLL。 MFC 擴展 DLL 具有下列功能和要求:
擴展 DLL 是使用 MFC 動態(tài)鏈接庫版本(也稱作共享 MFC 版本)生成的。 只有用共享 MFC 版本生成的 MFC 可執(zhí)行文件(應用程序或規(guī)則 DLL)才能使用擴展 DLL。 客戶端應用程序和擴展 DLL 必須使用相同版本的 MFCx0.dll。 使用擴展 DLL,可以從 MFC 派生新的自定義類,然后將此“擴展”版本的 MFC 提供給調(diào)用 DLL 的應用程序。 擴展 DLL 也可用于在應用程序和 DLL 之間傳遞 MFC 派生的對象。 與已傳遞的對象關聯(lián)的成員函數(shù)存在于創(chuàng)建對象所在的模塊中。 由于在使用 MFC 的共享 DLL 版本時正確導出了這些函數(shù),因此可以在應用程序和它加載的擴展 DLL 之間隨意傳遞 MFC 或 MFC 派生的對象指針。 客戶端必須定義_AFXDLL 編譯,其實就是說客戶端必須使用MFC動態(tài)庫,即共享MFC庫。另外,擴展DLL中顯示對話框,和動態(tài)鏈接MFC的DLL一樣需要進行資源的切換,只是兩個DLL的DllMain函數(shù)不同,導致切換資源的方法不同。擴展DLL的切換方法。MFC擴展DLL的導入導出,基本上和靜態(tài)鏈接MFC的DLL一樣。 HINSTANCE oldHInst = AfxGetResourceHandle(); HINSTANCE hInst = LoadLibrary("ExDll.dll"); AfxSetResourceHandle(hInst); CMyDlg dlg; dlg.DoModal(); AfxSetResourceHandle(oldHInst); 另外,還可以使用構(gòu)造函數(shù)、析構(gòu)函數(shù)來自動完成資源的切換,詳見示例代碼。 MFC擴展DLL的使用比較復雜,尤其是涉及資源導出之類的,所以如果不是必需,盡量少用FMC擴展DLL,能夠用MFC常規(guī)DLL代表的盡量代替。 想詳細了解擴展DLL的請參考: http://msdn.microsoft.com/zh-cn/library/1btd5ea3.aspx http://msdn.microsoft.com/zh-cn/library/h5f7ck28(VS.80).aspx 4. 純資源DLL 一個純資源 DLL 是一個 DLL,它包含資源如圖標、 位圖、 字符串和對話框。 使用一個純資源 DLL 是共享一組相同的多個程序之間的資源的好辦法。 它也是一個好的方法,以提供資源被針對多種語言進行本地化的應用程序。 要創(chuàng)建純資源 DLL,請創(chuàng)建一個新的 Win32 DLL (非 MFC) 項目,并將資源添加到項目中。
使用純資源 DLL 的應用程序應調(diào)用 LoadLibrary 到顯式鏈接到 DLL。 若要訪問的資源,調(diào)用泛型函數(shù) FindResource 和 LoadResource,其中從事任何種類的資源,或調(diào)用下面的特定資源的函數(shù)之一:
應用程序應調(diào)用句完成時使用的資源。 可以調(diào)用與資源切換相同的方式完成資源的切換。
也可以調(diào)用指定資源函數(shù)獲取指定資源的句柄。 注意項 1. DLL搜索路徑
上面是EXE默認的搜索DLL路徑。但是有有時我們希望更改DLL存放的目錄,那么就需要在搜索路徑上做一些修改了。 如果去改上的模塊目錄、當前目錄,會導致程序中使用目錄上的不便,所以不建議修改上面這些目錄。Windows其實提供了修改DLL搜索路徑的API。 void SetDllDirectory( LPCTSTR lpPathName); 調(diào)用這個函數(shù)之后,DLL的搜索路徑改變?yōu)椋?/p>
HMODULE LoadLibraryEx( LPCTSRlpFileName,HANDLEhFile, DWORD dwFlags); 以參數(shù)dwFlags為 _WITH_ALTERED_SEARCH_PATH調(diào)用上面的函數(shù)時,DLL搜索路徑如下:
Windows Me/98/95: This directory does not exist.
通過上面兩個修改DLL搜索路徑的API,我們可以發(fā)現(xiàn),LoadLibraryEx會將一個新添的搜索路徑放在其他所有搜索路徑之前。而SetDllDirectory則將搜索路徑放在第2位。通過實驗也發(fā)現(xiàn),LoadLibraryEx的DLL搜索時間明顯少于SetDllDirectory設置之后的DLL搜索時間。所以建議使用LoadLibraryEx修改DLL搜索路徑。 2. DLL中的靜態(tài)變量 如果是在一個進程中,幾個模塊共同調(diào)用同一個DLL,那么DLL中的靜態(tài)變量是全局共用的。 3. 關于釋放DLL內(nèi)存的問題 Windows允許一個進程有多個Heap。我們知道每個DLL會有自己的數(shù)據(jù)區(qū),也就是說每個DLL都會有自己的Heap。Windows有一個規(guī)則,即誰的Heap誰負責,也就是說每個DLL必須得自己負責Head上的內(nèi)存申請以及釋放。 簡單的內(nèi)存申請釋放很容易發(fā)現(xiàn),但是有一些vector、CString、CStringArray等,它們的內(nèi)部實現(xiàn)其實都是有動態(tài)申請內(nèi)存的,所以如果導出函數(shù)的參數(shù)涉及到這些類型時,也會導致內(nèi)存釋放的問題。 4. Client與DLL在設置上必須一致
以上只列舉了一些容易出現(xiàn)的錯誤??傊绻贑lient端鏈接出錯時,就應該考慮Client與DLL的一致性問題了。 5. 動態(tài)鏈接到MFC共享DLL If this DLL is dynamically linked against the MFC DLLs,any functions exported from this DLL which call into MFC must have the AFX_MANAGE_STATE macro added at the very beginning of the function. 即只要是動態(tài)鏈接到MFC共享DLL的,必須使用資源切換。 |
|