Supporto per le DLL a caricamento ritardato nel linker

Il linker MSVC supporta il caricamento ritardato delle DLL. Questa funzionalità consente di ridurre la necessità di usare le funzioni LoadLibrary di Windows SDK e GetProcAddress di implementare il caricamento ritardato della DLL.

Senza caricamento ritardato, l'unico modo per caricare una DLL in fase di esecuzione consiste nell'usare LoadLibrary e GetProcAddress. Il sistema operativo carica la DLL quando viene caricato il file eseguibile o la DLL.

Con il caricamento ritardato, quando si collega in modo implicito una DLL, il linker fornisce opzioni per ritardare il caricamento della DLL fino a quando il programma non chiama una funzione in tale DLL.

Un'applicazione può ritardare il caricamento di una DLL usando l'opzione /DELAYLOAD del linker (Importazione caricamento ritardata) con una funzione helper. Un'implementazione predefinita della funzione helper viene fornita da Microsoft. La funzione helper carica la DLL su richiesta in fase di esecuzione chiamando LoadLibrary e GetProcAddress per l'utente.

Valutare il ritardo del caricamento di una DLL se:

  • Il programma potrebbe non chiamare una funzione nella DLL.

  • Una funzione nella DLL potrebbe non essere chiamata fino alla fine dell'esecuzione del programma.

Il caricamento ritardato di una DLL può essere specificato durante la compilazione di un progetto EXE o DLL. Un progetto DLL che ritarda il caricamento di una o più DLL stesse non deve chiamare un punto di ingresso caricato in ritardo in DllMain.

Specificare dll per ritardare il caricamento

È possibile specificare le DLL da ritardare il caricamento usando l'opzione /delayload:dllname del linker. Se non prevedi di usare la tua versione di una funzione helper, devi anche collegare il programma con delayimp.lib (per le applicazioni desktop) o dloadhelper.lib (per le app UWP).

Ecco un semplice esempio di caricamento ritardato di una 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);
}

Compilare la versione DEBUG del progetto. Eseguire il codice usando il debugger e si noterà che user32.dll viene caricato solo quando si effettua la chiamata a MessageBox.

Scaricare in modo esplicito una DLL caricata in ritardo

L'opzione /delay:unload del linker consente al codice di scaricare in modo esplicito una DLL che è stata caricata in ritardo. Per impostazione predefinita, le importazioni caricate in ritardo rimangono nella tabella degli indirizzi di importazione (IAT). Tuttavia, se si usa /delay:unload nella riga di comando del linker, la funzione helper supporta lo scaricamento esplicito della DLL da una __FUnloadDelayLoadedDLL2 chiamata e reimposta l'IAT sul relativo formato originale. I puntatori non validi vengono sovrascritti. L'IAT è un campo nella ImgDelayDescr struttura che contiene l'indirizzo di una copia dell'IAT originale, se presente.

Esempio di scaricamento di una DLL con caricamento ritardato

In questo esempio viene illustrato come scaricare in modo esplicito una DLL, MyDll.dll, che contiene una funzione 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");
}

Note importanti sullo scaricamento di una DLL con caricamento ritardato:

  • È possibile trovare l'implementazione della __FUnloadDelayLoadedDLL2 funzione nel file delayhlp.cpp, nella directory MSVC include . Per altre informazioni, vedere Informazioni sulla funzione helper di caricamento ritardato.

  • Il name parametro della __FUnloadDelayLoadedDLL2 funzione deve corrispondere esattamente (incluso il case) che contiene la libreria di importazione. Tale stringa si trova anche nella tabella di importazione nell'immagine. È possibile visualizzare il contenuto della libreria di importazione usando DUMPBIN /DEPENDENTS. Se preferisci una corrispondenza di stringa senza distinzione tra maiuscole e minuscole, puoi eseguire l'aggiornamento __FUnloadDelayLoadedDLL2 per usare una delle funzioni stringa CRT senza distinzione tra maiuscole e minuscole o una chiamata API di Windows.

Associare le importazioni caricate in ritardo

Il comportamento predefinito del linker consiste nel creare una tabella di indirizzi di importazione associabile (IAT) per la DLL caricata in ritardo. Se la DLL è associata, la funzione helper tenta di usare le informazioni associate anziché chiamare GetProcAddress su ognuna delle importazioni a cui viene fatto riferimento. Se il timestamp o l'indirizzo preferito non corrisponde a quello nella DLL caricata, la funzione helper presuppone che la tabella degli indirizzi di importazione associata non sia aggiornata. Procede come se l'IAT non esiste.

Se non si intende mai associare le importazioni caricate in ritardo di una DLL, specificare /delay:nobind nella riga di comando del linker. Il linker non genererà la tabella degli indirizzi di importazione associata, che consente di risparmiare spazio nel file di immagine.

Caricare tutte le importazioni per una DLL caricata in ritardo

La __HrLoadAllImportsForDll funzione, definita in delayhlp.cpp, indica al linker di caricare tutte le importazioni da una DLL specificata con l'opzione /delayload del linker.

