DLL (Dynamic Link Library)

一、引言
動態鏈接庫在Windows中是非常重要的,幾乎所有的內容都是以DLL的形式存在的。例如顯示的字體和圖標存儲在GDI.DLL中;顯示Windows桌面,處理用戶輸入所需要的代碼被存儲在User.DLL中;Windows編程所需要的大量的API函數存儲在Kernel.DLL中。
比較大的應用程序都由很多模塊組成,這些模塊分別完成相對獨立的功能,它們彼此協作來完成整個軟件系統的工作。有些模塊的功能較爲通用,在構造其它軟件系統時仍會被使用,此時,如果將所有模塊的源代碼都靜態編譯到整個應用程序EXE文件中,會產生一些問題:一是增加了應用程序的大小,它會佔用更多的磁盤空間,程序運行時也會消耗較大的內存空間,造成系統資源的浪費;二是在編寫大的EXE程序時,每次修改重建時都必須調整編譯所有源代碼,增加了編譯過程的複雜性,也不利於階段性的單元測試。因此,我們可以利用Windows系統平臺提供的一種完全不同的編程和運行環境,將獨立的程序模塊創建爲較小的DLL文件,並可對它們單獨編譯和測試。這樣,只有當EXE程序確實要調用這些DLL模塊的情況下,系統纔會將它們裝載到內存空間中,不僅減少了EXE文件的大小和對內存空間的需求,而且使這些DLL模塊可以同時被多個應用程序使用。
二、DLL基本概念
       應用程序要從目標代碼(.obj)外部引用函數,可以通過兩種途徑實現——靜態鏈接和動態鏈接。
1. 靜態鏈接
       這種方式下,鏈接程序首先對庫文件(.lib)進行搜索,直到在某個庫中找到包含函數的對象模塊爲止。然後,鏈接程序把這個對象模塊拷貝到可執行文件(.exe)中。鏈接程序負責維護對該函數的所有引用。
       對於Windows而言,靜態鏈接會導致很大的問題。由於Windows是一個多任務的操作系統,會有多個應用程序同時運行,如果有多個應用程序運行相同的靜態鏈接函數,這樣在內存中就會有這個函數的多個副本,造成內存的極大浪費。
2. 動態鏈接
       這種方式下,鏈接程序同樣對庫文件(.lib)進行搜索,直到在某個庫中找到所引用的函數的輸入記錄爲止。注意,不同於靜態鏈接的對象模塊,這裏查找的是輸入記錄,它不直接包含函數的代碼或者數據,而是指定一個動態鏈接庫(.dll)。該動態鏈接庫包含函數的函數名(或函數序號)和代碼。然後,鏈接程序把輸入記錄拷貝到可執行文件中,產生一次對該函數的動態鏈接。動態鏈接庫主要有如下特點。
  •  系統中,同時運行的多個應用程序可以同時使用同一個動態鏈接庫,它們在內存中共享DLL文件的一個拷貝。這樣不僅節省了內存,而且減少了文件的動態交換。
  •  只要編寫的應用程序的函數變量、返回值的類型、數量不發生變化,動態鏈接庫的函數可以不用重新編譯鏈接,直接使用。
  • 只要遵循一定的規則,不同語言編寫的應用程序可以調用同一個動態鏈接庫。
概括來說,DLL實際上是一種磁盤文件,以.dll、.drv、.fon、.sys和許多以.exe爲擴展名的系統文件都可以是DLL。它由全局數據、服務函數和資源組成,在運行時被系統加載到進程的虛擬空間中,成爲調用進程的一部分
三、DLL的分類
       Visual C++支持三種DLL,它們分別是Non-MFC Dll(非MFC動態庫)、Regular Dll(常規DLL)、Extension Dll(擴展DLL)。
1. Non-MFC DLL(非MFC動態庫)
這種動態鏈接庫沒有采用MFC的類庫結構,而是直接用C語言寫的DLL。它的導出函數是標準的C接口,能被MFC和非MFC編寫的應用程序調用。相比採用MFC類庫編寫的DLL,它佔用很少的磁盤和內存空間。
2. Regular DLL(常規 DLL)
這種動態鏈接庫和下面的Extension DLL一樣,是用MFC類庫編寫的。它的一個明顯的特點是,在源文件裏有一個繼承自CWinApp的類,導出的對象可以是C函數、C++類或者C++成員函數。只要應用程序能調用C函數,它就可以調用常規DLL。因此,該類應用程序可以是由Visual C++、Delphi、Visual Basic、Borland C等編譯環境開發的。
3. Extension DLL(擴展 DLL)
       這種動態鏈接庫由MFC的動態鏈接庫版本創建,且只能被使用MFC類庫編寫的應用程序調用。例如,爲了創建一個新的工具欄,創建了一個CToolBar類的派生類,而要導出這個類,必須把它放到一個MFC擴展的DLL中。
       擴展DLL和常規DLL不一樣,它沒有一個從CWinApp繼承邇來的類的對象,所以開發人員必須在DLL中的DllMain函數添加初始化代碼和結束代碼。
四、DLL的工作原理
       應用程序打開動態鏈接庫時,把動態鏈接庫的執行代碼映射到進程的地址空間中,這裏的進程包括了使用動態鏈接庫的每一個進程。而動態鏈接庫中的數據,應用程序則不是通過映射方式獲取,而是做了一個備份。也就是說動態鏈接庫所有的的執行代碼是共享的,但其中的變量,每個應用程序均備份了一份。但也不是所有的數據都如此,有一些數據是爲各個進程所共享的。
1. 文件映射
       當應用程序調用動態鏈接庫時,系統首先要爲這個動態鏈接庫建立一個文件映射視圖,然後搜尋調用者的地址空間,將文件視圖映射到進程的地址空間中。如果同時又有另一個應用程序調用這個動態鏈接庫,那麼系統只是簡單地將這個動態鏈接庫的另一個文件視圖映射到這個應用程序的地址空間。使用這個動態鏈接庫的每一個進程,都有這個動態鏈接庫的一個映射視圖。
2. 引用表
       將文件映射到位後,系統要檢查調用者和動態鏈接庫的引用表,並把每個DLL函數新分配的虛擬地址插入調用者的輸入庫中。如果系統需要調整動態鏈接庫中的引用表,需要拷貝被修改過的內存頁面。由於多個進程調用相同的動態鏈接庫時,這個動態鏈接庫會被映射到不同的虛地址,所以不同的進程對這個動態鏈接庫模塊進行了不同的地址修改。
3. 內存分配
       在默認情況下,動態鏈接庫爲變量分配的所有內存空間,都是由調用這個動態鏈接庫的進程的堆分配的。如果兩個進程同時調用同一個動態鏈接庫,所有這些變量都要被分配兩次,也就是說每個進程的地址空間中都要被分配一次。
      Win32系統中,動態鏈接庫沒有自己的局部堆,當一個進程裝入一個動態鏈接庫時,系統把動態鏈接庫彙總的數據和代碼映射到進程的地址空間中。動態鏈接庫中的任何內存分配都是從進程的地址空間中分配的,而任何其他進程都不能訪問這塊內存,每個進程都有一套只屬於自己的動態鏈接庫的全局和靜態變量,在多進程中,這些變量是不共享的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章