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.dllktó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 pliku delayhlp.cpp, w katalogu MSVC include . 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 polecenia DUMPBIN /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.cpppliku 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 , CheckDelayExceptionw 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 przy GetModuleHandle użyciu polecenia po zapoznaniu się z tym, że pomocnik opóźnionego ładowania załadował bibliotekę DLL) i GetProcAddress.

  • 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 metody LoadLibrary. Dynamiczne protokoły TLS, korzystające z TlsAllocbibliotek , , TlsFreeTlsGetValuei TlsSetValue, 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