Zachowanie biblioteki wykonawczej DLL i Visual C++

Podczas kompilowania biblioteki dynamicznego linku (DLL) przy użyciu programu Visual Studio domyślnie konsolidator zawiera bibliotekę czasu wykonywania visual C++ (VCRuntime). VcRuntime zawiera kod wymagany do zainicjowania i zakończenia pliku wykonywalnego C/C++. Po połączeniu z biblioteką DLL kod VCRuntime udostępnia wewnętrzną funkcję punktu wejścia biblioteki DLL o nazwie _DllMainCRTStartup , która obsługuje komunikaty systemu operacyjnego Windows do biblioteki DLL w celu dołączenia lub odłączenia od procesu lub wątku. Funkcja _DllMainCRTStartup wykonuje podstawowe zadania, takie jak konfigurowanie zabezpieczeń buforu stosu, inicjowanie i kończenie biblioteki czasu wykonywania języka C (CRT) oraz wywoływanie konstruktorów i destruktorów dla obiektów statycznych i globalnych. _DllMainCRTStartup Wywołuje również funkcje punktów zaczepienia dla innych bibliotek, takich jak WinRT, MFC i ATL, aby wykonywać własne inicjowanie i kończenie. Bez tej inicjalizacji CRT i innych bibliotek, a także zmiennych statycznych, pozostanie w stanie niezainicjowanym. Te same wewnętrzne procedury inicjowania i kończenia VCRuntime są wywoływane, czy biblioteka DLL używa statycznie połączonego CRT, czy dynamicznie połączonej biblioteki DLL CRT.

Domyślny punkt wejścia biblioteki DLL _DllMainCRTStartup

W systemie Windows wszystkie biblioteki DLL mogą zawierać opcjonalną funkcję punktu wejścia, zwykle nazywaną DllMain, która jest wywoływana dla inicjowania i kończenia. Dzięki temu można przydzielić lub zwolnić dodatkowe zasoby zgodnie z potrzebami. System Windows wywołuje funkcję punktu wejścia w czterech sytuacjach: dołączanie procesów, odłączanie procesów, dołączanie wątków i odłączanie wątków. Gdy biblioteka DLL jest ładowana do przestrzeni adresowej procesu, gdy aplikacja korzystająca z niej jest ładowana lub gdy aplikacja żąda biblioteki DLL w czasie wykonywania, system operacyjny tworzy oddzielną kopię danych DLL. Jest to nazywane dołączaniem procesów. Dołączanie wątku występuje, gdy proces ładowania biblioteki DLL w programie tworzy nowy wątek. Odłączanie wątku występuje, gdy wątek kończy się, a odłączanie procesu jest wtedy, gdy biblioteka DLL nie jest już wymagana i jest zwalniana przez aplikację. System operacyjny wykonuje oddzielne wywołanie punktu wejścia biblioteki DLL dla każdego z tych zdarzeń, przekazując argument przyczyny dla każdego typu zdarzenia. Na przykład system operacyjny wysyła DLL_PROCESS_ATTACH jako argument przyczyny dołączenia procesu.

Biblioteka VCRuntime udostępnia funkcję punktu wejścia wywoływaną _DllMainCRTStartup w celu obsługi domyślnych operacji inicjowania i kończenia. Podczas dołączania _DllMainCRTStartup procesu funkcja konfiguruje kontrole zabezpieczeń buforu, inicjuje CRT i inne biblioteki, inicjuje informacje o typie czasu wykonywania, inicjuje i wywołuje konstruktory danych statycznych i nielokalnych, inicjuje magazyn lokalny wątków, zwiększa wewnętrzny licznik statyczny dla każdego dołączania, a następnie wywołuje element dostarczany DllMainprzez użytkownika lub bibliotekę. Po odłączeniu procesu funkcja przechodzi przez te kroki w odwrotnym kroku. Wywołuje DllMainmetodę , dekrementuje wewnętrzny licznik, wywołuje destruktory, wywołuje funkcje zakończenia CRT i zarejestrowane atexit funkcje i powiadamia wszystkie inne biblioteki zakończenia. Gdy licznik załączników przechodzi do zera, funkcja powraca FALSE do systemu Windows, że można zwolnić bibliotekę DLL. Funkcja jest również wywoływana _DllMainCRTStartup podczas dołączania wątku i odłączania wątku. W takich przypadkach kod VCRuntime nie wykonuje dodatkowej inicjalizacji ani zakończenia samodzielnie i po prostu wywołuje DllMain polecenie , aby przekazać komunikat. Jeśli DllMain zwracane FALSE z dołączania procesu, sygnalizujące niepowodzenie, _DllMainCRTStartup wywołuje DllMain ponownie i przekazuje DLL_PROCESS_DETACH jako argument przyczyny , następnie przechodzi przez pozostałą część procesu zakończenia.

