Come funziona la libreria di importazione? Dettagli?

So che questo può sembrare abbastanza semplice per i geek. Ma voglio renderlo cristallino.

Quando voglio usare una DLL Win32, di solito chiamo solo le API come LoadLibrary () e GetProcAdderss (). Ma recentemente, sto sviluppando con DirectX9 e ho bisogno di aggiungere file d3d9.lib , d3dx9.lib , etc.

Ho sentito abbastanza che LIB è per il collegamento statico e DLL è per il collegamento dinamico.

Quindi la mia attuale comprensione è che LIB contiene l’implementazione dei metodi ed è linkato staticamente al link time come parte del file EXE finale. Mentre DLL è dynamic caricata in fase di runtime e non fa parte del file EXE finale.

Ma a volte, ci sono alcuni file LIB che arrivano con i file DLL, quindi:

  • Per cosa sono questi file LIB?
  • Come ottengono ciò per cui sono destinati?
  • C’è qualche strumento che può permettermi di ispezionare gli interni di questi file LIB?

Aggiornamento 1

Dopo aver controllato wikipedia, ricordo che questi file LIB sono chiamati librerie di importazione . Ma mi chiedo come funzioni con la mia applicazione principale e le DLL per essere caricate dynamicmente.

Aggiornamento 2

Proprio come ha detto RBerteig, ci sono alcuni codici stub nei file LIB nati con le DLL. Quindi la sequenza di chiamata dovrebbe essere così:

La mia applicazione principale -> stub in LIB -> destinazione reale DLL

Quindi quali informazioni dovrebbero essere contenute in queste LIB? Potrei pensare a quanto segue:

  • Il file LIB dovrebbe contenere il percorso completo della DLL corrispondente; Quindi la DLL potrebbe essere caricata dal runtime.
  • L’indirizzo relativo (o l’offset del file?) Del punto di ingresso di ciascun metodo di esportazione della DLL deve essere codificato nello stub; In tal modo potrebbero essere effettuati salti corretti / chiamate di metodo.

Ho ragione su questo? C’è qualcosa di più?

BTW: C’è uno strumento che può ispezionare una libreria di importazione? Se riesco a vederlo, non ci saranno più dubbi.

Il collegamento a un file DLL può verificarsi implicitamente al momento della compilazione o esplicitamente 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 i puntatori alle funzioni che è necessario chiamare.

Se collegato in modo implicito al momento della creazione del programma, gli stub per ogni esportazione DLL utilizzata dal programma vengono collegati al programma da una libreria di importazione e tali stub vengono aggiornati con l’avvio del processo EXE e della DLL. (Sì, ho semplificato più di un po ‘qui …)

Questi stub devono provenire da qualche parte e nella catena di strumenti Microsoft provengono da una forma speciale di file .LIB chiamata libreria di importazione . Il .LIB richiesto viene in genere creato nello stesso momento della DLL e contiene uno stub per ogni funzione esportata dalla DLL.

Confusamente, una versione statica della stessa libreria verrebbe anche spedita come file .LIB. Non c’è un modo banale per distinguerli, tranne che LIBs che sono librerie di importazione per DLL di solito saranno più piccole (spesso molto più piccole) rispetto alla LIB statica corrispondente.

Se usi la toolchain GCC, tra l’altro, non hai davvero bisogno di librerie di importazione per abbinare le tue DLL. La versione del linker di Gnu trasferito a Windows comprende direttamente le DLL e può sintetizzare al volo quasi tutti gli stub richiesti.

Aggiornare

Se non riesci a resistere alla ricerca di dove siano realmente tutti i dadi e bulloni e cosa sta realmente succedendo, c’è sempre qualcosa in MSDN da aiutare. L’articolo di Matt Pietrek Uno sguardo approfondito nel formato di file eseguibile portatile Win32 è una panoramica molto completa del formato del file EXE e di come viene caricato ed eseguito. È stato anche aggiornato per coprire .NET e altro da quando è stato originariamente pubblicato su MSDN Magazine ca. 2002.

Inoltre, può essere utile sapere come imparare esattamente quali DLL sono utilizzate da un programma. Lo strumento per questo è Dependency Walker, aka depends.exe. Una versione di esso è inclusa in Visual Studio, ma l’ultima versione è disponibile dal suo autore su http://www.dependencywalker.com/ . È in grado di identificare tutte le DLL che sono state specificate al momento del collegamento (carico iniziale e caricamento ritardato) e può anche eseguire il programma e controllare eventuali DLL aggiuntive caricate in fase di esecuzione.

Aggiornamento 2

Ho riformulato parte del testo precedente per chiarirlo sulla rilettura e per utilizzare i termini di collegamento implicito ed esplicito per coerenza con MSDN.

Quindi, abbiamo tre modi in cui le funzioni della libreria potrebbero essere rese disponibili per essere utilizzate da un programma. L’ovvia domanda di follow-up è quindi: “Come scegliere da che parte?”

Il collegamento statico è il modo in cui la maggior parte del programma stesso è collegata. Tutti i file object sono elencati e raccolti insieme al file EXE dal linker. Lungo il percorso, il linker si occupa di piccoli compiti come la risoluzione dei riferimenti ai simboli globali in modo che i tuoi moduli possano chiamare le rispettive funzioni. Le biblioteche possono anche essere collegate staticamente. I file object che costituiscono 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 sono ignorati. Ad esempio, la tradizionale libreria matematica in C include molte funzioni di trigonometria. Ma se ci si collega e si utilizza cos() , non si finisce con una copia del codice per sin() o tan() meno che non si chiamino anche quelle funzioni. Per le librerie di grandi dimensioni con un ricco set di funzionalità, questa inclusione selettiva dei moduli è importante. Su molte piattaforms 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 inclusione selettiva, sarebbe più difficile gestire i dettagli dei programmi di costruzione per quelle piattaforms.

