動態連接庫(Dynamic Link Library,DLL)是一些編譯過的可執行程序模塊,它包含代c++
碼、數據或資源,能夠在應用程序中或其餘DLL中被調用。動態連接庫的文件擴展名通常爲.dll,也能夠是.drv(設備驅動程序)、.sys(系統文件)和.fon(字體文件)。DLL的應用很是普遍,能夠實現多個應用程序問的代碼和資源共享,是Windows Embedded Compact 7程序設計中的一個很是重要的組成部分。ide
12.1 dll概述模塊化
當使用普通的函數庫時,能夠在程序連接時將庫中的代碼拷貝到可執行文件中,這是一種函數
靜態連接。而在多個一樣的程序執行時,系統將保留許多重複的代碼副本,很容易形成內存資源的浪費。若是使用DLL動態連接庫,那麼在創建應用程序的可執行文件時,就沒必要將DLL連接到程序中,只須要在應用程序運行時動態地裝載DLL。裝載時DLL將被映射到進程的地址空間中,所以使用DLL動態連接並不就是拷貝庫代碼,只是在程序中記錄了函數的入口點和接口,在程序執行時纔將庫代碼裝入內存。因此無論多少程序使用了DLL,內存中都將只有該DLL的一個副本,當沒有程序使用它時,系統就將它移出內存,減小了對內存和磁盤的要求。因而可知,使用DLL的一個明顯的好處就是節省系統資源。測試
除了以上的優勢外,使用DLL設計程序還有如下一些優勢:字體
· 共享代碼、資源和數據。DLL做爲一種基於WindoWS的程序模塊,不只能夠包含可spa
執行代碼,還能夠包括數據和各類資源等,擴大了庫文件的使用範圍。操作系統
DLL提供了共享資源的途徑,例如位圖、字體或者圖表等均可以放到一個資源文件中,線程
並直接鏈接到應用程序中。若是將這些資源都放到DLL中,那麼許多應用程序均可以直接使設計
用,而沒必要在內存裏重複裝入這些數據。
在16位的Windows中,DLL有本身的數據段,所以全部須要調用同一個DLL的應用程
序都可以訪問同一個全局變量和靜態變量。可是在32位的系統中,狀況就不一樣了。由於DLL
的映像被映射到每一個進程的地址空間,該DLL的全部數據將屬於映射到的進程。值得注意的
是,儘管進程間不能共享DLL的數據,可是同一個進程的全部線程則能夠共享。因爲線程間
相互獨立,所以在訪問某一DLL全局變量時,要注意保持同步,以避免衝突。
盡管DLL的映像被映射到每一個進程的地址空間時,該DLL的全部數據將屬於映射到的進
程,這也並不意味着沒有辦法在進程間共享DLL的數據。利用內存映射文件就能夠實現,只要將數據存儲到內存映射的共享區,那麼一切須要調用DLL的應用程序都能夠讀取這些存儲在內存中的共享區域的數據。
· 可將系統模塊化,方便升級。DLL技術對於開發大型軟件系統也大有好處。若是使
用一個執行文件完成一個大型系統,那麼程序將會很龐大,並且還可能存在許多重複
的功能。而若是將程序分紅一系列的主程序和DLL,則能夠減小開發的工做量並加
快開發的速度。並且,若是開發過程當中就將一些功能模塊作成DLL,那麼在須要對
系統進行升級的時候,只須要升級個別DLL,而後用新的DLL文件覆蓋掉舊的DLL
文件就.-1以Y,如此一來,就不須要對整個系統進行從新編譯和連接,極大地方便了
系統的升級和維護。
· 隱藏實現的細節。在某些狀況下,用戶可能想隱藏例程實現的細節,DLL就是一個
很是不錯的實現方法。DLL的例程能夠被應用程序訪問,而不顯示其中的代碼細節。
還有很重要的一點就是DLL與語言無關。
12.2 dll的調用
12.2.1 靜態調用
靜態調用指由編譯系統完成對DLL的加載而且在應用程序結束時對DLL進行釋放。靜態
調用相對於動態調用而言,其優勢是簡單實用,弊端就是不夠靈活。
在Visual C++中靜態調用DLL也很是簡單,首先將動態鏈接庫的.LIB文件加入到應用程
序的工程中,而後在使用DLL函數的文件裏引用DLL的頭文件便可。
靜態調用不須要調用LoadLibrary和FreeLibrary,這是由於開發人員在創建一個DLL文
件時,連接程序會自動生成一個與之對應的LIB導入文件。該文件包含了每個DLL導出函
數的符號名和可選的標識號,可是並不包含實際的代碼,LIB文件會做爲DLL的替代文件被
編譯到應用程序項目中。當開發人員經過靜態連接方式編譯並生成應用程序時,應用程序中的
調用函數與LIB文件中的導出符號相匹配,這些符號或標識號進入到生成的EXE文件中。LIB
文件中也包含了對應的DLL文件名(但不是徹底的路徑名),連接程序將其存儲在EXE文件
內部。當應用程序運行過程當中須要加載DLL文件時,Windows將根據這些信息查尋並加載
DLL,而後經過符號名或標識號實現對DLL函數的動態連接。當加載應用程序的EXE文件時,
全部被應用程序調用的DLL文件都將被加載在到內存中。可執行程序直接經過函數名調用
DLL的輸出函數,其調用方法與調用程序內部的其餘的函數相同。
12.2.2 動態調用
動態調用是由開發人員使用APl函數手工加載和卸載DLL,以達到調用DLL的目的。動
態調用較之靜態調用,在使用上更爲複雜,但卻能更加有效地使用內存,所以是編寫大型應用
程序的重要方式。
動態調用是指在應用程序中使用LoadLibrary函數或MFC提供的AfxLoadLibrary函數顯
式地調入所需的動態鏈接庫,動態鏈接庫的文件名即上面兩個函數的參數,而後再使用
GetProeAddress獲取所需引入的函數。完成以上操做後,就能夠像使用本應用程序自定義的函數同樣來調用引入函數了。在應用程序退出以前,應該使用FreeLibrary函數或MFC提供的
AfxFreeLibrary函數來釋放動態鏈接庫。
動態調用DLL的第l步就是調用LoadLibrary函數來加載DLL。該函數的定義以下:
HINSTANCE LoadLibrary( LPCTSTR ipLibFi leName);
參數lpLibFileName用於指定DLL的文件名,而且該文件名能夠包含文件名的目錄。如
果不包含文件名的目錄,那麼該函數將遵循下面的搜索順序來定位DLL。
· 包含EXE文件的目錄
· 進程的當前工做目錄
· Windows系統目錄
· Windows目錄
· 列在Path環境變量中的一系列目錄
成功加載DLL後,函數將返回指向該DLL的句柄,不然將返回NULL。
成功執行第1步(加載DLL)以後,就能夠執行第2個步驟了。執行該步驟的目的就是
獲取DLL裏的輸出函數接口,能夠經過GetProcAddress函數來實現該目標。GetProcAddress
函數的定義以下:
FARPROC GetProcAddress(
HMODULE hModule,
LPCWSTR lpprocName);
· 參數hModule用於指定DLL旬柄,即LoadLibrary函數的返回值。
· 參數lpProcName指定想要獲得的函數名稱。
若是函數執行成功,那麼將返回指定函數的地址指針,不然返回NULL。若是DLL裏有
N個須要獲取的輸出函數,就須要執行GetProcAddress函數N次,來獲取這N個函數的地址。
獲取DLL裏的輸出函數以後,直接調用輸出函數便可。
動態調用DLL的最後一個步驟就是當再也不使用DLL裏的輸出函數時,調用FreeLibrary
函數釋放DLL,該函數的定義以下:
BOOL FreeLibrary(
HMODULE hLibModule)j
參數hLibModule用於指定DLL句柄,即LoadLibrary函數的返回值。
若是該函數成功的釋放了DLL,將返回TRUE,不然返回FALSE。
12.3 dll的建立
本節將分別介紹以下3種類型的DLL動態連接庫建立方法和建立過程。
● Windows Embedded Compact 7 DLL。
Windows Embedded Compact 7是指不使用MFC建立的DLL。Windows Embedded Compact 7導出函數一般使用標
準C接口,這些函數能夠被MFC或非MFC應用程序調用。
· MFC常規DLL(動態鏈接MFC)。 .
MFC常規DLL是使用MFC建立的,其導出函數也一般使用標準C接口,它們能夠被
MFC或非MFC應用程序調用。按照與MFC連接方式的不一樣,MFC常規DLL又能夠分爲動
態鏈接和靜態鏈接兩種,前者使用MFC的動態連接庫(即共享版本),後者使用MFC的靜態
連接版本。
· 純資源DLL。
純資源DLL只包含共享的資源,如菜單、字符串、圖標、位圖以及聲音等。
下面就分別介紹以上3類DLL動態連接庫的建立方法。
12.3.1 Windows Embedded Compact 7中 dll的建立
新建一個基於「Win32智能設備項目’’的項目,將項目名稱設爲MyCEDLL,實現頁面
如圖12-1所示。
圖12-1建立動態函數連接庫
選擇yincheng_OS,如圖12-2
圖12-2選擇程序開發環境yincheng_OS
選擇DLL,如圖12-3
圖12-3選擇程序類
單擊「finish」按鈕就完成了MyCEDLL工程的建立。
下面先來了解一下非MFC的DLL工程實現原理。
首先,每一個DLL必須有一個入口點,這就如同使用C語言編寫的應用程序必須有一個
WINMAlN函數同樣。DllMain是一個缺省的入口函數,它負責初始化(Initialization)和結束
(Termination)工做。當一個新的進程或者該進程的新的線程訪問DLL,以及訪問DLL的每一
個進程或者線程再也不使用此DLL時,都會調用DllMain函數。可是有一種特殊狀況,那就是如
果使用TerminateProcess或TerminateThread方法結束進程或線程,就不會調用DllMain函數。
用戶只須要打開MyCEDLL.cpp文件,就能夠看到DllMain函數的實現,它的函數原型
以下:
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
· 參數Moudle是動態庫被調用時所傳遞來的一個指向本身的句柄
· 參數ul reason for call是一個說明動態庫被調用緣由的標誌。當進程或者線程裝載、
卸載動態連接庫的時候,操做系統便調用入口函數,並說明動態連接庫被調用的緣由。
該參數能夠取以下所示的值:
>DLL PROCESS ATTACHt進程被建立。
>DLL THREAD ATTACH:線程被建立。
>LL PROCESS DETACH:進程被中止。
>LL THREAD DETACH:線程被中止。
· 參數lpReserved是一個被系統所保留的參數。
在理解了DllMain函數的實現原理後,就要接着考慮輸出函數的實現方法了。
輸出函數須要在函數名稱前加上修飾符declspec(dllexport),表示輸出。此外,還有一種
修飾符extem」C」declspec(dllexport),它也表示輸出,並且該類DLL不只能夠被c++調用,
還能夠被c調用。在c++下定義c函數時,須要加上extem」C」關鍵詞,用extern」C」來指明
該函數使用C的編譯方式,輸出的c函數能夠從c代碼裏調用,extern」C」使得在C++中使用C編譯方式成爲可能。
本示例嚮導完成後,會自動導出3種類型示例符號:一個是導出了一個「C++類」、一個
是導出了一個「全局變量」、另一個導出了一個「函數」。讀者能夠效仿示例進行添加自定義
的導出符號。這3個示例符號定義以下:
//此類是從MyCEDLL.dll導出的
class MYCEDLL_API CMyCEDLL{
public:
CMyCEDLL(void);
//TODO:在此添加您的方法
);
extern MYCEDLL_API int nMyCEDLL;
函數實現代碼中的修飾符MYCEDLL_APl其實就是_declspec(dllexport),由於在
MyCEDLL.h文件中含有以下宏代碼:
ifdef MYCEDLL—EXPORTS
#define MYCEDLL—API declspec(dllexport)
#else
#define MYCEDLL—API declspec(dllimport)
#endif
MYCEDLL_API int fnMyCEDLL(void);
下面就來爲MyCEDLL.dll動態連接庫增長一個輸出函數TestDll。
首先在MyCEDLL.h頭文件中添加TestDll函數的聲明,代碼以下:
extern」C」MYCEDLL_API void TestDll(void);
而後在MyCEDLL.cpp文件中添加以下所示的TestDll函數的實現代碼:
extern "C" MYCEDLL_API void TestDll(void)
{
MessageBox(NULL,_T("此信息來自DLL"),_T("測試所編DLL"),MB_OK);
}
完成以上操做後,一個簡單的DLL就編寫完了。按下F5編譯就會生成MyCEDLL.dll文件,將此文件下載到yincheng.OS\RelDir\VirtualPC_x86_Release目錄下。在下面的一個小節中,將以MyCEDll.dll爲例,介紹靜態調用和動態調用該DLL的方法步驟。