Podczas kompilowania bibliotek DLL w programie Visual Studio domyślny punkt _DllMainCRTStartup wejścia dostarczony przez vcRuntime jest połączony automatycznie. Nie trzeba określać funkcji punktu wejścia dla biblioteki DLL przy użyciu opcji konsolidatora /ENTRY (symbol punktu wejścia).

Uwaga

Chociaż istnieje możliwość określenia innej funkcji punktu wejścia dla biblioteki DLL przy użyciu /ENTRY: opcji konsolidatora, nie zalecamy jej, ponieważ funkcja punktu wejścia musiałaby zduplikować wszystko, co _DllMainCRTStartup robi, w tej samej kolejności. VcRuntime udostępnia funkcje, które umożliwiają duplikowanie jego zachowania. Możesz na przykład wywołać __security_init_cookie natychmiast przy dołączaniu procesu w celu obsługi opcji sprawdzania buforu /GS (sprawdzanie zabezpieczeń buforu). Funkcję można wywołać _CRT_INIT , przekazując te same parametry co funkcja punktu wejścia, aby wykonać pozostałe funkcje inicjowania lub kończenia bibliotek DLL.

Inicjowanie biblioteki DLL

Biblioteka DLL może mieć kod inicjowania, który musi zostać wykonany po załadowaniu biblioteki DLL. Aby wykonać własne funkcje inicjowania i kończenia bibliotek DLL, _DllMainCRTStartup wywołuje funkcję o nazwie DllMain , którą można podać. Musisz DllMain mieć podpis wymagany dla punktu wejścia biblioteki DLL. Domyślna funkcja _DllMainCRTStartup punktu wejścia wywołuje DllMain przy użyciu tych samych parametrów przekazywanych przez system Windows. Domyślnie, jeśli nie udostępniasz DllMain funkcji, program Visual Studio udostępnia go i łączy go w taki sposób, aby _DllMainCRTStartup zawsze miał coś do wywołania. Oznacza to, że jeśli nie musisz inicjować biblioteki DLL, podczas kompilowania biblioteki DLL nie trzeba wykonywać żadnych specjalnych czynności.

Jest to podpis używany dla elementu 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

Niektóre biblioteki opakowuje DllMain funkcję. Na przykład w regularnej biblioteki MFC DLL zaimplementuj CWinApp funkcje obiektu InitInstance i ExitInstance składowe, aby wykonać inicjowanie i zakończenie wymagane przez bibliotekę DLL. Aby uzyskać więcej informacji, zobacz sekcję Inicjowanie zwykłych bibliotek DLL MFC.

Ostrzeżenie

Istnieją znaczące ograniczenia dotyczące tego, co można bezpiecznie zrobić w punkcie wejścia biblioteki DLL. Aby uzyskać więcej informacji na temat określonych interfejsów API systemu Windows, które są niebezpieczne do wywołania w systemie DllMain, zobacz Ogólne najlepsze rozwiązania. Jeśli potrzebujesz niczego, ale najprostszego inicjowania, wykonaj to w funkcji inicjowania dla biblioteki DLL. Aplikacje mogą wymagać wywołania funkcji inicjowania po DllMain uruchomieniu i przed wywołaniem innych funkcji w bibliotece DLL.

Inicjowanie zwykłych bibliotek DLL (innych niż MFC)

Aby wykonać własną inicjację w zwykłych bibliotekach DLL (innych niż MFC), które używają punktu wejścia dostarczonego _DllMainCRTStartup przez vcRuntime, kod źródłowy biblioteki DLL musi zawierać funkcję o nazwie DllMain. Poniższy kod przedstawia podstawowy szkielet pokazujący, jak może wyglądać definicja 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.
}

