徹底搞懂動態庫、靜態庫、運行時庫、引入庫之間的區別

定義

  • 運行時庫:Unix中一個典型的運行時庫例子就是libc,它包含標準的C函數,如,print(),exit()等等,用戶能創建他們自己的運行庫(在Windows中是DLL),而具體的細節依賴編譯器和操作系統的。
  • 靜態庫:函數和數據被編譯進一個二進制文件(通常擴展名為.lib),靜態庫實際上是在鏈接時被鏈接到EXE的,庫本身不需要與可執行文件一起發行。
  • 動態庫:用VC++創建的動態庫包含兩個文件,一個lib文件和一個dll文件,這個lib文件就是引入庫,不是靜態庫,引入庫有時也叫輸入庫或導入庫。
  • 徹底搞懂動態庫、靜態庫、運行時庫、引入庫之間的區別

    注:windows操作系統下動態庫和運行時庫的擴展名都是.dll,COM組件的擴展名也是.dll,動態庫的引入庫和靜態庫的擴展名都是.lib。

    windows下調用動態庫的方法:

    1 隱式加載:即在程序中包含lib文件和.h文件,隱式鏈接有時稱為靜態加載或加載時動態鏈接。例如:

    #include "somedll.h"

    #pragma comment( lib, "somedll.lib")

    然後就可以直接調用此dll中的函數,注意運行時仍然需要somedll.dll。

    2 顯示加載:使用loadlibrary,GetProcAddress,FreeLibrary,不需要.h文件和.lib文件,但是要知道函數的原型。顯式鏈接有時稱為動態加載或運行時動態鏈接

    3 區別:如果在進程啟動時未找到 DLL,操作系統將終止使用隱式鏈接的進程。同樣是在此情況下,使用顯式鏈接的進程則不會被終止,並可以嘗試從錯誤中恢復。

    有關Win32 DLL,Unix共享庫及普通庫的詳細庫結構信息請參考《鏈接器與加載器》一書。


    1 確定要使用的鏈接方法:

    有兩種類型的鏈接:隱式鏈接和顯式鏈接。

    隱式鏈接

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

    顯式鏈接
    大部分應用程序使用隱式鏈接,因為這是最易於使用的鏈接方法。但是有時也需要顯式鏈接。下面是一些使用顯式鏈接的常見原因:

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

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

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

    2 隱式鏈接:

    為隱式鏈接到 DLL,可執行文件必須從 DLL 的提供程序獲取下列各項:

    1. 包含導出函數和/或 C++ 類的聲明的頭文件(.h 文件)。類、函數和數據均應具有 __declspec(dllimport),有關更多信息,請參見 dllexport, dllimport。
    2. 要鏈接的導入庫(.LIB files)。(生成 DLL 時鏈接器創建導入庫。)
    3. 實際的 DLL(.dll 文件)。

    使用 DLL 的可執行文件必須包括頭文件,此頭文件包含每個源文件中的導出函數(或 C++ 類),而這些源文件包含對導出函數的調用。從編碼的角度講,導出函數的函數調用與任何其他函數調用一樣。

    若要生成調用可執行文件,必須與導入庫鏈接。如果使用的是外部生成文件,請指定導入庫的文件名,此導入庫中列出了要鏈接到的其他對象 (.obj) 文件或庫。

    操作系統在加載調用可執行文件時,必須能夠定位 DLL 文件。

    3 顯式鏈接:

    在顯式鏈接下,應用程序必須進行函數調用以在運行時顯式加載 DLL。為顯式鏈接到 DLL,應用程序必須:

    1. 調用 LoadLibrary(或相似的函數)以加載 DLL 和獲取模塊句柄。
    2. 調用 GetProcAddress,以獲取指向應用程序要調用的每個導出函數的函數指針。由於應用程序是通過指針調用 DLL 的函數,編譯器不生成外部引用,故無需與導入庫鏈接。
    3. 使用完 DLL 後調用 FreeLibrary。


    分享到:


    相關文章: