Come funziona la libreria di importazione? Dettagli?


90

So che questo può sembrare abbastanza semplice per i geek. Ma voglio che sia chiarissimo.

Quando voglio usare una DLL Win32, di solito chiamo semplicemente API come LoadLibrary () e GetProcAdders (). Ma di recente sto sviluppando con DirectX9 e ho bisogno di aggiungere file d3d9.lib , d3dx9.lib , ecc.

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

Quindi la mia comprensione attuale è che LIB contiene l'implementazione dei metodi ed è collegata staticamente al momento del collegamento come parte del file EXE finale. Mentre la DLL è caricata in modo dinamico in fase di esecuzione e non fa parte del file EXE finale.

Ma a volte, ci sono alcuni file LIB in arrivo con i file DLL, quindi:

  • A cosa servono questi file LIB?
  • Come ottengono ciò per cui sono destinati?
  • Esistono strumenti che possono permettermi di ispezionare le parti interne di questi file LIB?

Aggiorna 1

Dopo aver controllato wikipedia, ricordo che questi file LIB si chiamano libreria di importazione . Ma mi chiedo come funziona con la mia applicazione principale e le DLL per essere caricate dinamicamente.

Aggiorna 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 nella LIB -> DLL di destinazione reale

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 ogni metodo di esportazione della DLL dovrebbe essere codificato nello stub; Quindi è possibile effettuare salti / chiamate di metodo corretti.

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

BTW: esiste uno strumento in grado di ispezionare una libreria di importazione? Se riesco a vederlo, non ci saranno più dubbi.


4
Vedo che nessuno ha affrontato l'ultima parte della tua domanda, che riguarda gli strumenti che possono ispezionare una libreria di importazione. Con Visual C ++, esistono almeno due modi per farlo: lib /list xxx.libe link /dump /linkermember xxx.lib. Vedi questa domanda su Stack Overflow .
Alan

Inoltre, dumpbin -headers xxx.libfornisce alcune informazioni più dettagliate, rispetto alle utilità libe link.
m_katsifarakis

Risposte:


105

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.


4
Eliminando il mio post e votando questo, perché spieghi le cose in modo migliore di me;) Bella risposta.
ere Il

2
@RBerteig: grazie per la tua ottima risposta. Solo una piccola correzione, secondo qui ( msdn.microsoft.com/en-us/library/9yd93633.aspx ), ci sono 2 tipi di collegamento dinamico a una DLL, collegamento implicito in fase di caricamento e collegamento esplicito in fase di esecuzione . Nessun collegamento in fase di compilazione . Ora mi chiedo qual è la differenza tra il collegamento statico tradizionale (collegamento a un file * .lib che contiene l'implementazione completa) e il collegamento dinamico in fase di caricamento a una DLL (tramite una libreria di importazione)?
smwikipedia

1
Continua: quali sono i pro ei contro del collegamento statico e del collegamento dinamico in fase di caricamento ? Sembra che questi 2 approcci caricino entrambi tutti i file necessari nello spazio degli indirizzi all'inizio di un processo. Perché ne abbiamo bisogno 2? Grazie.
smwikipedia

1
puoi forse usare uno strumento come "objdump" per sbirciare all'interno di un file .lib e capire se si tratta di una libreria di importazione o di una vera libreria statica. su Linux durante la compilazione incrociata su una destinazione Windows, è possibile eseguire 'ar' o 'nm' sui file .a (versione mingw dei file .lib) e notare che le librerie di importazione hanno nomi di file .o generici e nessun codice (solo un'istruzione 'jmp'), mentre le librerie statiche hanno molte funzioni e codice all'interno.
don brillante

1
Piccola correzione: puoi anche collegare in modo implicito in fase di esecuzione. Il supporto del linker per le DLL caricate in ritardo lo spiega in dettaglio. Ciò è utile se si desidera modificare dinamicamente il percorso di ricerca della DLL o gestire correttamente gli errori di risoluzione dell'importazione (per supportare le nuove funzionalità del sistema operativo, ma eseguire comunque su versioni precedenti, ad esempio).
Rilevabile

