共用方式為


將可執行檔連結至 DLL

可執行檔案會以下列兩種方式之一連結至 DLL:或載入 DLL:

  • 隱含連結,其中操作系統會與使用它的可執行檔同時載入 DLL。 用戶端可執行檔會呼叫 DLL 的導出函式,就像函式是以靜態方式連結並包含在可執行檔中一樣。 隱含連結有時稱為 靜態載入載入時間動態連結

  • 明確連結,其中操作系統會在運行時間視需要載入 DLL。 透過明確連結使用 DLL 的可執行文件必須明確載入和卸除 DLL。 它也必須設定函式指標,以從 DLL 存取它所使用的每個函式。 不同於對靜態連結庫或隱含連結 DLL 中函式的呼叫,用戶端可執行文件必須透過函式指標呼叫明確連結 DLL 中的導出函式。 明確連結有時稱為 動態載入運行時間動態連結

可執行檔可以使用任一連結方法來連結至相同的 DLL。 此外,這些方法並不互斥;一個可執行檔可能會隱含地連結至 DLL,另一個可執行檔可能會明確附加至它。

判斷要使用的連結方法

若要使用隱含連結或明確連結,是您必須為應用程式做出的架構決策。 每個方法都有優點和缺點。

隱含連結

當應用程式的程式代碼呼叫導出的 DLL 函式時,就會發生隱含連結。 編譯或組合呼叫可執行檔的原始程式碼時,DLL 函數調用會在物件程式代碼中產生外部函數參考。 若要解析此外部參考,應用程式必須與 DLL 建立者所提供的匯入連結庫 (.lib 檔案) 連結。

匯入連結庫只包含載入 DLL 的程式代碼,以及實作對 DLL 中函式的呼叫。 在匯入連結庫中尋找外部函式會通知連結器該函式的程式代碼位於 DLL 中。 為了解析 DLL 的外部參考,鏈接器只會將資訊新增至可執行檔,告知系統在進程啟動時要在哪裡尋找 DLL 程式代碼。

當系統啟動包含動態連結參考的程式時,它會使用程式可執行檔中的資訊來找出所需的 DLL。 如果找不到 DLL,系統就會終止進程,並顯示報告錯誤的對話方塊。 否則,系統會將 DLL 模組對應至進程地址空間。

如果任一 DLL 具有初始化和終止程式代碼的進入點函式,例如 DllMain,則操作系統會呼叫 函式。 傳遞至進入點函式的其中一個參數會指定程序代碼,指出 DLL 正在附加至進程。 如果進入點函式未傳回 TRUE,系統會終止進程並報告錯誤。

最後,系統會修改程式的可執行程序代碼,以提供 DLL 函式的起始位址。

就像程式的其餘程式代碼一樣,載入器會在進程啟動時,將 DLL 程式代碼對應至進程的位址空間。 只有在需要時,操作系統才會將它載入記憶體中。 因此, PRELOAD .def 檔案用來控制舊版 Windows 中載入的 和 LOADONCALL 程式代碼屬性不再具有意義。

明確連結

大部分的應用程式都會使用隱含連結,因為它是最簡單的連結方法。 不過,有時需要明確連結。 以下是使用明確連結的一些常見原因:

  • 應用程式不知道在運行時間之前載入的 DLL 名稱。 例如,應用程式可能會在啟動時從組態檔取得 DLL 的名稱和導出的函式。

  • 如果在進程啟動時找不到 DLL,則使用隱含連結的進程會由操作系統終止。 在此情況下,使用明確連結的程式不會終止,而且可能會嘗試從錯誤中復原。 例如,進程可能會通知使用者錯誤,並讓使用者指定 DLL 的另一個路徑。

  • 如果連結至其中任何 DLL 且函 DllMain 式失敗,也會終止使用隱含連結的進程。 在此情況下,不會終止使用明確連結的程式。

  • 隱含連結至許多 DLL 的應用程式可能會很慢,因為 Windows 會在應用程式載入時載入所有 DLL。 為了改善啟動效能,應用程式可能只會在載入後立即針對所需的 DLL 使用隱含連結。 它可能會使用明確的連結,只有在需要 DLL 時才載入其他 DLL。

  • 明確連結不需要使用匯入連結庫連結應用程式。 如果 DLL 中的變更導致匯出序數變更,如果應用程式使用函式的名稱而非序數值呼叫 GetProcAddress ,則不需要重新連結。 使用隱含連結的應用程式仍必須重新連結至已變更的匯入連結庫。

