Can you summarize the following document?
        
  DLL的最初目的是節約應用程式所需的磁碟和記憶體空間。在一個傳統的非共享函式庫中,一部分程式碼簡單地附加到呼叫的程式上。如果兩個程式呼叫同一個子程式,就會出現兩份那段程式碼。相反,許多應用共享的程式碼能夠切分到一個DLL中,在硬碟上存為一個檔案,在記憶體中使用一個例項(instance)。DLL的廣泛應用使得早期的視窗能夠在緊張的記憶體條件下執行。  DLL提供瞭如模組化這樣的共享函式庫的普通好處。模組化允許僅僅更改幾個應用程式共享使用的一個DLL中的程式碼和資料而不需要更改應用程式自身。這種模組化的基本形式允許如Microsoft Office、Microsoft Visual Studio、甚至Microsoft Windows自身這樣大的應用程式使用較為緊湊的補丁和服務包。  模組化的另外一個好處是外掛的通用介面使用。單個的介面允許舊的模組與新的模組一樣能夠與以前的應用程式執行時無縫地整合到一起,而不需要對應用程式本身作任何更改。這種動態擴充套件的思想在ActiveX中發揮到了極致。  儘管有這麼多的優點,使用DLL也有一個缺點:DLL地獄,也就是幾個應用程式在使用同一個共享DLL函式庫發生版本衝突。這樣的衝突可以通過將不同版本的問題DLL放到應用程式所在的資料夾而不是放到系統資料夾來解決;但是,這樣將抵消共享DLL節約的空間。目前,Microsoft.NET將解決DLL hell問題當作自己的目標,它允許同一個共享函式庫的不同版本並列共存。由於現代的計算機有足夠的磁碟空間和記憶體,這也可以作為一個合理的實現方法。

  

  在Win32中,DLL檔案按照片段(sections)進行組織。每個片段有它自己的屬性,如可寫或是隻讀、可執行(程式碼)或者不可執行(資料)等等。這些section可分為兩種,一個是與絕對地址定址無關的,所以能被多程序公用;另一個是與絕對地址定址有關的,這個就必須由每個程序有自己的副本專用。sections的這種二分類,在編譯DLL時就已經由編譯器、連結器給標註好了。所以在裝入DLL時,裝入器知道哪些sections在記憶體實體地址空間只需要有一份,供多個程序共享(對映到各個程序的記憶體邏輯地址空間,所以邏輯地址可以不同); 哪些sections必須是程序使用自己的專用副本。  也可在程式編譯時通過編譯選項/section 的S (Shared)屬性,顯式指定哪個節是跨程序共享的。預設情況下,DLL的資料節都是寫時複製(COW)。  具體說,DLL裝入時需考慮下述情形:  DLL程式碼段通常被使用這個DLL的所有程序所共享。如果程式碼段所佔據的實體記憶體被收回,它的內容就會被放棄,後面如果需要的話就直接從DLL檔案重新載入。  與程式碼段不同,DLL的資料段通常是私有的;也就是說,每個使用DLL的程序都有自己的DLL資料副本。作為選擇,資料段可以設定為共享,允許通過這個共享記憶體區域進行程序間通訊。但是,因為使用者許可權不能應用到這個共享DLL記憶體,這將產生一個安全漏洞;也就是一個程序能夠破壞共享資料,這將導致其它的共享程序異常。例如,一個使用訪客賬號的程序將可能通過這種方式破壞其它執行在特權賬號的程序。這是在DLL中避免使用共享片段的一個重要原因。  當DLL被如UPX這樣一個可執行的packer壓縮時,它的所有程式碼段都標記為可以讀寫並且是非共享的。可以讀寫的程式碼段,類似於私有資料段,是每個程序私有的並且被頁面檔案備份。這樣,壓縮DLL將同時增加記憶體和磁碟空間消耗,所以共享DLL應當避免使用壓縮DLL。

  DLL輸出的每個函式都由一個數字序號唯一標識,也可以由可選的名字標識。同樣,DLL引入的函式也可以由序號或者名字標識。對於內部函式來說,只輸出序號的情形很常見。對於大多數視窗API函式來說名字是不同視窗版本之間保留不變的;序號有可能會發生變化。這樣,我們不能根據序號引用視窗API函式。  按照序號引用函式並不一定比按照名字引用函式效能更好:DLL輸出表是按照名字排列的,所以對半查詢可以用來在在這個表中根據名字查詢這個函式。另外一方面,只有線性查詢才可以用於根據序號查詢函式。  將一個可執行檔案繫結到一個特定版本的DLL也是可能的,這也就是說,可以在編譯時解析輸入函式(imported functions)的地址。對於繫結的輸入函式,連結工具儲存了輸入函式繫結的DLL的時間戳和校驗和。在執行時Windows檢查是否正在使用同樣版本的函式庫,如果是的話,Windows將繞過處理輸入函式;否則如果函式庫與繫結的函式庫不同,Windows將按照正常的方式處理輸入函式。  繫結的可執行檔案如果執行在與它們編譯所用的環境一樣,函式呼叫將會較快,如果是在一個不同的環境它們就等同於正常的呼叫,所以繫結輸入函式沒有任何的缺點。例如,所有的標準Windows應用程式都繫結到它們各自的Windows釋出版本的系統DLL。將一個應用程式輸入函式繫結到它的目的環境的好機會是在應用程式安裝的過程。

  程序/執行緒載入時,可以通過DllMain函式通知DLL相關資訊,提供對應處理的機會。

  對於Windows,載入動態連結庫時:  Windows Desktop應用程式的DLL標準搜尋序: 如果SafeDllSearchMode被禁止,則當前目錄成為第二個被搜尋的目錄。  Windows Desktop應用程式的DLL替代搜尋序。 這包括兩種情況: 當所有DLL都完成搜尋,要開始執行DLL的初始化時,替代搜尋序結束,恢復標準搜尋序。

  對於一個程序,每個被載入的DLL在程序的PEB中有一個引用計數器,在當前程序中每多一次載入該計數器便加一。codice_7 與 codice_8 指令影響每一個行程內含的計數器;動態連結則不影響。因此藉由呼叫 codice_8 而從記憶體解除安裝一 DLL 是很重要的。一個行程可以從它自己的 VAS 登出此計數器。該計數器變為0時,這個DLL從當前程序中解除安裝,但並不意味著會從實體記憶體中解除安裝,因為其它程序可能使用這個DLL。  DLL 檔案能夠在執行時使用 codice_7(或者 codice_11)API 函式進行顯式呼叫,這個的過程微軟簡單地稱為執行時動態呼叫。如果在上述API的引數中指明瞭庫檔案的全路徑,則不再搜尋這個庫檔案。API 函式codice_12 查詢具有某名稱的輸出函式、codice_8解除安裝 DLL(實際上是引用計數器減一)。這些函式類似於 POSIX 標準 API 中的 codice_14、codice_15、和 codice_16。  注意微軟簡單稱為“執行時動態連結”的執行時隱式連結,如果不能找到連結的 DLL 檔案,Windows 將提示一個錯誤訊息並且呼叫應用程式失敗(準確地說是不能建立程序)。應用程式開發人員不能通過編譯連結來處理這種缺少 DLL 檔案的隱式連結問題,而且在更改了實現後還必須重新編譯連結整個程式——預設的增量連結模式會一直保留著那條缺少的函式引用,只有重新編譯連結才能去掉。相反,雖然顯式連結的程式碼量增多了,但開發人員有機會提供一個完善的出錯處理機制。  執行時顯式連結的過程在所有語言中都是相同的,因為它依賴於 Windows API 而不是語言結構。只要一種語言能夠呼叫上述的 codice_7 等函式,就能執行執行時顯示連結。

  16位Windows,所有程序共享同一個記憶體地址空間。如果一個DLL被多個程序使用,它只被載入到記憶體中一次,只有一份資料節(data segment)。也就是說,DLL是系統全域性而不是程序內的;程序不能得到DLL的一份獨立的拷貝。每個DLL在記憶體中只有一份例項(instance)。  如果一個EXE檔案同時執行多次,那麼在記憶體中只有該EXE的一套只讀拷貝(如程式碼或資源),但這個EXE的每個程序都有自己的一套資料段,即有多份例項(instance)。實際上,程序的例項控制代碼就是data segment(存放程序的全域性變數)的記憶體起始地址。  模組(module)是指一個硬碟檔案,可被載入到記憶體中。模組控制代碼是一個資料結構,表示這個硬碟檔案的各部分(section)出自哪裡,是否已經載入到記憶體中。  所以程序只能用例項控制代碼標識,而不能用模組控制代碼標識。  Win32的程序使用自己專用的邏輯記憶體空間。程序的全域性變數不再是跨程序邊界可見的。例項控制代碼與模組控制代碼相同,都是指向模組載入後的記憶體基地址。這樣規定實際上也相容了Win16時例項控制代碼與模組控制代碼的含義。

  EXE和DLL都有其自己的資源(如對話方塊資源),而且這些資源的ID可能重複,預設使用EXE的資源。 如果需要載入、使用DLL中的資源,需要通過DLL載入後的例項控制代碼(HINSTANCE)來找到DLL的資源。  應用程式程序本身及其呼叫的每個DLL模組都具有一個全域性唯一的HINSTANCE控制代碼,它們代表了EXE或DLL模組在程序邏輯地址空間中的起始地址。程序本身的模組控制代碼一般為0x400000,而DLL模組預設載入地址為0x10000000。如果程式同時載入了多個DLL,則每個DLL模組都會有不同的HINSTANCE。  幾種可行的辦法:  方法1:  AFX_MANAGE_STATE(AfxGetStaticModuleState());必須作為介面函式的第一條語句。功能是在棧上建立一個AFX_MAINTAIN_STATE2類的例項,利用其建構函式和解構函式對_afxThreadState.GetData()所指向記憶體儲存塊的模組狀態(AFX_MODULE_STATE型別)設定現場(AfxGetModuleState函式返回的值)及恢復現場。 方法2: 方法3:

  

  在原始檔的開頭使用關鍵詞codice_18而不是codice_19,在檔案的末尾輸出函式使用codice_20排列。  Delphi不需要codice_21檔案以從DLL中輸入函式。為了連結一個DLL,在函式宣告中使用關鍵詞codice_22。

  在Visual Basic(VB)中只支援執行時連結;但是除了使用codice_7和codice_12這兩個API函式之外,允許使用輸入函式的宣告來引入DLL函式,如果找不到codice_25檔案,VB將產生一個執行時異常。開發人員可以捕獲該異常並且進行適當的處理。  Visual Basic 6這樣的較老的語言,只能呼叫codice_26呼叫約定修飾的函式,並需要在宣告DLL的輸出函式時使用Alias,否則就會出現在DLL中找不到函式入口點的錯誤。例如:  也可以用def檔案來定義dll輸出函式的名字。。另外需要注意,VB6呼叫的dll,如果還依賴其他的dll,那麼這些間接依賴的dll必須在VB6程式的dll搜尋路徑上,否則會報無法找到(被直接依賴的)dll的錯誤。  為了相容,codice_27 的方式一直沿用到 VB.net。不過在.net 中,平臺呼叫提供了一種新的宣告DLL中的輸出函式方式,通過 codice_28 提供。例如:  在同為.net 語言的 C# 中,這是僅有的處理方式。當然託管 C/C++ 就不必受此限制,見下節。

  微軟Visual C++(MSVC)提供了許多標準C++的擴充套件,它允許直接在C++程式碼中將函式(類、資料變數)宣告為輸入或輸出;這種做法已經被Windows平臺上其他的C和C++編譯器採納,包括Windows平臺上的GCC。這種擴充套件在函式宣告前使用codice_29屬性:  如果是遵從C命名規範(naming convention)的外部名字,它們必須在C++程式碼中宣告為codice_36以避免它們使用C++命名規範。如果使用dll的語言(如Fortran、Visual Basic 6)不能識別C命名規範,那麼需要採取辦法指出在DLL中輸出函式的名字。也可以通過DEF檔案來定義輸出函式的名字與序號。或者使用如下的連結指令來制定dll輸出函式名字:  除了使用codice_29屬性定義輸入輸出函式之外,它們也可以列在專案codice_38檔案的IMPORT或者EXPORTS部分。codice_38檔案不是由編譯器進行處理,而是由連結器據此生成DLL檔案中輸出函式的名字與順序(ordinal number),這樣DEF檔案就不是C/C++特有的,其它語言寫的程式如果想要編譯為DLL也可以使用DEF。  DLL的編譯將生成codice_25和codice_21兩個檔案。codice_21檔案被稱為輸入庫(import library),在編譯時為呼叫DLL的程式提供“樁”(stub)實際上是間接跳轉到執行時載入的DLL的對應的函式上再繼續執行。這種通過輸入庫來使用DLL的方式,在程式執行時啟動程序時就會自動(隱式)載入所有用到的DLL。另一種使用DLL的方式是通過codice_7(或者codice_11) API函式進行顯式載入DLL,用codice_12 API函式通過函式名稱獲取其載入後的記憶體地址、通過codice_8解除安裝DLL。  codice_25一般說來必須放在PATH環境變數、預設系統路經或者是使用它的程式所在路徑三個的一個之內。COM伺服器DLL使用regsvr32.exe註冊,它將DLL的路徑和全域性唯一身份(GUID)記錄在登錄檔中。應用程式能夠通過在登錄檔中查詢GUID、找到它的路徑從而使用這個DLL。

  

  下面的例子展示了與特定語言相關的從DLL輸出符號表的方法。  Delphi  C 或 C++

  下面的例子展示了與特定語言相關的如何在編譯時連結DLL輸入符號表的方法。  C 或 C++

  下面的例子展示瞭如何使用不同語言特有的WIN32 API繫結進行執行時的呼叫和連結。  Microsoft Visual Basic C 或 C++

  元件物件模型(COM)將DLL概念擴充到了物件導向程式設計。物件能夠從另外一個程序呼叫或者在另外一臺機器上執行。COM物件有一個唯一的GUID並且能夠實現強大的後臺以簡化如Visual Basic和ASP這樣的GUI前臺應用。它們也可以使用指令碼語言程式設計。COM物件的建立和使用比DLL更為複雜。
動態連結函式庫(英語:Dynamic-link library,縮寫為DLL)是微軟公司在微軟視窗作業系統中實現共享函式庫概念的一種實作方式。這些函式庫函式的副檔名是codice_1、codice_2(包含ActiveX控制的函式庫)或者codice_3(舊式的系統驅動程式)。