共用方式為


DLL 和 Visual C++ 執行階段程式庫行為

當您使用 Visual Studio 建置動態連結庫 (DLL) 時,鏈接器預設會包含 Visual C++執行時間連結庫 (VCRuntime)。 VCRuntime 包含初始化和終止 C/C++ 可執行檔所需的程式代碼。 連結至 DLL 時,VCRuntime 程式代碼會提供稱為 的內部 DLL 進入點函式 _DllMainCRTStartup ,此函式會處理 Windows OS 訊息至 DLL 以附加至進程或線程或線程。 函 _DllMainCRTStartup 式會執行基本工作,例如已設定堆疊緩衝區安全性、C 運行時間連結庫 (CRT) 初始化和終止,以及對靜態和全域物件的建構函式和解構函式的呼叫。 _DllMainCRTStartup 也會針對 WinRT、MFC 和 ATL 等其他連結庫呼叫攔截函式,以執行自己的初始化和終止。 如果沒有此初始化,CRT 和其他連結庫以及您的靜態變數都會處於未初始化的狀態。 不論 DLL 使用靜態連結的 CRT 還是動態連結的 CRT DLL,都會呼叫相同的 VCRuntime 內部初始化和終止例程。

預設 DLL 進入點_DllMainCRTStartup

在 Windows 中,所有 DLL 都可以包含選擇性的進入點函式,通常稱為 DllMain,該函式會針對初始化和終止呼叫。 這可讓您視需要配置或釋放其他資源。 Windows 會在四種情況下呼叫進入點函式:進程附加、進程中斷鏈接、線程連結和線程卸離。 當 DLL 載入行程位址空間時,當使用 DLL 的應用程式載入時,或當應用程式在運行時間要求 DLL 時,操作系統會建立個別的 DLL 數據復本。 這稱為 進程附加當載入 DLL 的進程建立新的線程時,就會發生線程附加線程卸離 會在線程終止時發生,而 進程卸離 是不再需要 DLL 且由應用程式釋放時。 操作系統會針對每個事件分別呼叫 DLL 進入點,並 傳遞每個事件類型的 reason 自變數。 例如,OS 會將 作為原因自變數傳送DLL_PROCESS_ATTACH給訊號進程附加。

VCRuntime 連結庫提供稱為 _DllMainCRTStartup 的進入點函式,以處理預設初始化和終止作業。 在行程附加時,函 _DllMainCRTStartup 式會設定緩衝區安全性檢查、初始化 CRT 和其他連結庫、初始化運行時間類型資訊、初始化和呼叫靜態和非本機數據的建構函式、初始化線程本機記憶體、遞增每個附加的內部靜態計數器,然後呼叫使用者或連結庫提供的 DllMain。 在進程中斷連結上,函式會反向執行這些步驟。 它會呼叫 DllMain、遞減內部計數器、呼叫解構函式、呼叫CRT終止函式和已註冊 atexit 的函式,並通知任何其他終止連結庫。 當附件計數器移至零時,函式會傳回 FALSE ,以指示 Windows 可以卸除 DLL。 此 _DllMainCRTStartup 函式也會在線程附加和線程中斷連結期間呼叫。 在這些情況下,VCRuntime 程式代碼不會自行進行額外的初始化或終止,而只會呼叫 DllMain 來傳遞訊息。 如果 DllMain 從進程附加傳回 FALSE 、發出訊號失敗、 _DllMainCRTStartup 再次呼叫 DllMain 並傳遞 DLL_PROCESS_DETACHreason 自變數,則會經歷終止程式的其餘部分。

在 Visual Studio 中建置 DLL 時,VCRuntime 提供的預設進入點 _DllMainCRTStartup 會自動連結在 中。 您不需要使用 /ENTRY (進入點符號) 連結器選項來指定 DLL 的進入點函式。

注意

雖然您可以使用 /ENTRY: 鏈接器選項為 DLL 指定另一個進入點函式,但我們不建議這麼做,因為您的進入點函式必須以相同順序複製 _DllMainCRTStartup 所有作業。 VCRuntime 提供可讓您複製其行為的函式。 例如,您可以在行程附加時立即呼叫 __security_init_cookie,以支援 /GS (緩衝區安全性檢查) 緩衝區檢查選項。 您可以呼叫 _CRT_INIT 函式,傳遞與進入點函式相同的參數,以執行 DLL 初始化或終止函式的其餘部分。

初始化 DLL

您的 DLL 可能有在 DLL 載入時必須執行的初始化程式代碼。 為了讓您執行自己的 DLL 初始化和終止函式, _DllMainCRTStartup 請呼叫稱為 的函式,以提供該函 DllMain 式。 您必須 DllMain 具有 DLL 進入點所需的簽章。 默認進入點函 _DllMainCRTStartup 式會使用 Windows 所傳遞的相同參數來呼叫 DllMain 。 根據預設,如果您未提供函 DllMain 式,Visual Studio 會為您提供函式,並將它連結在 中, _DllMainCRTStartup 讓一律有要呼叫的內容。 這表示,如果您不需要初始化 DLL,在建置 DLL 時就不需要執行任何特殊動作。

