Qt DLL總結【一】-鏈接庫預備知識

轉載自http://qimo601.iteye.com/blog/1396937


1、鏈接庫概念


靜態鏈接庫和動態鏈接庫介紹    

     我們可以創建一種文件裏面包含了很多函數和變量的目標代碼,鏈接的時候只要把這個文件指示給鏈接程序就自動地從文件中查找符合要求的函數和變量進行鏈接,整個查找過程根本不需要我們操心。

     這個文件叫做 “庫(Libary)”,平時我們把編譯好的目標代碼存儲到“庫”裏面,要用的時候鏈接程序幫我們從庫裏面找出來。

 

靜態鏈接庫:

  在早期庫的組織形式相對簡單,裏面的目標代碼只能夠進行靜態鏈接,所以我們稱爲“靜態庫”,靜態庫的結構比較簡單,其實就是把原來的目標代碼放在一起,鏈接程序根據每一份目標代碼的符號表查找相應的符號(函數和變量的名字),找到的話就把該函數裏面需要定位的進行定位,然後將整塊函數代碼放進可執行文件裏,若是找不到需要的函數就報錯退出。

     靜態庫的兩個特點:

      1、鏈接後產生的可執行文件包含了所有需要調用的函數的代碼,因此佔用磁盤空間較大。

      2、如果有多個(調用相同庫函數的)進程在內存中同時運行,內存中就存有多份相同的庫函數代碼,因此佔用內存空間較多。


動態鏈接庫:

      動態鏈接庫就是爲了解決這些問題而誕生的技術,顧名思義,動態鏈接的意思就是在程序裝載內存的時候才真正的把庫函數代碼鏈接進行確定它們的地址,並且就算有幾個程序同時運行,內存也只存在一份函數代碼。

  動態庫的代碼必須滿足這樣一種條件:能夠被加載到不同進程的不同地址,所以代碼要經過特別的編譯處理,我們把這種經過特別處理的代碼叫做“位置無關代碼(Position independed Code .PIC)”.

  根據載入程序何時確定動態代碼的邏輯地址,可以把動態裝載分爲兩類。


      1、靜態綁定(static binding)

     使用靜態綁定的程序一開始載入內存的時候,載入程序就會把程序所有調用到的動態代碼的地址算出確定下來,這種方式使程序剛運行的初始化時間較長,不過旦完成動態裝載,程序的運行速度就很快。


      2、動態綁定(dynamic binding)

      使用這種方式的程序並不在一開始就完成動態鏈接,而是直到真正調用動態庫代碼時,載入程序才計算(被調用的那部分)動態代碼的邏輯地址,然後等到某個時候,程序又需要調用另外某塊動態代碼時,載入程序又去計算這部分代碼的邏輯地址,所以,這種方式使程序初始化時間較短,但運行期間的性能比不上靜態綁定的程序。


      平時默認進行鏈接的標準 C/C++ 函數就是動態庫。


 2、鏈接庫常識
      目前以lib後綴的庫有兩種,一種爲靜態鏈接庫(Static Libary,以下簡稱“靜態庫”),另一種爲動態連接庫(DLL,以下簡稱“動態庫”)的導入庫(Import Libary,以下簡稱“導入庫”)。 
     靜態庫是一個或者多個obj文件的打包,所以有人乾脆把從obj文件生成lib的過程稱爲Archive,即合併到一起。比如你鏈接一個靜態庫,如果其中有錯,它會準確的找到是哪個obj有錯,即靜態lib只是殼子。 
     動態庫一般會有對應的導入庫,方便程序靜態載入動態鏈接庫,否則你可能就需要自己LoadLibary調入DLL文件,然後再手工GetProcAddress獲得對應函數了。有了導入庫,你只需要鏈接導入庫後按照頭文件函數接口的聲明調用函數就可以了。
     導入庫和靜態庫的區別很大,他們實質是不一樣的東西。靜態庫本身就包含了實際執行代碼、符號表等等,而對於導入庫而言,其實際的執行代碼位於動態庫中,導入庫只包含了地址符號表等,確保程序找到對應函數的一些基本地址信息。 這也是實際上很多開源代碼發佈的慣用方式:
     1. 預編譯的開發包:包含一些.dll文件和一些.lib文件。其中這裏的.lib就是導入庫,而不要錯以爲是靜態庫。但是引入方式和靜態庫一樣,要在鏈接路徑上添加找到這些.lib的路徑。而.dll則最好放到最後產生的應用程序exe執行文件相同的目錄。這樣運行時,就會自動調入動態鏈接庫。
      2. 用戶自己編譯: 下載的是源代碼,按照readme自己編譯。生成很可能也是.dll + .lib(導入庫)的庫文件
      3. 如果你只有dll,並且你知道dll中函數的函數原型,那麼你可以直接在自己程序中使用LoadLibary調入DLL文件,GetProcAddress
