Differenza tra oggetti condivisi (.so), librerie statiche (.a) e DLL (.so)?

Sono stato coinvolto in un dibattito riguardo alle librerie in Linux e vorrei confermare alcune cose.

Secondo la mia comprensione (correggimi se ho torto e modificherò il mio post in un secondo momento), ci sono due modi per usare le librerie quando si costruisce un’applicazione:

  1. Librerie statiche (file .a): al momento del collegamento, una copia dell’intera libreria viene inserita nell’applicazione finale in modo che le funzioni all’interno della libreria siano sempre disponibili per l’applicazione chiamante
  2. Oggetti condivisi (file .so): al momento del collegamento, l’object viene semplicemente verificato rispetto alla sua API tramite il corrispondente file di intestazione (.h). La libreria non viene effettivamente utilizzata fino al runtime, dove è necessaria.

L’ovvio vantaggio delle librerie statiche è che consentono all’intera applicazione di essere autosufficiente, mentre il vantaggio delle librerie dinamiche è che il file “.so” può essere sostituito (es .: nel caso debba essere aggiornato a causa di una sicurezza bug) senza richiedere la ricompilazione dell’applicazione di base.

Ho sentito alcune persone fare una distinzione tra oggetti condivisi e librerie dinamiche collegate (DLL), anche se sono entrambi file “.so”. C’è qualche distinzione tra oggetti condivisi e DLL quando si tratta di sviluppo C / C ++ su Linux o qualsiasi altro sistema operativo POSIX (ad esempio: MINIX, UNIX, QNX, ecc.)? Mi è stato detto che una differenza chiave (finora) è che gli oggetti condivisi sono utilizzati solo in fase di esecuzione, mentre le DLL devono essere aperte prima utilizzando la chiamata dlopen () all’interno dell’applicazione.

Infine, ho anche sentito alcuni sviluppatori parlare di “archivi condivisi”, che, a mio modo di vedere, sono anch’essi delle librerie statiche, ma non sono mai usati direttamente da un’applicazione. Invece, altre librerie statiche collegheranno agli “archivi condivisi” per estrarre alcune (o non tutte) funzioni / risorse dall’archivio condiviso nella libreria statica che si sta costruendo.

Grazie a tutti per la vostra assistenza.

Aggiornare


Nel contesto in cui mi sono stati forniti questi termini, ho scoperto le lievi differenze in questi termini, che possono anche essere solo espressioni colloquiali nel mio settore:

  1. Oggetto condiviso: una libreria che viene automaticamente collegata a un programma all’avvio del programma ed esiste come file autonomo. La libreria è inclusa nella lista di collegamento in fase di compilazione (es .: LDOPTS+=-lmylib per un file di libreria chiamato mylib.so ). La libreria deve essere presente al momento della compilazione e all’avvio dell’applicazione.
  2. Libreria statica: una libreria che viene unita al programma stesso al momento della compilazione per una singola applicazione (più grande) contenente il codice dell’applicazione e il codice della libreria che viene automaticamente collegato a un programma al momento della creazione del programma e il file finale contenente entrambi il programma principale e la libreria stessa esistono come un singolo file binario autonomo. La libreria è inclusa nella lista di collegamento in fase di compilazione (es .: LDOPTS+=-lmylib per un file di libreria chiamato mylib.a). La libreria deve essere presente al momento della compilazione.
  3. DLL: Essenzialmente uguale a un object condiviso, ma piuttosto che essere incluso nell’elenco di collegamento in fase di compilazione, la libreria viene caricata tramite i dlopen() / dlsym() modo che la libreria non debba essere presente al momento della compilazione per il programma da compilare. Inoltre, la libreria non ha bisogno di essere presente (necessariamente) all’avvio dell’applicazione o al tempo di compilazione , in quanto è necessaria solo nel momento in dlsym vengono effettuate le chiamate dlopen / dlsym .
  4. Archivio condiviso: sostanzialmente uguale a una libreria statica, ma compilato con i flag “export-shared” e “-fPIC”. La libreria è inclusa nella lista di collegamento in fase di compilazione (es .: LDOPTS + = – lmylib S per un file di libreria chiamato mylib S .a). La distinzione tra i due è che questo flag aggiuntivo è necessario se un object o DLL condivisi vuole colbind staticamente l’archivio condiviso nel proprio codice E essere in grado di rendere le funzioni nell’object condiviso disponibili ad altri programmi, piuttosto che semplicemente usarli interno alla DLL. Questo è utile nel caso in cui qualcuno ti fornisca una libreria statica e desideri riconfezionarla come SO. La libreria deve essere presente al momento della compilazione.