這是 用於 DllMain的簽章:

#include <windows.h>

extern "C" BOOL WINAPI DllMain (
    HINSTANCE const instance,  // handle to DLL module
    DWORD     const reason,    // reason for calling function
    LPVOID    const reserved); // reserved

某些連結庫會為您包裝函 DllMain 式。 例如,在一般 MFC DLL 中,實 CWinApp 作 物件的 InitInstanceExitInstance 成員函式,以執行 DLL 所需的初始化和終止。 如需詳細資訊,請參閱 初始化一般 MFC DLL 一節。

警告

您可以在 DLL 進入點安全地執行哪些動作有重大限制。 如需在 中 DllMain呼叫不安全的特定 Windows API 詳細資訊,請參閱 一般最佳做法。 如果您需要最簡單的初始化以外的任何專案,請在 DLL 的初始化函式中執行此動作。 您可以要求應用程式在執行 之後 DllMain 呼叫初始化函式,以及呼叫 DLL 中任何其他函式之前。

初始化一般 (非 MFC) DLL

若要在使用 VCRuntime 提供的 _DllMainCRTStartup 進入點的一般 (非 MFC) DLL 中執行您自己的初始化,您的 DLL 原始程式碼必須包含稱為 DllMain的函式。 下列程式代碼會呈現基本基本架構,其中顯示 的定義 DllMain 可能看起來像這樣:

#include <windows.h>

extern "C" BOOL WINAPI DllMain (
    HINSTANCE const instance,  // handle to DLL module
    DWORD     const reason,    // reason for calling function
    LPVOID    const reserved)  // reserved
{
    // Perform actions based on the reason for calling.
    switch (reason)
    {
    case DLL_PROCESS_ATTACH:
        // Initialize once for each new process.
        // Return FALSE to fail DLL load.
        break;

    case DLL_THREAD_ATTACH:
        // Do thread-specific initialization.
        break;

    case DLL_THREAD_DETACH:
        // Do thread-specific cleanup.
        break;

    case DLL_PROCESS_DETACH:
        // Perform any necessary cleanup.
        break;
    }
    return TRUE;  // Successful DLL_PROCESS_ATTACH.
}

注意

舊版 Windows SDK 檔指出,必須在連結器命令行上指定 DLL 進入點函式的實際名稱與 /ENTRY 選項。 使用 Visual Studio 時,如果您的進入點函式名稱是 DllMain,則不需要使用 /ENTRY 選項。 事實上,如果您使用 /ENTRY 選項並將進入點函式命名為 以外的 DllMain專案點函式,除非進入點函式進行相同的初始化呼叫, _DllMainCRTStartup 否則CRT不會正確初始化。

初始化一般 MFC DLL

因為一般 MFC DLL 有 CWinApp 物件,所以它們應該在與 MFC 應用程式相同的位置執行其初始化和終止工作:在 InitInstance DLL 衍生類別的 CWinAppExitInstance 成員函式中。 由於 MFC 提供 DllMain 針對 DLL_PROCESS_ATTACHDLL_PROCESS_DETACH呼叫_DllMainCRTStartup的函式,因此您不應該撰寫自己的DllMain函式。 載入 DLL 時,MFC 提供的 DllMain 函式會呼叫 InitInstance ,並在卸除 DLL 之前呼叫 ExitInstance 它。

一般 MFC DLL 可以藉由在其函式中InitInstance呼叫 TlsAllocTlsGetValue 來追蹤多個線程。 這些函式可讓 DLL 追蹤線程特定數據。

在動態連結至 MFC 的一般 MFC DLL 中,如果您使用任何 MFC OLE、MFC 資料庫(或 DAO)或 MFC 套接字支援,則分別支援偵錯 MFC 擴充 DLL MFCO版本D.dll、MFCD版本D.dll,以及 MFCN版本D.dll(其中版本是版本號碼)會自動連結。 您必須針對您在一般 MFC DLL CWinApp::InitInstance中所使用的每個 DLL 呼叫下列其中一個預先定義的初始化函式。

MFC 支援類型 要呼叫的初始化函式
MFC OLE (MFCO版本D.dll) AfxOleInitModule
MFC 資料庫 (MFCD版本D.dll) AfxDbInitModule
MFC 套接字 (MFCN版本D.dll) AfxNetInitModule

初始化 MFC 擴充 DLL

因為 MFC 擴充 DLL 沒有 CWinApp衍生的物件(如同一般 MFC DLL),因此您應該將初始化和終止程式代碼新增至 DllMain MFC DLL 精靈產生的函式。