Quando si caricano tutte le importazioni contemporaneamente, è possibile centralizzare la gestione degli errori in un'unica posizione. È possibile evitare la gestione strutturata delle eccezioni in tutte le chiamate effettive alle importazioni. Evita inoltre una situazione in cui l'applicazione ha esito negativo in un processo: ad esempio, se il codice helper non riesce a caricare un'importazione, dopo il caricamento di altri utenti.

La chiamata __HrLoadAllImportsForDll non modifica il comportamento degli hook e della gestione degli errori. Per altre informazioni, vedere Gestione degli errori e notifica.

__HrLoadAllImportsForDll esegue un confronto con distinzione tra maiuscole e minuscole con il nome archiviato all'interno della DLL stessa.

Ecco un esempio che usa __HrLoadAllImportsForDll in una funzione chiamata TryDelayLoadAllImports per tentare di caricare una DLL denominata. Usa una funzione , CheckDelayException, per determinare il comportamento dell'eccezione.

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

È possibile usare il risultato di TryDelayLoadAllImports per controllare se si chiamano o meno le funzioni di importazione.

Gestione e notifica degli errori

Se il programma usa DLL caricate in ritardo, deve gestire gli errori in modo affidabile. Gli errori che si verificano durante l'esecuzione del programma genereranno eccezioni non gestite. Per altre informazioni sulla gestione e la notifica degli errori di caricamento ritardato della DLL, vedere Gestione e notifica degli errori.

Dump delle importazioni caricate in ritardo

Le importazioni caricate in ritardo possono essere scaricate tramite DUMPBIN /IMPORTS. Queste importazioni vengono visualizzate con informazioni leggermente diverse rispetto alle importazioni standard. Sono separati nella propria sezione dell'elenco /imports e vengono etichettati in modo esplicito come importazioni con caricamento ritardato. Se nell'immagine sono presenti informazioni sullo scaricamento, viene indicato. Se sono presenti informazioni di associazione, l'indicatore di data e ora della DLL di destinazione viene annotato insieme agli indirizzi associati delle importazioni.

Vincoli sulle DLL di caricamento ritardato

Esistono diversi vincoli per il caricamento ritardato delle importazioni DLL.

  • Le importazioni di dati non possono essere supportate. Una soluzione alternativa consiste nell'gestire in modo esplicito l'importazione dei dati usando (o usando LoadLibraryGetModuleHandle dopo aver appreso che l'helper di caricamento ritardato ha caricato la DLL) e GetProcAddress.

  • Il caricamento ritardato Kernel32.dll non è supportato. Questa DLL deve essere caricata per il funzionamento delle routine helper di caricamento ritardato.

  • L'associazione di punti di ingresso inoltrati non è supportata.

  • Un processo può avere un comportamento diverso se una DLL viene caricata in ritardo, anziché caricata all'avvio. Può essere visto se sono presenti inizializzazioni per processo che si verificano nel punto di ingresso della DLL caricata in ritardo. Altri casi includono TLS statico (archiviazione locale thread), dichiarato tramite __declspec(thread), che non viene gestito quando la DLL viene caricata tramite LoadLibrary. La memoria locale di thread dinamica, che usa TlsAlloc, TlsFree, TlsGetValue e TlsSetValue, può essere comunque usata per DLL statiche e DLL a caricamento ritardato.

  • Reinizializzare i puntatori a funzioni globali statiche alle funzioni importate dopo la prima chiamata di ogni funzione. Questa operazione è necessaria perché il primo uso di un puntatore a funzione punta al thunk, non alla funzione caricata.

  • Attualmente non è possibile ritardare il caricamento di solo procedure specifiche da una DLL usando il normale meccanismo di importazione.

  • Le convenzioni di chiamata personalizzate (ad esempio l'uso di codici di condizione nelle architetture x86) non sono supportate. Inoltre, i registri a virgola mobile non vengono salvati in alcuna piattaforma. Prestare attenzione se la routine o le routine hook personalizzate dell'helper usano tipi a virgola mobile: le routine devono salvare e ripristinare lo stato completo a virgola mobile nei computer che usano convenzioni di chiamata di registrazione con parametri a virgola mobile. Prestare attenzione al caricamento ritardato della DLL CRT, in particolare se si chiamano funzioni CRT che accettano parametri a virgola mobile in uno stack NDP (Numeric Data Processor) nella funzione della Guida.

Informazioni sulla funzione helper di caricamento ritardato

La funzione helper per il caricamento ritardato supportato dal linker è ciò che carica effettivamente la DLL in fase di esecuzione. È possibile modificare la funzione helper per personalizzarne il comportamento. Anziché usare la funzione helper fornita in delayimp.lib, scrivere la propria funzione e collegarla al programma. Una funzione helper serve tutte le DLL caricate in ritardo. Per altre informazioni, vedere Informazioni sulla funzione helper di caricamento ritardato e Sviluppare una funzione helper personalizzata.

Vedi anche

Creare DLL C/C++ in Visual Studio
Informazioni di riferimento sul linker MSVC