DLL: 
      動態鏈接庫 (DLL) 是作爲共享函數庫的可執行文件。動態鏈接提供了一種方法,使進程可以調用不屬於其可執行代碼的函數。函數的可執行代碼位於一個 DLL 中,該 DLL 包含一個或多個已被編譯、鏈接並與使用它們的進程分開存儲的函數。DLL 還有助於共享數據和資源。多個應用程序可同時訪問內存中單個 DLL 副本的內容。 
      動態鏈接與靜態鏈接的不同之處在於它允許可執行模塊(.dll 文件或 .exe 文件)僅包含在運行時定位 DLL 函數的可執行代碼所需的信息。在靜態鏈接中,鏈接器從靜態鏈接庫獲取所有被引用的函數,並將庫同代碼一起放到可執行文件中。 
         使用動態鏈接代替靜態鏈接有若干優點。擴展了應用程序的特性、、可以用許多種編程語言來編寫、簡化了軟件項目的管理、有助於節省內存、有助於資源共享、有助於應用程序的本地化、有助於解決平臺差異、可以用於一些特殊的目的。windows使得某些特性只能爲DLL所用。

3、動態鏈接庫的調用
      有兩種類型的鏈接:隱式鏈接和顯式鏈接。

隱式鏈接
      應用程序的代碼調用導出 DLL 函數時發生隱式鏈接。 當調用可執行文件的源代碼被編譯或被彙編時,DLL 函數調用在對象代碼中生成一個外部函數引用。 若要解析此外部引用,應用程序必須與 DLL 的創建者所提供的導入庫(.LIB 文件)鏈接。
      導入庫僅包含加載 DLL 的代碼和實現 DLL 函數調用的代碼。 在導入庫中找到外部函數後,會通知鏈接器此函數的代碼在 DLL 中。 要解析對 DLL 的外部引用,鏈接器只需向可執行文件中添加信息,通知系統在進程啓動時應在何處查找 DLL 代碼。
      系統啓動包含動態鏈接引用的程序時,它使用程序的可執行文件中的信息定位所需的 DLL。 如果系統無法定位 DLL,它將終止進程並顯示一個對話框來報告錯誤。 否則,系統將 DLL 模塊映射到進程的地址空間中。
       如果任何 DLL 具有(用於初始化代碼和終止代碼的)入口點函數,操作系統將調用此函數。 在傳遞到入口點函數的參數中,有一個指定用以指示 DLL 正在附帶到進程的代碼。 如果入口點函數沒有返回 TRUE,系統將終止進程並報告錯誤。
       最後,系統修改進程的可執行代碼以提供 DLL 函數的起始地址。
       與程序代碼的其餘部分一樣,DLL 代碼在進程啓動時映射到進程的地址空間中,且僅當需要時才加載到內存中。 因此,由 .def 文件用來在 Windows 的早期版本中控制加載的 PRELOAD 和 LOADONCALL 代碼特性不再具有任何意義。