精靈提供下列 MFC 擴充功能 DLL 的程式代碼。 在程式代碼中, PROJNAME 是專案名稱的佔位元。

#include "pch.h" // For Visual Studio 2017 and earlier, use "stdafx.h"
#include <afxdllx.h>

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
static AFX_EXTENSION_MODULE PROJNAMEDLL;

extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
   if (dwReason == DLL_PROCESS_ATTACH)
   {
      TRACE0("PROJNAME.DLL Initializing!\n");

      // MFC extension DLL one-time initialization
      AfxInitExtensionModule(PROJNAMEDLL,
                                 hInstance);

      // Insert this DLL into the resource chain
      new CDynLinkLibrary(Dll3DLL);
   }
   else if (dwReason == DLL_PROCESS_DETACH)
   {
      TRACE0("PROJNAME.DLL Terminating!\n");
   }
   return 1;   // ok
}

初始化期間建立新的 CDynLinkLibrary 物件可讓MFC擴充 DLL 將對象或資源匯出 CRuntimeClass 至用戶端應用程式。

如果您要從一或多個一或多個一般 MFC DLL 使用 MFC 擴充 DLL,您必須匯出建立 CDynLinkLibrary 物件的初始化函式。 您必須從使用 MFC 擴充 DLL 的每個一般 MFC DLL 呼叫該函式。 呼叫這個初始化函式的適當位置是在一般 MFC DLL CWinApp衍生對象的成員函式中InitInstance,再使用任何 MFC 延伸模組 DLL 的匯出類別或函式。

DllMain在 MFC DLL 精靈產生的 中,呼叫 AfxInitExtensionModule 會擷取模組的運行時間類別(CRuntimeClass結構),以及其對象處理站 (COleObjectFactoryobjects) 在建立物件時CDynLinkLibrary使用。 您應該檢查 的 AfxInitExtensionModule傳回值;如果從 AfxInitExtensionModule傳回零值,則從函 DllMain 式傳回零。

如果您的 MFC 延伸模組 DLL 將明確連結至可執行檔(表示要連結至 DLL 的可執行檔呼叫AfxLoadLibrary),您應該在 上新增 對 的DLL_PROCESS_DETACH呼叫AfxTermExtensionModule。 當每個進程與 MFC 延伸模組 DLL 中斷連結時,此函式可讓 MFC 清除 MFC 擴充 DLL(當進程結束或 DLL 因呼叫而卸除時發生 AfxFreeLibrary )。 如果您的 MFC 延伸模組 DLL 會隱含地連結至應用程式,則不需要呼叫 AfxTermExtensionModule

在釋放 DLL 時,明確連結至 MFC 擴充 DLL 的應用程式必須呼叫 AfxTermExtensionModule 。 如果應用程式使用多個線程,則它們也應該使用 AfxLoadLibraryAfxFreeLibrary (而不是 Win32 函 LoadLibrary 式和 FreeLibrary)。 使用 AfxLoadLibraryAfxFreeLibrary 可確保載入和卸除 MFC 擴充功能 DLL 時執行的啟動和關機程式代碼不會損毀全域 MFC 狀態。

由於MFCx0.dll在呼叫時 DllMain 會完全初始化,因此您可以在 內 DllMain 配置記憶體並呼叫 MFC 函式(不同於 16 位版本的 MFC)。

擴充 DLL 可以藉由處理 DLL_THREAD_ATTACH 函式中的 DllMainDLL_THREAD_DETACH 案例來處理多線程。 當線程附加和中斷連結 DLL 時,會傳遞 DllMain 這些案例。 附加 DLL 時呼叫 TlsAlloc 可讓 DLL 維護連結至 DLL 之每個線程的線程本機記憶體 (TLS) 索引。

請注意,頭檔 Afxdllx.h 包含 MFC 擴展名 DLL 中使用的結構的特殊定義,例如 和 CDynLinkLibrary的定義AFX_EXTENSION_MODULE。 您應該在 MFC 擴充 DLL 中包含此標頭檔。

注意

請務必不要在 pch.h 中定義或取消定義任何巨集Visual Studio 2017 和更早版本中的 _AFX_NO_XXX stdafx.h)。 這些巨集只為了檢查特定目標平臺是否支援該功能而存在。 您可以撰寫程式來檢查這些巨集(例如 #ifndef _AFX_NO_OLE_SUPPORT),但您的程式絕對不應該定義或取消定義這些巨集。

處理多線程的範例初始化函式包含在 Windows SDK 的 Dynamic-Link 連結庫中 使用線程本機記憶體中。 請注意,此範例包含稱為 LibMain的進入點函式,但您應該將此函 DllMain 式命名為 ,讓它與 MFC 和 C 運行時間連結庫搭配運作。

另請參閱

在 Visual Studio 中建立 C++ DLL
DllMain 進入點
動態連結庫最佳做法