5

Questi file di libreria di importazione .LIB vengono utilizzati nella seguente proprietà del progetto Linker->Input->Additional Dependencies, durante la creazione di un gruppo di DLL che richiedono informazioni aggiuntive al momento del collegamento, fornite dai file .LIB della libreria di importazione. Nell'esempio seguente per non ottenere errori del linker, ho bisogno di fare riferimento alle DLL A, B, C e D attraverso i loro file lib. (nota affinché il linker trovi questi file, potresti dover includere il loro percorso di distribuzione Linker->General->Additional Library Directoriesaltrimenti riceverai un errore di compilazione relativo all'impossibilità di trovare nessuno dei file lib forniti.)

Linker-> Input-> Dipendenze aggiuntive

Se la tua soluzione sta costruendo tutte le librerie dinamiche, potresti essere stato in grado di evitare questa specifica di dipendenza esplicita affidandoti invece ai flag di riferimento esposti nella Common Properties->Framework and Referencesfinestra di dialogo. Questi flag sembrano eseguire automaticamente il collegamento per tuo conto utilizzando i file * .lib. Framework e riferimenti

Questo tuttavia è come dice una proprietà comune , che non è specifica della configurazione o della piattaforma. Se è necessario supportare uno scenario di compilazione mista come nella nostra applicazione, avevamo una configurazione di compilazione per eseguire il rendering di una compilazione statica e una configurazione speciale che ha creato una compilazione vincolata di un sottoinsieme di assembly distribuiti come librerie dinamiche. Avevo usato i flag Use Library Dependency Inputs e Link Library Dependenciesimpostati su true in vari casi per fare in modo che le cose compilassero e in seguito mi rendevo conto di semplificare le cose, ma quando ho introdotto il mio codice nelle build statiche ho introdotto un sacco di avvisi del linker e la build era incredibilmente lenta per le build statiche. Ho finito per introdurre un mucchio di questo tipo di avvertimenti ...

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

E ho finito per utilizzare la specifica manuale di Additional Dependenciesper soddisfare il linker per le build dinamiche pur mantenendo felici i builders statici non utilizzando una proprietà comune che li rallentava. Quando distribuisco la build del sottoinsieme dinamico, distribuisco solo i file dll poiché questi file lib vengono utilizzati solo al momento del collegamento, non al runtime.


3

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

Le librerie statiche sono collegate al codice in 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 viene chiamata la funzione principale.

Quelle caricate dinamicamente sono molto simili alle librerie condivise, tranne per il fatto che vengono caricate quando e se la necessità sorge dal codice che hai scritto.


@Thanks zacsek. Ma non sono sicuro della tua dichiarazione sulla libreria condivisa.
smwikipedia

@smwikipedia: Linux li ha, io li uso, quindi sicuramente esistono. Leggi anche: en.wikipedia.org/wiki/Library_(computing)
Zoltán Szőcs

3
È una sottile differenza. Le librerie condivise e dinamiche sono entrambe file DLL. La differenza è quando vengono caricati. Le librerie condivise vengono caricate dal sistema operativo insieme all'EXE. Le librerie dinamiche vengono caricate dalla chiamata di codice LoadLibrary()e dalle API correlate.
RBerteig

Ho letto da [1] che la DLL è l'implementazione di Microsoft del concetto di libreria condivisa. [1]: en.wikipedia.org/wiki/Dynamic-link_library#Import_libraries
smwikipedia

Non sono d'accordo sul fatto che sia una sottile differenza, dalla vista di programmazione fa un'enorme differenza se la libreria condivisa è caricata dinamicamente o meno (se è caricata dinamicamente, allora devi aggiungere codice boilerplate per accedere alle funzioni).
Zoltán Szőcs

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.