Tuttavia, avere una copia della stessa libreria in ogni programma in esecuzione crea un onere su un sistema che normalmente esegue molti processi. Con il giusto tipo di sistema di memoria virtuale, le pagine di memoria che hanno contenuti identici devono esistere solo una volta nel sistema, ma possono essere utilizzate da molti processi. Ciò crea un vantaggio per aumentare le probabilità che le pagine contenenti codice siano probabilmente identiche a qualche pagina in tutti gli altri processi in esecuzione possibili. Ma, se i programmi si collegano staticamente alla libreria di runtime, ognuno di essi ha un diverso mix di funzioni ciascuno disposto nella mappa della memoria dei processi in posizioni diverse, e non ci sono molte code page condivisibili a meno che non sia un programma tutto da solo eseguire più di processo. Quindi l’idea di una DLL ha ottenuto un altro, importante vantaggio.

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 tutti condividere le proprie code page. Tutti vincono. (Bene, finché non aggiorni una DLL con la nuova versione, ma questa non fa parte di questa storia. Google DLL Hell per quel lato del racconto.)

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 usi. Tuttavia, il tuo programma è più grande e non è abbastanza buono 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 usi, ma in genere sei più amichevole con altri processi sul sistema.

Un grande vantaggio di una DLL è che può essere caricato e utilizzato senza ricompilare o ricolbind il programma principale. Ciò può consentire a un provider di librerie di terze parti (ad esempio, Microsoft e il runtime C, ad esempio) di correggere un bug nella loro libreria e distribuirlo. Una volta che un utente finale installa la DLL aggiornata, ottiene immediatamente il beneficio di tale correzione 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 extra di 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 plugin, per esempio.

Esistono tre tipi di librerie: librerie statiche, condivise e caricate dynamicmente.

Le librerie statiche sono collegate al codice nella fase di collegamento, quindi sono effettivamente nell’eseguibile, a differenza della libreria condivisa, che ha solo stub (simboli) da cercare nel file della libreria condivisa, che viene caricato in fase di esecuzione prima del la funzione principale viene chiamata.

Quelle caricate dynamicmente sono molto simili alle librerie condivise, eccetto che vengono caricate quando e se il bisogno è dovuto al codice che hai scritto.

Questi file della libreria di importazione .LIB vengono utilizzati nella seguente proprietà del progetto, Linker->Input->Additional Dependencies , quando si Linker->Input->Additional Dependencies un sacco di dll che richiedono informazioni aggiuntive al momento del collegamento fornito dai file .LIB della libreria di importazione. Nell’esempio seguente per non ottenere errori di linker ho bisogno di fare riferimento a dll A, B, C e D attraverso i loro file lib. (nota che il linker per trovare questi file potrebbe aver bisogno di includere il loro percorso di distribuzione in Linker->General->Additional Library Directories altrimenti avrai un errore di compilazione riguardo l’impossibilità di trovare i file lib forniti.)

Linker-/> Input-> Ulteriori dipendenze”> </p>
<p>  Se la tua soluzione sta costruendo tutte le librerie dinamiche potresti essere stato in grado di evitare questa specifica esplicita delle dipendenze affidandoti invece ai flag di riferimento esposti sotto la finestra di dialogo <code>Common Properties->Framework and References</code> .  Questi flag sembrano eseguire automaticamente il collegamento a tuo nome utilizzando i file * .lib. <img src=

Ciò tuttavia è come si dice una proprietà comune , che non è la configurazione o la piattaforma specifica. Se è necessario supportare uno scenario di compilazione misto come nella nostra applicazione, è stata creata una configurazione di build per il rendering di una build statica e una configurazione speciale che ha generato una build vincasting di un sottoinsieme di assembly distribuiti come librerie dinamiche. Avevo usato i flag Use Library Dependency Inputs e Link Library Dependencies impostati su true in vari casi per ottenere cose da build e in seguito rendermi conto di semplificare le cose ma quando introducevo il mio codice alle build statiche ho introdotto una tonnellata di avvertimenti di linker e la build era incredibilmente lento per le build statiche. Ho finito con l’introdurre un sacco di questi avvertimenti …

 warning LNK4006: "bool __cdecl XXX::YYY() already defined in CoreLibrary.lib(JSource.obj); second definition ignored D.lib(JSource.obj) 

E mi sono ritrovato a utilizzare la specifica manuale delle Additional Dependencies per soddisfare il linker per le build dinamiche, mantenendo al contempo felici i builder statici non utilizzando una proprietà comune che li rallentava. Quando distribuisco la generazione di sottoinsiemi dinamici, distribuisco solo i file dll in quanto questi file lib vengono utilizzati solo al momento del collegamento, non in fase di runtime.

Ecco alcuni argomenti relativi a MSDN per rispondere alla mia domanda:

Collegamento di un eseguibile a una DLL

Collegamento implicito

Determinazione del metodo di collegamento da utilizzare

Costruire una libreria di importazione ed esportare file