Obsługa konsolidatora dla bibliotek DLL załadowanych z opóźnieniem
Konsolidator MSVC obsługuje opóźnione ładowanie bibliotek DLL. Ta funkcja zwalnia z konieczności używania funkcji LoadLibrary
zestawu Windows SDK i GetProcAddress
implementowania opóźnionego ładowania bibliotek DLL.
Bez opóźnionego ładowania jedynym sposobem załadowania biblioteki DLL w czasie wykonywania jest użycie polecenia LoadLibrary
i GetProcAddress
; system operacyjny ładuje bibliotekę DLL, gdy plik wykonywalny lub biblioteka DLL przy jego użyciu zostanie załadowany.
W przypadku opóźnionego ładowania, gdy niejawnie łączysz bibliotekę DLL, konsolidator udostępnia opcje opóźnienia ładowania bibliotek DLL, dopóki program nie wywołuje funkcji w tej bibliotece DLL.
Aplikacja może opóźnić ładowanie biblioteki DLL przy użyciu /DELAYLOAD
opcji konsolidatora Opóźnij ładowanie z funkcją pomocnika. (Domyślna implementacja funkcji pomocnika jest dostarczana przez firmę Microsoft). Funkcja pomocnika ładuje bibliotekę DLL na żądanie w czasie wykonywania, wywołując LoadLibrary
polecenie i GetProcAddress
za Ciebie.
Rozważ opóźnienie ładowania biblioteki DLL, jeśli:
Program może nie wywoływać funkcji w bibliotece DLL.
Funkcja w bibliotece DLL może nie zostać wywołana dopiero pod koniec wykonywania programu.
Opóźnione ładowanie biblioteki DLL można określić podczas kompilacji projektu EXE lub DLL. Projekt dll, który opóźnia ładowanie co najmniej jednej biblioteki DLL, nie powinien wywoływać punktu wejścia ładowanego z opóźnieniem w obiekcie DllMain
.
Określanie bibliotek DLL w celu opóźnienia ładowania
Można określić, które biblioteki DLL opóźniają ładowanie przy użyciu opcji konsolidatora /delayload:
dllname
. Jeśli nie planujesz używania własnej wersji funkcji pomocniczej, musisz również połączyć program z delayimp.lib
(w przypadku aplikacji klasycznych) lub dloadhelper.lib
(w przypadku aplikacji platformy UWP).
Oto prosty przykład opóźnienia ładowania biblioteki 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);
}
Skompiluj wersję DEBUG projektu. Przejdź przez kod przy użyciu debugera i zauważysz, że user32.dll
jest ładowany tylko wtedy, gdy wykonasz wywołanie metody MessageBox
.
Jawne zwalnianie biblioteki DLL załadowanej z opóźnieniem
Opcja /delay:unload
konsolidatora pozwala kodowi jawnie zwolnić bibliotekę DLL, która została załadowana z opóźnieniem. Domyślnie import ładowane z opóźnieniem pozostają w tabeli adresów importu (IAT). Jeśli jednak używasz /delay:unload
w wierszu polecenia konsolidatora, funkcja pomocnika obsługuje jawne zwalnianie biblioteki DLL przez __FUnloadDelayLoadedDLL2
wywołanie i resetuje IAT do oryginalnego formularza. Teraz nieprawidłowe wskaźniki są zastępowane. IAT jest polem w ImgDelayDescr
strukturze zawierającej adres kopii oryginalnego IAT, jeśli istnieje.
Przykład zwalniania biblioteki DLL załadowanej z opóźnieniem
W tym przykładzie pokazano, jak jawnie zwolnić bibliotekę DLL, MyDll.dll
która zawiera funkcję fnMyDll
:
// 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");
}
Ważne uwagi dotyczące zwalniania biblioteki DLL ładowanej z opóźnieniem:
Implementację
__FUnloadDelayLoadedDLL2
funkcji można znaleźć w plikudelayhlp.cpp
, w katalogu MSVCinclude
. Aby uzyskać więcej informacji, zobacz Understand the delay load helper function (Omówienie funkcji pomocnika opóźniania ładowania).Parametr
name
__FUnloadDelayLoadedDLL2
funkcji musi dokładnie odpowiadać (w tym wielkości liter) zawartej w bibliotece importu. (Ten ciąg znajduje się również w tabeli importu na obrazie). Zawartość biblioteki importu można wyświetlić przy użyciu poleceniaDUMPBIN /DEPENDENTS
. Jeśli wolisz dopasowanie ciągu bez uwzględniania wielkości liter, możesz przeprowadzić aktualizację__FUnloadDelayLoadedDLL2
, aby użyć jednej z funkcji ciągów CRT bez uwzględniania wielkości liter lub wywołania interfejsu API systemu Windows.
Powiązywanie importów załadowanych z opóźnieniem
Domyślne zachowanie konsolidatora polega na utworzeniu powiązanej tabeli adresów importu (IAT) dla biblioteki DLL ładowanej z opóźnieniem. Jeśli biblioteka DLL jest powiązana, funkcja pomocnika próbuje użyć powiązanych informacji zamiast wywoływania GetProcAddress
dla każdego z przywoływałych importów. Jeśli znacznik czasu lub preferowany adres nie jest zgodny z adresem w załadowanej bibliotece DLL, funkcja pomocnika zakłada, że powiązana tabela adresów importu jest nieaktualna. Działa tak, jakby IAT nie istnieje.
Jeśli nigdy nie zamierzasz powiązać importów biblioteki DLL załadowanych z opóźnieniem, określ /delay:nobind
polecenie w wierszu polecenia konsolidatora. Konsolidator nie wygeneruje powiązanej tabeli adresów importu, co pozwala zaoszczędzić miejsce w pliku obrazu.
Ładowanie wszystkich importów dla biblioteki DLL załadowanej z opóźnieniem
Funkcja zdefiniowana __HrLoadAllImportsForDll
w delayhlp.cpp
pliku informuje konsolidatora o załadowaniu wszystkich importów z biblioteki DLL określonej za pomocą opcji konsolidatora /delayload
.
Podczas ładowania wszystkich importów jednocześnie można scentralizować obsługę błędów w jednym miejscu. Można uniknąć obsługi wyjątków strukturalnych wokół wszystkich rzeczywistych wywołań do importów. Pozwala to również uniknąć sytuacji, w której aplikacja kończy się niepowodzeniem w trakcie procesu: na przykład jeśli kod pomocnika nie może załadować importu, po pomyślnym załadowaniu innych osób.
Wywołanie __HrLoadAllImportsForDll
nie zmienia zachowania haków i obsługi błędów. Aby uzyskać więcej informacji, zobacz Obsługa błędów i powiadamianie.
__HrLoadAllImportsForDll
sprawia, że uwzględniana wielkość liter jest porównywana z nazwą przechowywaną wewnątrz samej biblioteki DLL.
Oto przykład, który używa __HrLoadAllImportsForDll
funkcji wywoływanej TryDelayLoadAllImports
do próby załadowania nazwanej biblioteki DLL. Używa funkcji , CheckDelayException
w celu określenia zachowania wyjątku.
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;
}
Możesz użyć wyniku TryDelayLoadAllImports
, aby kontrolować, czy wywołujesz funkcje importu, czy nie.
Obsługa błędów oraz powiadomienia
Jeśli program używa bibliotek DLL ładowanych z opóźnieniem, musi obsługiwać błędy niezawodnie. Błędy występujące podczas uruchamiania programu spowodują nieobsługiwane wyjątki. Aby uzyskać więcej informacji na temat obsługi błędów opóźnienia ładowania bibliotek DLL i powiadomień, zobacz Obsługa błędów i powiadomienia.
Importowanie ładowane z opóźnieniem zrzutu
Import ładowany z opóźnieniem może być po cenach dumpingowych przy użyciu polecenia DUMPBIN /IMPORTS
. Importy te zawierają nieco inne informacje niż standardowe importy. Są one podzielone na własną sekcję /imports
listy i są jawnie oznaczone jako importy ładowane z opóźnieniem. Jeśli na obrazie znajdują się informacje o zwalnianiu, jest to zanotowany. Jeśli istnieją informacje dotyczące powiązania, zostanie zanotowany sygnatura czasowa i data docelowej biblioteki DLL wraz z powiązanymi adresami importów.
Ograniczenia dotyczące bibliotek DLL z opóźnieniem ładowania
Istnieje kilka ograniczeń związanych z opóźnieniem ładowania importów bibliotek DLL.
Importowanie danych nie może być obsługiwane. Obejściem jest jawne obsłużenie importowania danych przy użyciu metody
LoadLibrary
(lub przyGetModuleHandle
użyciu polecenia po zapoznaniu się z tym, że pomocnik opóźnionego ładowania załadował bibliotekę DLL) iGetProcAddress
.Opóźnienie ładowania
Kernel32.dll
nie jest obsługiwane. Ta biblioteka DLL musi zostać załadowana, aby procedury pomocnika opóźniania obciążenia działały.Powiązanie przekazanych punktów wejścia nie jest obsługiwane.
Proces może mieć inne zachowanie, jeśli biblioteka DLL jest ładowana z opóźnieniem, a nie ładowana podczas uruchamiania. Można zobaczyć, czy w punkcie wejścia biblioteki DLL ładowanej z opóźnieniem występują inicjacje poszczególnych procesów. Inne przypadki obejmują statyczny protokół TLS (magazyn lokalny wątku), zadeklarowany przy użyciu metody
__declspec(thread)
, która nie jest obsługiwana, gdy biblioteka DLL jest ładowana za pośrednictwem metodyLoadLibrary
. Dynamiczne protokoły TLS, korzystające zTlsAlloc
bibliotek , ,TlsFree
TlsGetValue
iTlsSetValue
, są nadal dostępne do użycia w statycznych lub ładowanych z opóźnieniem bibliotekach DLL.Zainicjuj statyczne wskaźniki funkcji globalnej do zaimportowanych funkcji po pierwszym wywołaniu każdej funkcji. Jest to wymagane, ponieważ pierwsze użycie wskaźnika funkcji wskazuje thunk, a nie załadowaną funkcję.
Obecnie nie ma możliwości opóźnienia ładowania tylko określonych procedur z biblioteki DLL podczas korzystania z normalnego mechanizmu importu.
Niestandardowe konwencje wywoływania (takie jak używanie kodów warunku w architekturach x86) nie są obsługiwane. Ponadto rejestry zmiennoprzecinkowe nie są zapisywane na żadnej platformie. Należy zachować ostrożność, jeśli niestandardowa rutyna pomocnika lub procedury zaczepienia używają typów zmiennoprzecinkowych: Procedury muszą zapisywać i przywracać pełny stan zmiennoprzecinkowy na maszynach, które używają konwencji wywoływania rejestru z parametrami zmiennoprzecinkowych. Należy zachować ostrożność podczas ładowania biblioteki DLL CRT, szczególnie w przypadku wywoływania funkcji CRT, które przyjmują parametry zmiennoprzecinkowe na stosie procesora danych liczbowych (NDP) w funkcji pomocy.
Omówienie funkcji pomocnika opóźniania ładowania
Funkcja pomocnika do ładowania opóźnionego obsługiwanego przez konsolidatora jest tym, co faktycznie ładuje bibliotekę DLL w czasie wykonywania. Możesz zmodyfikować funkcję pomocnika, aby dostosować jej zachowanie. Zamiast używać dostarczonej funkcji pomocniczej w programie delayimp.lib
, napisz własną funkcję i połącz ją z programem. Jedna funkcja pomocnika obsługuje wszystkie opóźnione biblioteki DLL. Aby uzyskać więcej informacji, zobacz Understand the delay load helper function and Develop your own helper function (Omówienie funkcji pomocnika opóźnienia) i Develop your own helper function (Opracowywanie własnej funkcji pomocniczej).
Zobacz też
Tworzenie bibliotek DLL języka C/C++ w programie Visual Studio
Dokumentacja konsolidatora MSVC