顯式鏈接
      大部分應用程序使用隱式鏈接,因爲這是最易於使用的鏈接方法。 但是有時也需要顯式鏈接。 下面是一些使用顯式鏈接的常見原因:
      直到運行時,應用程序才知道需要加載的 DLL 的名稱。 例如,應用程序可能需要從配置文件獲取 DLL 的名稱和導出函數名。
      如果在進程啓動時未找到 DLL,操作系統將終止使用隱式鏈接的進程。 同樣是在此情況下,使用顯式鏈接的進程則不會被終止,並可以嘗試從錯誤中恢復。 例如,進程可通知用戶所發生的錯誤,並讓用戶指定 DLL 的其他路徑。
      如果使用隱式鏈接的進程所鏈接到的 DLL 中有任何 DLL 具有失敗的 DllMain 函數,該進程也會被終止。 同樣是在此情況下,使用顯式鏈接的進程則不會被終止。
      因爲 Windows 在應用程序加載時加載所有的 DLL,故隱式鏈接到許多 DLL 的應用程序啓動起來會比較慢。 爲提高啓動性能,應用程序可隱式鏈接到那些加載後立即需要的 DLL,並等到在需要時顯式鏈接到其他 DLL。
      顯式鏈接下不需將應用程序與導入庫鏈接。 如果 DLL 中的更改導致導出序號更改,使用顯式鏈接的應用程序不需重新鏈接(假設它們是用函數名而不是序號值調用 GetProcAddress),而使用隱式鏈接的應用程序必須重新鏈接到新的導入庫。

     下面是需要注意的顯式鏈接的兩個缺點:

     如果 DLL 具有 DllMain 入口點函數,則操作系統在調用 LoadLibrary 的線程上下文中調用此函數。 如果由於以前調用了 LoadLibrary 但沒有相應地調用 FreeLibrary 函數而導致 DLL 已經附加到進程,則不會調用此入口點函數。 如果 DLL 使用 DllMain 函數爲進程的每個線程執行初始化,顯式鏈接會造成問題,因爲調用 LoadLibrary(或 AfxLoadLibrary)時存在的線程將不會初始化。
      如果 DLL 將靜態作用域數據聲明爲 __declspec(thread),則在顯式鏈接時 DLL 會導致保護錯誤。 用 LoadLibrary 加載 DLL 後,每當代碼引用此數據時 DLL 就會導致保護錯誤。 (靜態作用域數據既包括全局靜態項,也包括局部靜態項。)因此,創建 DLL 時應避免使用線程本地存儲區,或者應(在用戶嘗試動態加載時)告訴 DLL 用戶潛在的缺陷。

4、顯示鏈接和隱式鏈接的區別

一、Implicit Linking(隱式連接)
       Implicit Linking(隱式連接) ,又叫靜態載入,所謂靜態載入是指程序在連接時期即與dlls所對應的import libraries作靜態連接,於是可執行文件中便對所有的dll函數都有一份重定位表格(relocation table)和待修正記錄(fixup record)。當程序被windows載入器載入內存中時,載入器會自動修正所有的fixup records,而這個fixup records 就是記錄DLL中所有輸出資源的正確位置地址,經過這樣的程序動態連接便自動產生。也就是說,程序開始執行時,會用靜態載入的方式時所使用的DLLs都載入到程序的內存裏。
      靜態載入方式的優點
      1、靜態載入方式所使用的dll會在應用程序執行時載入,然後就可以調用所有dll中提供的函數,就像是程序中一樣。
      2、處理簡單,載入的方法有編譯器負責處理,不需動腦筋。
 
      靜態載入方式的缺點
      1、當程序機構態載入方式所使用的dll不存在時,程序開始就會報dll無法找到的錯誤而使得程序無法運行。
編譯時需要加入import library。
      2、若調用的dll很多,載入應用程序的速度就會很慢。
不同的c++編譯器靜態載入的方式也不一樣。
 
二、Explicit Linking(顯式連接)
          所謂Explicit Link(顯式連接)又叫動態載入,使用dll的可執行文件必須明確調用載入和御載dll的函數調用(Function Call),並且存取dll的輸出函數。用戶端必須通過函數聲明調用函數。
          可執行文件可以使用任何一種連接方式的相同低dll。並且,這些機制之間並不會相互排斥,因此,當一個可執行文件隱式的連接dll時,其他程序還可以顯示地連接它。
 
      動態載入方式的優缺點:
       1、dll只有需要時才載入內存中,這樣可以更有效地使用存儲空間。
      2、應用程序載入速度較隱式連接較快,因爲當程序開始載入時並不需要把dll載入到程序中。
      3、編譯時不需要額外的import library。
      4、可以讓用戶個清楚地知道dll的載入流程。     
      缺點就是必須多寫一點代碼。 
      動態載入基本流程
      必須使用LoadLibrary這個Windows API來手動載入DLL,並使用GetProcessAddress來取得所需要使用的函數的函數指針,最後用FreeLibrary將DLL釋放。所以學會動態載入DLL時,必須先知道函數指針的用法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章