以下是明確連結要注意的兩種危害:

  • 如果 DLL 有 DllMain 進入點函式,操作系統會在呼叫 LoadLibrary的線程內容中呼叫 函式。 如果 DLL 已經附加至進程,則不會呼叫進入點函式,因為先前對的呼叫 LoadLibrary 沒有對函式的對應呼叫 FreeLibrary 。 如果 DLL 使用函DllMain式來初始化進程的每個線程,明確連結可能會導致問題,因為未初始化呼叫 (或 AfxLoadLibrary) 時LoadLibrary已經存在的任何線程。

  • 如果 DLL 將靜態範圍數據宣告為 __declspec(thread),如果明確連結,可能會導致保護錯誤。 呼叫 DLL LoadLibrary之後,每當程式代碼參考此數據時,就會造成保護錯誤。 (靜態範圍數據同時包含全域和本機靜態專案。這就是為什麼當您建立 DLL 時,應該避免使用線程本機記憶體。 如果您無法,請通知 DLL 用戶動態載入 DLL 的潛在陷阱。 如需詳細資訊,請參閱 在動態連結庫 (Windows SDK) 中使用線程本機記憶體。

如何使用隱含連結

若要透過隱含連結來使用 DLL,用戶端可執行文件必須從 DLL 的提供者取得這些檔案:

  • 一或多個頭檔 (.h 檔案)包含 DLL 中匯出數據、函式和C++類別的宣告。 DLL 導出的類別、函式和數據都必須在頭檔中標示 __declspec(dllimport) 。 如需詳細資訊,請參閱 dllexport、dllimport

  • 要連結至可執行文件的匯入連結庫。 連結器會在建置 DLL 時建立匯入連結庫。 如需詳細資訊,請參閱 LIB 檔案作為連結器輸入

  • 實際的 DLL 檔案。

若要透過隱含連結在 DLL 中使用數據、函式和類別,任何用戶端來源檔案都必須包含宣告它們的頭檔。 從程式代碼撰寫的觀點來看,對導出函式的呼叫就像任何其他函數調用一樣。

若要建置用戶端可執行檔,您必須連結至 DLL 的匯入連結庫。 如果您使用外部makefile或建置系統,請指定匯入連結庫與您連結的其他物件檔案或連結庫。

當操作系統載入呼叫的可執行檔時,必須能夠找到 DLL 檔案。 這表示當您安裝應用程式時,您必須部署或驗證 DLL 是否存在。

若要透過明確連結使用 DLL,應用程式必須進行函數調用,才能在運行時間明確載入 DLL。 若要明確連結至 DLL,應用程式必須:

  • 呼叫 LoadLibraryEx 或類似的函式以載入 DLL 並取得模組句柄。

  • 呼叫 GetProcAddress 以取得應用程式所呼叫之每個匯出函式的函式指標。 因為應用程式會透過指標呼叫 DLL 函式,所以編譯程式不會產生外部參考,因此不需要連結至匯入連結庫。 不過,您必須有 typedefusing 語句,以定義您所呼叫之匯出函式的呼叫簽章。

  • 使用 DLL 完成時呼叫 FreeLibrary

例如,這個範例函式會呼叫 LoadLibrary 以載入名為 「MyDLL」 的 DLL、呼叫 GetProcAddress 以取得名為 「DLLFunc1」 的函式指標、呼叫 函式並儲存結果,然後呼叫 FreeLibrary 以卸除 DLL。

#include "windows.h"

typedef HRESULT (CALLBACK* LPFNDLLFUNC1)(DWORD,UINT*);

HRESULT LoadAndCallSomeFunction(DWORD dwParam1, UINT * puParam2)
{
    HINSTANCE hDLL;               // Handle to DLL
    LPFNDLLFUNC1 lpfnDllFunc1;    // Function pointer
    HRESULT hrReturnVal;

    hDLL = LoadLibrary("MyDLL");
    if (NULL != hDLL)
    {
        lpfnDllFunc1 = (LPFNDLLFUNC1)GetProcAddress(hDLL, "DLLFunc1");
        if (NULL != lpfnDllFunc1)
        {
            // call the function
            hrReturnVal = lpfnDllFunc1(dwParam1, puParam2);
        }
        else
        {
            // report the error
            hrReturnVal = ERROR_DELAY_LOAD_FAILED;
        }
        FreeLibrary(hDLL);
    }
    else
    {
        hrReturnVal = ERROR_DELAY_LOAD_FAILED;
    }
    return hrReturnVal;
}

不同於此範例,在大部分情況下,您應該只針對指定的 DLL 在應用程式中呼叫 LoadLibraryFreeLibrary 次。 如果您要在 DLL 中呼叫多個函式,或重複呼叫 DLL 函式,則特別如此。

您還想知道關於哪些方面的詳細資訊?

另請參閱

在 Visual Studio 中建立 C++ DLL