Il collegamento a un file DLL può avvenire in modo implicito in fase di collegamento della compilazione o in modo esplicito in fase di esecuzione. In entrambi i casi, la DLL viene caricata nello spazio di memoria dei processi e tutti i punti di ingresso esportati sono disponibili per l'applicazione.
Se utilizzato in modo esplicito in fase di esecuzione, si utilizza LoadLibrary()
e GetProcAddress()
per caricare manualmente la DLL e ottenere puntatori alle funzioni che è necessario chiamare.
Se collegato in modo implicito quando il programma viene creato, gli stub per ogni esportazione DLL utilizzata dal programma vengono collegati al programma da una libreria di importazione e tali stub vengono aggiornati quando l'EXE e la DLL vengono caricate all'avvio del processo. (Sì, ho semplificato più di un po 'qui ...)
Quelle matrici devono provenire da qualche parte e nella catena di strumenti Microsoft provengono da una forma speciale di file .LIB chiamata libreria di importazione . Il file .LIB richiesto viene solitamente creato contemporaneamente alla DLL e contiene uno stub per ciascuna funzione esportata dalla DLL.
In modo confuso, una versione statica della stessa libreria verrebbe fornita anche come file .LIB. Non esiste un modo banale per distinguerli, tranne per il fatto che le LIB che sono librerie di importazione per DLL saranno generalmente più piccole (spesso molto più piccole) di quanto sarebbe la LIB statica corrispondente.
Se usi la toolchain di GCC, per inciso, non hai effettivamente bisogno di librerie di importazione per abbinare le tue DLL. La versione del linker Gnu trasferita su Windows comprende direttamente le DLL e può sintetizzare al volo quasi tutti gli stub richiesti.
Aggiornare
Se non riesci a resistere nel sapere dove sono realmente tutti i dettagli e cosa sta realmente accadendo, in MSDN c'è sempre qualcosa che ti può aiutare. L'articolo di Matt Pietrek Un'analisi approfondita del formato di file eseguibile portatile Win32 approfondita del formato di file offre una panoramica molto completa del formato del file EXE e di come viene caricato ed eseguito. È stato persino aggiornato per coprire .NET e altro da quando è apparso originariamente su MSDN Magazine ca. 2002.
Inoltre, può essere utile sapere come apprendere esattamente quali DLL vengono utilizzate da un programma. Lo strumento per questo è Dependency Walker, noto anche come dipende.exe. Una versione di esso è inclusa con Visual Studio, ma l'ultima versione è disponibile presso il suo autore all'indirizzo http://www.dependencywalker.com/ . Può identificare tutte le DLL specificate al momento del collegamento (caricamento anticipato e caricamento ritardato) e può anche eseguire il programma e controllare eventuali DLL aggiuntive caricate in fase di esecuzione.
Aggiorna 2
Ho riformulato parte del testo precedente per chiarirlo durante la rilettura e per utilizzare i termini del collegamento implicito ed esplicito per motivi di coerenza con MSDN.
Quindi, abbiamo tre modi in cui le funzioni di libreria possono essere rese disponibili per essere utilizzate da un programma. L'ovvia domanda successiva è quindi: "Come scegliere in che modo?"
Il collegamento statico è il modo in cui viene collegata la maggior parte del programma stesso. Tutti i file oggetto sono elencati e vengono raccolti insieme nel file EXE dal linker. Lungo la strada, il linker si occupa di piccole faccende come correggere i riferimenti a simboli globali in modo che i tuoi moduli possano chiamare le funzioni degli altri. Le biblioteche possono anche essere collegate staticamente. I file oggetto che compongono la libreria vengono raccolti insieme da un bibliotecario in un file .LIB che il linker ricerca per i moduli contenenti i simboli necessari. Un effetto del collegamento statico è che solo i moduli della libreria utilizzati dal programma sono collegati ad esso; altri moduli vengono ignorati. Ad esempio, la tradizionale libreria matematica C include molte funzioni di trigonometria. Ma se ti colleghi contro di esso e usicos()
, non ti ritroverai con una copia del codice per sin()
oa tan()
meno che tu non abbia chiamato anche quelle funzioni. Per le biblioteche di grandi dimensioni con un ricco set di funzionalità, questa inclusione selettiva di moduli è importante. Su molte piattaforme come i sistemi embedded, la dimensione totale del codice disponibile per l'uso nella libreria può essere grande rispetto allo spazio disponibile per memorizzare un eseguibile nel dispositivo. Senza l'inclusione selettiva, sarebbe più difficile gestire i dettagli della creazione di programmi per quelle piattaforme.
Tuttavia, avere una copia della stessa libreria in ogni programma in esecuzione crea un peso su un sistema che normalmente esegue molti processi. Con il giusto tipo di sistema di memoria virtuale, le pagine di memoria con contenuto identico devono esistere solo una volta nel sistema, ma possono essere utilizzate da molti processi. Ciò crea un vantaggio per aumentare le possibilità che le pagine contenenti codice siano probabilmente identiche a qualche pagina nel maggior numero possibile di altri processi in esecuzione. Ma, se i programmi si collegano staticamente alla libreria di runtime, ognuno ha un diverso mix di funzioni, ciascuna disposta in quella mappa di memoria dei processi in posizioni diverse, e non ci sono molte code page condivisibili a meno che non sia un programma che è tutto da solo eseguire in più di un processo. Quindi l'idea di una DLL ha ottenuto un altro vantaggio importante.
Una DLL per una libreria contiene tutte le sue funzioni, pronte per essere utilizzate da qualsiasi programma client. Se molti programmi caricano quella DLL, possono condividerne tutti i codici. Tutti vincono. (Bene, finché non aggiorni una DLL con una nuova versione, ma questo non fa parte di questa storia. Google DLL Hell per quel lato della storia.)
Quindi la prima grande scelta da fare quando si pianifica un nuovo progetto è tra collegamento dinamico e statico. Con il collegamento statico, hai meno file da installare e sei immune da terze parti che aggiornano una DLL che utilizzi. Tuttavia, il tuo programma è più grande e non è altrettanto bravo come cittadino dell'ecosistema Windows. Con il collegamento dinamico, hai più file da installare, potresti avere problemi con una terza parte che aggiorna una DLL che utilizzi, ma in genere sei più amichevole con altri processi sul sistema.
Un grande vantaggio di una DLL è che può essere caricata e utilizzata senza ricompilare o anche ricollegare il programma principale. Ciò può consentire a un fornitore di librerie di terze parti (ad esempio, Microsoft e il runtime C) di correggere un bug nella propria libreria e distribuirlo. Una volta che un utente finale installa la DLL aggiornata, ottiene immediatamente il vantaggio di quella correzione di bug in tutti i programmi che utilizzano quella DLL. (A meno che non rompa le cose. Vedi DLL Hell.)
L'altro vantaggio deriva dalla distinzione tra caricamento implicito ed esplicito. Se si passa allo sforzo aggiuntivo del caricamento esplicito, la DLL potrebbe non essere nemmeno esistita quando il programma è stato scritto e pubblicato. Ciò consente meccanismi di estensione che possono scoprire e caricare plug-in, ad esempio.
lib /list xxx.lib
elink /dump /linkermember xxx.lib
. Vedi questa domanda su Stack Overflow .