Collegare un eseguibile a una DLL
Un file eseguibile collega (o carica) una DLL in uno dei due modi seguenti:
Collegamento implicito, in cui il sistema operativo carica la DLL contemporaneamente all'eseguibile che lo usa. L'eseguibile client chiama le funzioni esportate della DLL come se le funzioni fossero collegate in modo statico e contenute all'interno dell'eseguibile. Il collegamento implicito viene talvolta definito collegamento dinamico di caricamento o tempo di caricamento statico.
Collegamento esplicito, in cui il sistema operativo carica la DLL su richiesta in fase di esecuzione. Un eseguibile che usa una DLL tramite collegamento esplicito deve caricare e scaricare in modo esplicito la DLL. Deve anche configurare un puntatore a funzione per accedere a ogni funzione usata dalla DLL. A differenza delle chiamate a funzioni in una libreria collegata in modo statico o in una DLL collegata in modo implicito, l'eseguibile client deve chiamare le funzioni esportate in una DLL collegata in modo esplicito tramite puntatori a funzione. Il collegamento esplicito viene talvolta definito collegamento dinamico o collegamento dinamico in fase di esecuzione.
Un eseguibile può usare uno dei metodi di collegamento per collegarsi alla stessa DLL. Inoltre, questi metodi non si escludono a vicenda; un eseguibile può collegarsi in modo implicito a una DLL e un altro potrebbe collegarsi in modo esplicito.
Determinare il metodo di collegamento da usare
Se usare il collegamento implicito o il collegamento esplicito è una decisione dell'architettura che è necessario prendere per l'applicazione. Esistono vantaggi e svantaggi per ogni metodo.
Collegamento implicito
Il collegamento implicito si verifica quando il codice di un'applicazione chiama una funzione DLL esportata. Quando il codice sorgente per l'eseguibile chiamante viene compilato o assemblato, la chiamata di funzione DLL genera un riferimento di funzione esterna nel codice oggetto. Per risolvere questo riferimento esterno, l'applicazione deve collegarsi alla libreria di importazione (file con estensione lib) fornita dal creatore della DLL.
La libreria di importazione contiene solo codice per caricare la DLL e implementare chiamate alle funzioni nella DLL. La ricerca di una funzione esterna in una libreria di importazione informa il linker che il codice per tale funzione si trova in una DLL. Per risolvere i riferimenti esterni alle DLL, il linker aggiunge semplicemente informazioni al file eseguibile che indica al sistema dove trovare il codice DLL all'avvio del processo.
Quando il sistema avvia un programma che contiene riferimenti collegati dinamicamente, usa le informazioni nel file eseguibile del programma per individuare le DLL necessarie. Se non riesce a individuare la DLL, il sistema termina il processo e visualizza una finestra di dialogo che segnala l'errore. In caso contrario, il sistema esegue il mapping dei moduli DLL nello spazio indirizzi del processo.
Se una delle DLL ha una funzione punto di ingresso per l'inizializzazione e il codice di terminazione, DllMain
ad esempio , il sistema operativo chiama la funzione. Uno dei parametri passati alla funzione del punto di ingresso specifica un codice che indica che la DLL è collegata al processo. Se la funzione del punto di ingresso non restituisce TRUE, il sistema termina il processo e segnala l'errore.
Infine, il sistema modifica il codice eseguibile del processo per fornire gli indirizzi iniziali per le funzioni DLL.
Analogamente al resto del codice di un programma, il caricatore esegue il mapping del codice DLL nello spazio indirizzi del processo all'avvio del processo. Il sistema operativo lo carica in memoria solo quando necessario. Di conseguenza, gli attributi di codice e LOADONCALL
usati dai file def per controllare il PRELOAD
caricamento nelle versioni precedenti di Windows non hanno più significato.
Collegamento esplicito
La maggior parte delle applicazioni usa il collegamento implicito perché è il metodo di collegamento più semplice da usare. Tuttavia, ci sono momenti in cui è necessario il collegamento esplicito. Ecco alcuni motivi comuni per usare il collegamento esplicito:
L'applicazione non conosce il nome di una DLL caricata fino al runtime. Ad esempio, l'applicazione potrebbe ottenere il nome della DLL e le funzioni esportate da un file di configurazione all'avvio.
Un processo che usa il collegamento implicito viene terminato dal sistema operativo se la DLL non viene trovata all'avvio del processo. Un processo che usa il collegamento esplicito non viene terminato in questa situazione e può tentare di eseguire il ripristino dall'errore. Ad esempio, il processo potrebbe notificare all'utente l'errore e fare in modo che l'utente specifichi un altro percorso della DLL.
Un processo che usa il collegamento implicito viene terminato anche se una delle DLL collegate per avere una
DllMain
funzione che non riesce. Un processo che usa il collegamento esplicito non viene terminato in questa situazione.Un'applicazione che collega in modo implicito a molte DLL può essere lenta da avviare perché Windows carica tutte le DLL quando l'applicazione viene caricata. Per migliorare le prestazioni di avvio, un'applicazione potrebbe usare solo il collegamento implicito per le DLL necessarie immediatamente dopo il caricamento. Potrebbe usare il collegamento esplicito per caricare altre DLL solo quando sono necessarie.
Il collegamento esplicito elimina la necessità di collegare l'applicazione usando una libreria di importazione. Se le modifiche apportate alla DLL causano la modifica dei ordinali di esportazione, le applicazioni non devono ricollegarsi se chiamano
GetProcAddress
usando il nome di una funzione e non un valore ordinale. Le applicazioni che usano il collegamento implicito devono comunque ricollegarsi alla libreria di importazione modificata.
Ecco due pericoli per il collegamento esplicito da tenere presente:
Se la DLL ha una
DllMain
funzione del punto di ingresso, il sistema operativo chiama la funzione nel contesto del thread che ha chiamatoLoadLibrary
. La funzione del punto di ingresso non viene chiamata se la DLL è già collegata al processo a causa di una chiamata precedente aLoadLibrary
che non ha avuto una chiamata corrispondente allaFreeLibrary
funzione. Il collegamento esplicito può causare problemi se la DLL usa unaDllMain
funzione per inizializzare ogni thread di un processo, perché qualsiasi thread già esistente quandoLoadLibrary
(oAfxLoadLibrary
) viene chiamato non viene inizializzato.Se una DLL dichiara dati extent statici come
__declspec(thread)
, può causare un errore di protezione se collegato in modo esplicito. Dopo che la DLL viene caricata da una chiamata aLoadLibrary
, genera un errore di protezione ogni volta che il codice fa riferimento a questi dati. I dati dell'extent statico includono elementi statici globali e locali. Ecco perché, quando si crea una DLL, è consigliabile evitare di usare l'archiviazione locale del thread. In caso contrario, informare gli utenti della DLL sulle potenziali insidie del caricamento dinamico della DLL. Per altre informazioni, vedere Uso dell'archiviazione locale del thread in una libreria a collegamento dinamico (Windows SDK).
Come usare il collegamento implicito
Per usare una DLL tramite collegamento implicito, i file eseguibili client devono ottenere questi file dal provider della DLL:
Uno o più file di intestazione (file con estensione h) che contengono le dichiarazioni dei dati, delle funzioni e delle classi C++ esportate nella DLL. Le classi, le funzioni e i dati esportati dalla DLL devono essere contrassegnati
__declspec(dllimport)
nel file di intestazione. Per altre informazioni, vedere dllexport, dllimport.Libreria di importazione da collegare al file eseguibile. Il linker crea la libreria di importazione al momento della compilazione della DLL. Per altre informazioni, vedere File LIB come input del linker.
File DLL effettivo.
Per usare i dati, le funzioni e le classi in una DLL tramite collegamento implicito, qualsiasi file di origine client deve includere i file di intestazione che li dichiarano. Dal punto di vista del codice, le chiamate alle funzioni esportate sono esattamente come qualsiasi altra chiamata di funzione.
Per compilare il file eseguibile del client, è necessario collegarsi alla libreria di importazione della DLL. Se si usa un makefile esterno o un sistema di compilazione, specificare la libreria di importazione insieme agli altri file oggetto o librerie collegati.
Il sistema operativo deve essere in grado di individuare il file DLL quando carica l'eseguibile chiamante. Ciò significa che è necessario distribuire o verificare l'esistenza della DLL quando si installa l'applicazione.
Come collegare in modo esplicito a una DLL
Per usare una DLL tramite collegamento esplicito, le applicazioni devono effettuare una chiamata di funzione per caricare in modo esplicito la DLL in fase di esecuzione. Per collegare in modo esplicito una DLL, un'applicazione deve:
Chiamare LoadLibraryEx o una funzione simile per caricare la DLL e ottenere un handle di modulo.
Chiamare GetProcAddress per ottenere un puntatore di funzione a ogni funzione esportata chiamata dall'applicazione. Poiché le applicazioni chiamano le funzioni DLL tramite un puntatore, il compilatore non genera riferimenti esterni, quindi non è necessario collegare una libreria di importazione. Tuttavia, è necessario disporre di un'istruzione
typedef
ousing
che definisce la firma di chiamata delle funzioni esportate chiamate.Chiama FreeLibrary al termine della DLL.
Ad esempio, questa funzione di esempio chiama LoadLibrary
per caricare una DLL denominata "MyDLL", chiama GetProcAddress
per ottenere un puntatore a una funzione denominata "DLLFunc1", chiama la funzione e salva il risultato e quindi chiama FreeLibrary
per scaricare la DLL.
#include "windows.h"
typedef HRESULT (CALLBACK* LPFNDLLFUNC1)(DWORD,UINT*);
HRESULT LoadAndCallSomeFunction(DWORD dwParam1, UINT * puParam2)
{
HINSTANCE hDLL; // Handle to DLL
LPFNDLLFUNC1 lpfnDllFunc1; // Function pointer
HRESULT hrReturnVal;
hDLL = LoadLibrary("MyDLL");
if (NULL != hDLL)
{
lpfnDllFunc1 = (LPFNDLLFUNC1)GetProcAddress(hDLL, "DLLFunc1");
if (NULL != lpfnDllFunc1)
{
// call the function
hrReturnVal = lpfnDllFunc1(dwParam1, puParam2);
}
else
{
// report the error
hrReturnVal = ERROR_DELAY_LOAD_FAILED;
}
FreeLibrary(hDLL);
}
else
{
hrReturnVal = ERROR_DELAY_LOAD_FAILED;
}
return hrReturnVal;
}
A differenza di questo esempio, nella maggior parte dei casi è consigliabile chiamare LoadLibrary
e FreeLibrary
una sola volta nell'applicazione per una determinata DLL. È particolarmente vero se si intende chiamare più funzioni nella DLL o chiamare ripetutamente le funzioni DLL.