Share via


延遲載入 DLL 的連結器支援

MSVC 連結器支援延遲載入 DLL。 這項功能可減輕您使用 Windows SDK 函 LoadLibrary 式和 GetProcAddress 實作 DLL 延遲載入的需求。

若未延遲載入,在執行時間載入 DLL 的唯一方法是使用 LoadLibraryGetProcAddress ;當可執行檔或 DLL 使用 DLL 載入時,作業系統會載入 DLL。

當您隱含連結 DLL 時,連結器會提供延遲 DLL 載入的選項,直到程式在該 DLL 中呼叫函式為止。

應用程式可以使用協助程式函式的 /DELAYLOAD [延遲載入匯入] 連結器選項來延遲載入 DLL。 (Microsoft 提供預設協助程式函式實作。協助程式函式會藉由呼叫 LoadLibraryGetProcAddress ,依需求在執行時間載入 DLL。

如果下列事項,請考慮延遲載入 DLL:

  • 您的程式可能不會在 DLL 中呼叫函式。

  • DLL 中的函式在程式執行後期之前可能無法呼叫。

DLL 的延遲載入可以在 EXE 或 DLL 專案的建置期間指定。 延遲載入一或多個 DLL 本身的 DLL 專案不應該在 中 DllMain 呼叫延遲載入的進入點。

指定要延遲載入的 DLL

您可以使用連結器選項,指定要延遲載入的 /delayload:dllname DLL。 如果您不打算使用自己的協助程式函式版本,您也必須將程式連結至 delayimp.lib (適用于傳統型應用程式)或 dloadhelper.lib (適用于 UWP 應用程式)。

以下是載入 DLL 的簡單範例:

// cl t.cpp user32.lib delayimp.lib  /link /DELAYLOAD:user32.dll
#include <windows.h>
// uncomment these lines to remove .libs from command line
// #pragma comment(lib, "delayimp")
// #pragma comment(lib, "user32")

int main() {
   // user32.dll will load at this point
   MessageBox(NULL, "Hello", "Hello", MB_OK);
}

建置專案的偵錯版本。 使用偵錯工具逐步執行程式碼,您會發現 user32.dll 只有在呼叫 MessageBox 時才會載入 。

明確卸載延遲載入的 DLL

連結 /delay:unload 器選項可讓您的程式碼明確卸載載入延遲的 DLL。 根據預設,延遲載入的匯入會保留在匯入位址表 (IAT) 中。 不過,如果您在 /delay:unload 連結器命令列上使用 ,協助程式函式會支援 __FUnloadDelayLoadedDLL2 呼叫明確卸載 DLL,並將 IAT 重設為其原始格式。 現在不正確指標會遭到覆寫。 如果存在,IAT 是 結構中的 ImgDelayDescr 欄位,其中包含原始 IAT 複本的位址。

卸載延遲載入 DLL 的範例

此範例示範如何明確卸載包含函 fnMyDll 式的 DLL MyDll.dll

// link with /link /DELAYLOAD:MyDLL.dll /DELAY:UNLOAD
#include <windows.h>
#include <delayimp.h>
#include "MyDll.h"
#include <stdio.h>

#pragma comment(lib, "delayimp")
#pragma comment(lib, "MyDll")
int main()
{
    BOOL TestReturn;
    // MyDLL.DLL will load at this point
    fnMyDll();

    //MyDLL.dll will unload at this point
    TestReturn = __FUnloadDelayLoadedDLL2("MyDll.dll");

    if (TestReturn)
        printf_s("\nDLL was unloaded");
    else
        printf_s("\nDLL was not unloaded");
}

卸載延遲載入 DLL 的重要注意事項:

  • 您可以在 MSVC include 目錄中的 檔案 delayhlp.cpp 中找到 函式的實作 __FUnloadDelayLoadedDLL2 。 如需詳細資訊,請參閱 瞭解延遲載入協助程式函式

  • name 式的參數 __FUnloadDelayLoadedDLL2 必須完全符合匯入程式庫所包含的內容(包括大小寫)。 (該字串也位於影像中的匯入資料表中。您可以使用 來檢視匯入程式庫 DUMPBIN /DEPENDENTS 的內容。 如果您偏好不區分大小寫的字串比對,您可以更新 __FUnloadDelayLoadedDLL2 為使用其中一個不區分大小寫的 CRT 字串函式或 Windows API 呼叫。

系結延遲載入的匯入

預設連結器行為是建立延遲載入 DLL 的可系結匯入位址表 (IAT)。 如果 DLL 已系結,協助程式函式會嘗試使用系結資訊,而不是在每個參考的匯入上呼叫 GetProcAddress 。 如果時間戳記或慣用的位址不符合載入 DLL 中的時間戳記,協助程式函式會假設系結的匯入位址資料表已過期。 它會繼續,就像 IAT 不存在一樣。

如果您不想系結 DLL 延遲載入的匯入,請在連結器命令列上指定 /delay:nobind 。 連結器不會產生系結的匯入位址表,這會節省影像檔案的空間。

載入延遲載入 DLL 的所有匯入

定義 __HrLoadAllImportsForDll 于 中的 delayhlp.cpp 函式會指示連結器從以 /delayload 連結器選項指定的 DLL 載入所有匯入。

當您一次載入所有匯入時,可以在一個地方集中處理錯誤。 您可以避免針對所有實際呼叫匯入的結構化例外狀況處理。 它也會避免您的應用程式在順利載入其他程式之後,失敗的部分方式:例如,如果協助程式程式碼無法載入匯入。

呼叫 __HrLoadAllImportsForDll 並不會變更攔截和錯誤處理的行為。 如需詳細資訊,請參閱 錯誤處理和通知

__HrLoadAllImportsForDll 會區分大小寫地比較儲存在 DLL 本身內的名稱。

以下是在呼叫 TryDelayLoadAllImports 的函式中使用 來嘗試載入具名 DLL 的範例 __HrLoadAllImportsForDll 。 它會使用 函式 CheckDelayException 來判斷例外狀況行為。

int CheckDelayException(int exception_value)
{
    if (exception_value == VcppException(ERROR_SEVERITY_ERROR, ERROR_MOD_NOT_FOUND) ||
        exception_value == VcppException(ERROR_SEVERITY_ERROR, ERROR_PROC_NOT_FOUND))
    {
        // This example just executes the handler.
        return EXCEPTION_EXECUTE_HANDLER;
    }
    // Don't attempt to handle other errors
    return EXCEPTION_CONTINUE_SEARCH;
}

bool TryDelayLoadAllImports(LPCSTR szDll)
{
    __try
    {
        HRESULT hr = __HrLoadAllImportsForDll(szDll);
        if (FAILED(hr))
        {
            // printf_s("Failed to delay load functions from %s\n", szDll);
            return false;
        }
    }
    __except (CheckDelayException(GetExceptionCode()))
    {
        // printf_s("Delay load exception for %s\n", szDll);
        return false;
    }
    // printf_s("Delay load completed for %s\n", szDll);
    return true;
}

您可以使用 的結果 TryDelayLoadAllImports 來控制是否呼叫匯入函式。

錯誤處理和通知

如果您的程式使用延遲載入 DLL,它必須妥善處理錯誤。 程式執行時發生的失敗會導致未處理的例外狀況。 如需 DLL 延遲載入錯誤處理和通知的詳細資訊,請參閱 錯誤處理和通知

傾印延遲載入的匯入

延遲 DUMPBIN /IMPORTS 載入的匯入可以使用 來傾印。 這些匯入會顯示與標準匯入稍微不同的資訊。 它們會隔離為清單的專屬區段 /imports ,並明確標示為延遲載入的匯入。 如果影像中有卸載資訊,則表示。 如果有系結資訊存在,則會記下目標 DLL 的日期和時間戳記記,以及匯入的系結位址。

延遲載入 DLL 的條件約束

DLL 匯入延遲載入有數個條件約束。

  • 不支援資料匯入。 因應措施是使用 明確處理匯 LoadLibrary 入資料(或 GetModuleHandle 在您知道延遲載入協助程式載入 DLL 之後使用 )和 GetProcAddress

  • 不支援延遲載入 Kernel32.dll 。 此 DLL 必須載入,延遲載入協助程式常式才能運作。

  • 不支援轉送進入點的系結。

  • 如果 DLL 載入延遲,進程可能會有不同的行為,而不是在啟動時載入。 如果延遲載入 DLL 的進入點發生個別進程初始化,就可以看到它。 其他案例包括靜態 TLS(執行緒本機儲存體),使用 __declspec(thread) 宣告,當 DLL 透過 LoadLibrary 載入時,不會處理此情況。 動態 TLS (使用 TlsAllocTlsFreeTlsGetValueTlsSetValue) 仍可用於靜態或延遲載入 DLL。

  • 在每個函式的第一次呼叫之後,重新初始化匯入函式的靜態全域函式指標。 這是必要的,因為第一次使用函式指標指向 Thunk,而不是已載入的函式。

  • 使用一般匯入機制時,目前無法延遲只從 DLL 載入特定程式。

  • 不支援自訂呼叫慣例(例如在 x86 架構上使用條件碼)。 此外,浮點暫存器不會儲存在任何平臺上。 請小心您的自訂協助程式常式或攔截常式使用浮點類型:常式必須在使用暫存器呼叫慣例與浮點參數的電腦上儲存和還原完整的浮點狀態。 請小心延遲載入 CRT DLL,特別是如果您在說明函式中呼叫採用數值資料處理者 (NDP) 堆疊上浮點參數的 CRT 函式。

瞭解延遲載入協助程式函式

連結器支援的延遲載入的協助程式函式是實際在執行時間載入 DLL 的功能。 您可以修改協助程式函式來自訂其行為。 不要在 中使用 delayimp.lib 提供的協助程式函式,而是撰寫您自己的函式,並將它連結至您的程式。 一個協助程式函式會提供所有延遲載入的 DLL。 如需詳細資訊,請參閱 瞭解延遲載入協助程式函 式和 開發您自己的協助程式函式

另請參閱

在 Visual Studio 中建立 C++ DLL
MSVC 連結器參考