Uwaga

Starsza dokumentacja zestawu Windows SDK mówi, że rzeczywista nazwa funkcji punktu wejścia biblioteki DLL musi być określona w wierszu polecenia konsolidatora z opcją /ENTRY. W programie Visual Studio nie trzeba używać opcji /ENTRY, jeśli nazwa funkcji punktu wejścia to DllMain. W rzeczywistości, jeśli używasz /ENTRY opcji i nazwij funkcję punktu wejścia coś innego niż DllMain, CRT nie zostanie zainicjowane poprawnie, chyba że funkcja punktu wejścia wykonuje te same wywołania inicjowania, które _DllMainCRTStartup wykonuje.

Inicjowanie zwykłych bibliotek DLL MFC

Ponieważ zwykłe biblioteki DLL MFC mają CWinApp obiekt, powinny wykonywać zadania inicjowania i kończenia w tej samej lokalizacji co aplikacja MFC: w InitInstance funkcjach składowych i ExitInstance klasy pochodnej biblioteki DLL CWinApp. Ponieważ MFC udostępnia funkcję wywoływaną DllMain przez _DllMainCRTStartup element i DLL_PROCESS_DETACHDLL_PROCESS_ATTACH , nie należy pisać własnej DllMain funkcji. Funkcja udostępniona DllMain przez MFC wywołuje funkcję InitInstance podczas ładowania biblioteki DLL i wywołuje ExitInstance ją przed zwolnieniem biblioteki DLL.

Zwykła biblioteka MFC DLL może śledzić wiele wątków, wywołując w funkcji InitInstance tlsAlloc i TlsGetValue. Te funkcje umożliwiają biblioteki DLL śledzenie danych specyficznych dla wątku.

W regularnej biblioteki MFC DLL, która dynamicznie łączy się z MFC, jeśli używasz dowolnego MFC OLE, bazy danych MFC (lub DAO) lub obsługi gniazd MFC odpowiednio debugowania biblioteki DLL rozszerzenia MFC MFCOw wersjiD.dll, wersji MFCDd.dll i MFCNw wersjiD.dll (gdzie wersja jest numerem wersji) są połączone automatycznie. Należy wywołać jedną z następujących wstępnie zdefiniowanych funkcji inicjowania dla każdej z tych bibliotek DLL, które są używane w zwykłych bibliotekach MFC DLL CWinApp::InitInstance.

Typ obsługi MFC Funkcja inicjowania do wywoływania
MFC OLE (wersja D.dll MFCO) AfxOleInitModule
Baza danych MFC (wersja MFCDD.dll) AfxDbInitModule
MFC Sockets (wersja MFCND.dll) AfxNetInitModule

Inicjowanie bibliotek DLL rozszerzeń MFC

Ponieważ biblioteki DLL rozszerzeń MFC nie mają obiektu pochodnego CWinApp(podobnie jak zwykłe biblioteki MFC DLL), należy dodać kod inicjowania i zakończenia do DllMain funkcji generowanej przez Kreatora biblioteki MFC DLL.

Kreator udostępnia następujący kod dla bibliotek DLL rozszerzeń MFC. W kodzie PROJNAME jest symbolem zastępczym nazwy projektu.

#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
}

Utworzenie nowego CDynLinkLibrary obiektu podczas inicjowania umożliwia biblioteki DLL rozszerzenia MFC eksportowanie CRuntimeClass obiektów lub zasobów do aplikacji klienckiej.

Jeśli zamierzasz użyć biblioteki DLL rozszerzenia MFC z co najmniej jednej regularnej biblioteki MFC DLL, musisz wyeksportować funkcję inicjowania, która tworzy CDynLinkLibrary obiekt. Ta funkcja musi być wywoływana z każdej z zwykłych bibliotek DLL MFC korzystających z biblioteki DLL rozszerzenia MFC. Odpowiednim miejscem do wywołania tej funkcji inicjowania jest InitInstance funkcja składowa zwykłego obiektu pochodnego biblioteki MFC DLL przed użyciem dowolnej z wyeksportowanych klas lub funkcji biblioteki DLL CWinApprozszerzenia MFC.

