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 DllMain
przez użytkownika lub bibliotekę. Po odłączeniu procesu funkcja przechodzi przez te kroki w odwrotnym kroku. Wywołuje DllMain
metodę , 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_DETACH
DLL_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 standardowej biblioteki MFC DLL, która dynamicznie łączy się z MFC, jeśli korzystasz z dowolnego MFC OLE, bazy danych MFC (lub DAO) lub obsługi gniazd MFC, odpowiednio, debugowanie bibliotek DLL rozszerzenia MFC MFCOw wersjiD.dll, wersji MFCDD.dll i wersji MFCND.dll (gdzie wersja jest numeremwersji) 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 MFCOD.dll) | AfxOleInitModule |
Baza danych MFC (wersja MFCDD.dll) | AfxDbInitModule |
MFC Sockets (wersjaMFCND.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 CWinApp
rozszerzenia 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ść AfxInitExtensionModule
zwracaną 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 AfxTermExtensionModule
DLL_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ż MFCx0.dll jest w pełni inicjowany przez czas 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_MODULE
CDynLinkLibrary
. 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