Aggiornamento aggiuntivo

La distinzione tra ” DLL ” e ” shared library ” era solo un colloquialismo (pigro, impreciso) nella società in cui lavoravo in quel momento (gli sviluppatori di Windows sono stati costretti a passare allo sviluppo di Linux e il termine bloccato), aderendo alle descrizioni notato sopra.

Inoltre, la lettera ” S ” finale dopo il nome della libreria, nel caso di “archivi condivisi” era solo una convenzione utilizzata in quell’azienda e non nel settore in generale.

Ho sempre pensato che DLL e oggetti condivisi fossero solo termini diversi per la stessa cosa: Windows li chiama DLL, mentre su sistemi UNIX sono oggetti condivisi, con il termine generale – libreria collegata dynamicmente – che copre entrambi (anche la funzione per apri un .so su UNIX è chiamato dlopen() dopo ‘dynamic library’).

Sono infatti solo collegati all’avvio dell’applicazione, tuttavia la tua nozione di verifica rispetto al file di intestazione non è corretta. Il file di intestazione definisce i prototipi che sono necessari per compilare il codice che utilizza la libreria, ma al momento del collegamento il linker guarda all’interno della libreria stessa per assicurarsi che le funzioni di cui ha bisogno siano effettivamente lì. Il linker deve trovare i corpi delle funzioni da qualche parte al momento del collegamento o genererà un errore. Lo fa ANCHE in fase di runtime, perché come giustamente fai notare la libreria stessa potrebbe essere cambiata da quando il programma è stato compilato. Questo è il motivo per cui la stabilità ABI è così importante nelle librerie di piattaforms, poiché il cambiamento ABI è ciò che spezza i programmi esistenti compilati rispetto alle versioni precedenti.

Le librerie statiche sono solo bundle di file object direttamente dal compilatore, proprio come quelli che stai costruendo tu stesso come parte della compilazione del tuo progetto, in modo che vengano inseriti e alimentati al linker esattamente nello stesso modo, e i bit non utilizzati sono lasciato cadere nello stesso modo.

Una libreria statica (.a) è una libreria che può essere collegata direttamente all’eseguibile finale prodotto dal linker, è contenuta in essa e non è necessario avere la libreria nel sistema in cui verrà implementato l’eseguibile.

Una libreria condivisa (.so) è una libreria collegata ma non incorporata nell’eseguibile finale, quindi verrà caricata all’avvio dell’eseguibile e dovrà essere presente nel sistema in cui viene distribuito l’eseguibile.

Una libreria di collegamenti dinamici su windows (.dll) è come una libreria condivisa (.so) su linux ma ci sono alcune differenze tra le due implementazioni che sono legate al sistema operativo (Windows vs Linux):

Una DLL può definire due tipi di funzioni: esportate e interne. Le funzioni esportate devono essere richiamate da altri moduli, nonché dalla DLL in cui sono definiti. Le funzioni interne sono in genere destinate a essere chiamate solo all’interno della DLL in cui sono definite.

Una libreria SO su Linux non ha bisogno di speciali istruzioni di esportazione per indicare simboli esportabili, poiché tutti i simboli sono disponibili per un processo di interrogazione.

Posso approfondire i dettagli delle DLL in Windows per chiarire questi misteri ai miei amici qui in * NIX-land …

Una DLL è come un file object condiviso. Entrambe sono immagini, pronte per essere caricate in memoria dal programma di caricamento del rispettivo sistema operativo. Le immagini sono accompagnate da vari bit di metadati per aiutare i linker e i caricatori a fare le associazioni necessarie e ad usare la libreria di codice.