W kreatorze DllMain biblioteki MFC DLL jest generowane wywołanie AfxInitExtensionModule przechwytywania klas czasu wykonywania (struktur) modułu,CRuntimeClass a także jego fabryk obiektów (COleObjectFactory obiektów) do użycia podczas CDynLinkLibrary tworzenia obiektu. Należy sprawdzić wartość AfxInitExtensionModulezwracaną przez parametr ; jeśli zwracana jest wartość zero z AfxInitExtensionModule, zwraca zero z DllMain funkcji.

Jeśli biblioteka DLL rozszerzenia MFC zostanie jawnie połączona z plikiem wykonywalnym (co oznacza, że wywołania AfxLoadLibrary pliku wykonywalnego w celu połączenia z biblioteką DLL), należy dodać wywołanie metody w dniu AfxTermExtensionModuleDLL_PROCESS_DETACH. Ta funkcja umożliwia MFC czyszczenie biblioteki DLL rozszerzenia MFC, gdy każdy proces odłącza się od biblioteki DLL rozszerzenia MFC (co ma miejsce, gdy proces zakończy się lub gdy biblioteka DLL zostanie zwolniona w wyniku AfxFreeLibrary wywołania). Jeśli biblioteka DLL rozszerzenia MFC zostanie połączona niejawnie z aplikacją, wywołanie AfxTermExtensionModule metody nie jest konieczne.

Aplikacje, które jawnie łączą się z bibliotekami DLL rozszerzeń MFC, muszą wywoływać AfxTermExtensionModule podczas zwalniania biblioteki DLL. Powinny również używać AfxLoadLibrary funkcji i AfxFreeLibrary (zamiast funkcji LoadLibrary Win32 i FreeLibrary), jeśli aplikacja używa wielu wątków. Użycie AfxLoadLibrary i AfxFreeLibrary gwarantuje, że kod uruchamiania i zamykania, który jest wykonywany, gdy biblioteka DLL rozszerzenia MFC jest ładowana i zwalniana, nie powoduje uszkodzenia globalnego stanu MFC.

Ponieważ biblioteka MFCx0.dll jest w pełni inicjowana w czasie DllMain , można przydzielić pamięć i wywołać funkcje MFC w programie DllMain (w przeciwieństwie do 16-bitowej wersji MFC).

Biblioteki DLL rozszerzeń mogą obsługiwać wielowątkowość, obsługując DLL_THREAD_ATTACH przypadki i DLL_THREAD_DETACH w DllMain funkcji . Te przypadki są przekazywane, DllMain gdy wątki dołączają i odłączają się od biblioteki DLL. Wywoływanie metody TlsAlloc , gdy dołączana biblioteka DLL umożliwia biblioteki DLL obsługę indeksów magazynu lokalnego wątku (TLS) dla każdego wątku dołączonego do biblioteki DLL.

Należy pamiętać, że plik nagłówka Afxdllx.h zawiera specjalne definicje struktur używanych w bibliotekach DLL rozszerzeń MFC, takich jak definicja i AFX_EXTENSION_MODULECDynLinkLibrary. Ten plik nagłówka należy dołączyć do biblioteki DLL rozszerzenia MFC.

Uwaga

Ważne jest, aby nie definiować ani nie definiować żadnych _AFX_NO_XXX makr w pliku pch.h (stdafx.h w programie Visual Studio 2017 i starszych). Te makra istnieją tylko w celu sprawdzenia, czy dana platforma docelowa obsługuje tę funkcję, czy nie. Możesz napisać program, aby sprawdzić te makra (na przykład #ifndef _AFX_NO_OLE_SUPPORT), ale program nigdy nie powinien definiować ani nie definiować tych makr.

Przykładowa funkcja inicjowania, która obsługuje wielowątkowość, jest zawarta w temacie Using Thread Local Storage in a Dynamic-Link Library in the Windows SDK (Używanie magazynu lokalnego wątku w bibliotece dynamicznego linku w zestawie SDK systemu Windows). Należy pamiętać, że przykład zawiera funkcję punktu wejścia o nazwie LibMain, ale należy nazwać tę funkcję DllMain tak, aby współdziałała z bibliotekami MFC i C czasu wykonywania.

Zobacz też

Tworzenie bibliotek DLL języka C/C++ w programie Visual Studio
Punkt wejścia DllMain
Najlepsze rozwiązania dotyczące biblioteki łączy dynamicznych