將可執行檔連結至 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