Le DLL di Windows hanno una tabella di esportazione. Le esportazioni possono essere per nome o per posizione della tabella (numerico). Quest’ultimo metodo è considerato “vecchia scuola” ed è molto più fragile: la ricostruzione della DLL e la modifica della posizione di una funzione nella tabella terminano in caso di emergenza, mentre non vi è alcun problema reale se il collegamento dei punti di ingresso è per nome. Quindi, dimenticalo come un problema, ma tieni presente che è lì se lavori con codice “dinosauro” come le librerie di terze parti.

Le DLL di Windows sono costruite compilando e collegando, proprio come fareste per un EXE (applicazione eseguibile), ma la DLL non vuole essere autonoma, proprio come un SO è pensato per essere usato da un’applicazione, tramite caricamento dinamico, o mediante associazione link-time (il riferimento a SO è incorporato nei metadati del binario dell’applicazione e il caricatore del programma OS caricherà automaticamente i SO di riferimento). Le DLL possono fare riferimento ad altre DLL, proprio come gli SO possono fare riferimento ad altri SO.

In Windows, le DLL renderanno disponibili solo specifici punti di ingresso. Questi sono chiamati “esportazioni”. Lo sviluppatore può utilizzare una parola chiave speciale del compilatore per rendere un simbolo esternamente visibile (ad altri linker e al caricatore dinamico), oppure le esportazioni possono essere elencate in un file di definizione del modulo che viene utilizzato al momento del collegamento quando la DLL stessa è in fase di creazione. La pratica moderna è di decorare la definizione della funzione con la parola chiave per esportare il nome del simbolo. È anche ansible creare file di intestazione con parole chiave che dichiareranno tale simbolo come uno da importare da una DLL al di fuori dell’unità di compilazione corrente. Cerca le parole chiave __declspec (dllexport) e __declspec (dllimport) per maggiori informazioni.

Una delle caratteristiche interessanti delle DLL è che possono dichiarare una funzione di gestione “su carico / scarico” standard. Ogni volta che la DLL viene caricata o scaricata, la DLL può eseguire alcune operazioni di inizializzazione o pulizia, a seconda dei casi. Questo si integra perfettamente nell’avere una DLL come un gestore di risorse orientato agli oggetti, come un driver di dispositivo o un’interfaccia condivisa di oggetti.

Quando uno sviluppatore vuole utilizzare una DLL già costruita, deve fare riferimento a una “libreria di esportazione” (* .LIB) creata dallo sviluppatore DLL quando ha creato la DLL, oppure deve caricare esplicitamente la DLL in fase di esecuzione e richiedere il indirizzo del punto di ingresso per nome tramite i meccanismi LoadLibrary () e GetProcAddress (). La maggior parte delle volte, il collegamento a un file LIB (che contiene semplicemente i metadati del linker per i punti di ingresso esportati dalla DLL) è il modo in cui le DLL vengono utilizzate. Il caricamento dinamico è tipicamente riservato all’implementazione di “polimorfismo” o “configurabilità di runtime” nei comportamenti del programma (accesso a componenti aggiuntivi o funzionalità definite successivamente, ovvero “plug-in”).

Il modo in cui Windows fa le cose può causare qualche confusione a volte; il sistema utilizza l’estensione .LIB per fare riferimento sia alle normali librerie statiche (archivi, come POSIX * .a file) che alle librerie “export stub” necessarie per associare un’applicazione a una DLL al momento del collegamento. Quindi, si dovrebbe sempre cercare di vedere se un file * .LIB ha lo stesso nome del file * .DLL; in caso contrario, è probabile che il file * .LIB sia un archivio di librerie statiche e non esporti i metadati di associazione per una DLL.

Sei corretto nel fatto che i file statici vengono copiati nell’applicazione al momento del collegamento e che i file condivisi vengono semplicemente verificati al momento del collegamento e caricati in fase di runtime.

La chiamata dlopen non è solo per oggetti condivisi, se l’applicazione desidera farlo a runtime per suo conto, altrimenti gli oggetti condivisi vengono caricati automaticamente all’avvio dell’applicazione. DLL e .so sono la stessa cosa. il dlopen esiste per aggiungere ancora più capacità di caricamento dinamico a grana fine per i processi. Non è necessario utilizzare dlopen per aprire / utilizzare le DLL, anche all’avvio dell’applicazione.