Diferença entre objetos compartilhados (.so), bibliotecas estáticas (.a) e DLLs (.so)?


273

Estive envolvido em algum debate sobre bibliotecas no Linux e gostaria de confirmar algumas coisas.

Entendo (por favor, corrija-me se estiver errado e editarei minha postagem mais tarde), existem duas maneiras de usar bibliotecas ao criar um aplicativo:

  1. Bibliotecas estáticas (arquivos .a): no momento do link, uma cópia de toda a biblioteca é colocada no aplicativo final para que as funções na biblioteca estejam sempre disponíveis para o aplicativo de chamada.
  2. Objetos compartilhados (arquivos .so): no momento do link, o objeto é apenas verificado em relação à sua API por meio do arquivo de cabeçalho correspondente (.h). A biblioteca não é realmente usada até o tempo de execução, onde é necessária.

A vantagem óbvia das bibliotecas estáticas é que elas permitem que o aplicativo inteiro seja independente, enquanto o benefício das bibliotecas dinâmicas é que o arquivo ".so" pode ser substituído (ou seja: caso precise ser atualizado devido a uma segurança) bug) sem exigir que o aplicativo base seja recompilado.

Ouvi algumas pessoas fazerem uma distinção entre objetos compartilhados e bibliotecas vinculadas dinâmicas (DLLs), mesmo que sejam arquivos ".so". Existe alguma distinção entre objetos compartilhados e DLLs quando se trata de desenvolvimento de C / C ++ no Linux ou em qualquer outro sistema operacional compatível com POSIX (por exemplo: MINIX, UNIX, QNX, etc)? Disseram-me que uma diferença importante (até agora) é que os objetos compartilhados são usados ​​apenas em tempo de execução, enquanto os DLLs devem ser abertos primeiro usando a chamada dlopen () no aplicativo.

Por fim, também ouvi alguns desenvolvedores mencionarem "arquivos compartilhados", que, a meu ver, também são bibliotecas estáticas, mas nunca são usados ​​diretamente por um aplicativo. Em vez disso, outras bibliotecas estáticas serão vinculadas aos "arquivos compartilhados" para extrair algumas (mas não todas) funções / recursos do arquivo compartilhado para a biblioteca estática que está sendo construída.

Agradecemos desde já a sua ajuda.

Atualizar


No contexto em que esses termos foram fornecidos a mim, eram termos efetivamente errôneos usados ​​por uma equipe de desenvolvedores do Windows que precisavam aprender Linux. Tentei corrigi-los, mas as normas de linguagem (incorretas) ficaram presas.

  1. Objeto compartilhado: uma biblioteca que é automaticamente vinculada a um programa quando o programa é iniciado e existe como um arquivo independente. A biblioteca é incluída na lista de links em tempo de compilação (ou seja: LDOPTS+=-lmylibpara um arquivo de biblioteca chamado mylib.so). A biblioteca deve estar presente no momento da compilação e quando o aplicativo é iniciado.
  2. Biblioteca estática: uma biblioteca que é mesclada no próprio programa real no momento da construção para um único aplicativo (maior) que contém o código do aplicativo e o código da biblioteca que é automaticamente vinculado a um programa quando o programa é criado e o binário final que contém ambos o programa principal e a própria biblioteca existem como um único arquivo binário independente. A biblioteca é incluída na lista de links em tempo de compilação (ou seja: LDOPTS+=-lmylibpara um arquivo de biblioteca chamado mylib.a). A biblioteca deve estar presente no momento da compilação.
  3. DLL: Essencialmente o mesmo que um objeto compartilhado, mas em vez de ser incluída na lista de vinculação no momento da compilação, a biblioteca é carregada via dlopen()/ dlsym()comandos para que a biblioteca não precise estar presente no tempo de compilação para a compilação do programa. Além disso, a biblioteca não precisa estar presente (necessariamente) na inicialização do aplicativo ou no tempo de compilação , pois é necessária apenas no momento em que as chamadas dlopen/ dlsymsão feitas.
  4. Arquivo compartilhado: essencialmente o mesmo que uma biblioteca estática, mas é compilado com os sinalizadores "export-shared" e "-fPIC". A biblioteca é incluída na lista de links em tempo de compilação (ou seja: LDOPTS+=-lmylibSpara um arquivo de biblioteca chamado mylibS.a). A distinção entre os dois é que esse sinalizador adicional é necessário se um objeto compartilhado ou DLL desejar vincular estaticamente o archive compartilhado em seu próprio código E poder disponibilizar as funções no objeto compartilhado para outros programas, em vez de apenas usá-los interno para a DLL. Isso é útil no caso em que alguém fornece uma biblioteca estática e você deseja reembalá-la como uma SO. A biblioteca deve estar presente no momento da compilação.

Atualização Adicional

A distinção entre " DLL" e " shared library" foi apenas um coloquialismo (preguiçoso, impreciso) na empresa em que trabalhei na época (os desenvolvedores do Windows foram forçados a mudar para o desenvolvimento do Linux e o termo travado), seguindo as descrições mencionadas acima.

Além disso, o Sliteral " " à direita após o nome da biblioteca, no caso de "arquivos compartilhados", era apenas uma convenção usada nessa empresa e não no setor em geral.


14
Para .aarquivos, o "a" na verdade significa "archove" e é simplesmente um arquivo de arquivos de objetos. Os vinculadores modernos devem ser bons o suficiente para não precisar incluir a biblioteca while, apenas os arquivos de objetos no archive necessário e podem até usar as seções de código / dados nos arquivos de objetos que são referenciados.
Algum programador,

4
DLL é apenas a terminologia do Windows. Não é usado em unices.
G .. GitHub Pare de ajudar o



2
@DevNull "arch i ve" é claro. :)
Algum programador,

Respostas:


94

Eu sempre pensei que DLLs e objetos compartilhados são apenas termos diferentes para a mesma coisa - o Windows os chama de DLLs, enquanto nos sistemas UNIX eles são objetos compartilhados, com o termo geral - biblioteca vinculada dinamicamente - cobrindo os dois (até a função de abrir um .so no UNIX é chamado dlopen()após 'biblioteca dinâmica').

Na verdade, eles são vinculados apenas na inicialização do aplicativo, no entanto, sua noção de verificação no arquivo de cabeçalho está incorreta. O arquivo de cabeçalho define protótipos necessários para compilar o código que usa a biblioteca, mas no momento do link, o vinculador procura dentro da própria biblioteca para garantir que as funções necessárias estejam realmente lá. O vinculador precisa encontrar os corpos da função em algum lugar no momento do link ou isso gera um erro. Também faz isso em tempo de execução, porque, como você aponta corretamente, a própria biblioteca pode ter sido alterada desde que o programa foi compilado. É por isso que a estabilidade da ABI é tão importante nas bibliotecas da plataforma, pois a mudança da ABI é o que quebra os programas existentes compilados em versões mais antigas.

As bibliotecas estáticas são apenas pacotes de arquivos de objetos diretamente do compilador, exatamente como aqueles que você está construindo como parte da compilação do seu projeto, para que sejam atraídos e alimentados ao vinculador exatamente da mesma maneira, e bits não utilizados são caiu exatamente da mesma maneira.


1
Por que alguns projetos que eu vejo no Linux precisam usar a chamada dlopen () para acessar as funções em um arquivo ".so" e alguns não precisam fazer isso? Obrigado, a propósito!
Cloud

9
Aqueles que não fazem isso recebem as funções entregues pelo carregador de processos, ou seja, o elf loader do linux. O dlopen existe se o aplicativo quiser abrir e usar um .so ou .dll que não estava lá no momento da compilação ou apenas adicionar funcionalidade extra, como plug-ins.
Raphael

Mas o aplicativo não será compilado se o .so não estiver presente no tempo de compilação? É possível forçar o vinculador a criar apenas o programa final sem o .so presente? Obrigado.
Cloud

1
Eu acredito que depende de como você usa as funções de .so, mas aqui meu conhecimento sobre isso pára: / Boas perguntas.
Raphael

1
Em relação ao dlopen () e sua família de funções, entendo que isso é usado para abrir / fechar programaticamente uma dll, para que não precise ser carregada na memória durante toda a execução do aplicativo. Caso contrário, você deve informar ao vinculador em seus argumentos de linha de comando (também conhecido como seu makefile) que deseja que a biblioteca seja carregada. Ele será carregado no tempo de execução e permanecerá carregado na memória até o aplicativo sair. Há mais coisas que podem acontecer no nível do sistema operacional, mas isso é basicamente o que acontece no que diz respeito ao seu aplicativo.
Taylor Price

198

Uma biblioteca estática (.a) é uma biblioteca que pode ser vinculada diretamente ao executável final produzido pelo vinculador, está contida nele e não há necessidade de incluir a biblioteca no sistema em que o executável será implantado.

Uma biblioteca compartilhada (.so) é uma biblioteca vinculada, mas não incorporada ao executável final; portanto, será carregada quando o executável for iniciado e precisará estar presente no sistema em que o executável está implantado.

Uma biblioteca de vínculo dinâmico no windows (.dll) é como uma biblioteca compartilhada (.so) no linux, mas existem algumas diferenças entre as duas implementações relacionadas ao sistema operacional (Windows x Linux):

Uma DLL pode definir dois tipos de funções: exportada e interna. As funções exportadas devem ser chamadas por outros módulos, bem como de dentro da DLL onde estão definidas. As funções internas costumam ser chamadas apenas de dentro da DLL em que estão definidas.

Uma biblioteca SO no Linux não precisa de uma declaração de exportação especial para indicar símbolos exportáveis, pois todos os símbolos estão disponíveis para um processo de interrogação.


1
+1 boa explicação simples. Se uma função é declarada "Interna" em uma DLL, isso significa que ela não pode ser chamada de fora da biblioteca?
Mike

23
Não é necessariamente verdade que todos os símbolos estão disponíveis em uma biblioteca SO. Símbolos ocultos são possíveis e recomendados, porque não há uma boa razão para os usuários da biblioteca verem todos os seus símbolos.
Zan Lynx

3
FYI: o g ++ possui __attribute__sintaxe para 'exportar' seletivamente os símbolos:#define DLLEXPORT __attribute__ ((visibility("default"))) #define DLLLOCAL __attribute__ ((visibility("hidden")))
Brian Haak

33

Eu posso elaborar os detalhes das DLLs no Windows para ajudar a esclarecer esses mistérios para meus amigos aqui em * NIX-land ...

Uma DLL é como um arquivo de Objeto Compartilhado. Ambas são imagens, prontas para carregar na memória pelo carregador de programa do respectivo SO. As imagens são acompanhadas por vários bits de metadados para ajudar os vinculadores e carregadores a fazer as associações necessárias e usar a biblioteca de códigos.

As DLLs do Windows têm uma tabela de exportação. As exportações podem ser por nome ou por posição da tabela (numérica). O último método é considerado "old school" e é muito mais frágil - a reconstrução da DLL e a alteração da posição de uma função na tabela terminam em desastre, enquanto não há problema real se a vinculação dos pontos de entrada for pelo nome. Portanto, esqueça isso como um problema, mas esteja ciente de que ele existe se você trabalha com código de "dinossauro", como bibliotecas de fornecedores de terceiros.

As DLLs do Windows são criadas compilando e vinculando, da mesma forma que você faria para um EXE (aplicativo executável), mas a DLL não deve ser autônoma, assim como uma SO deve ser usada por um aplicativo, seja por carregamento dinâmico ou por ligação de tempo de link (a referência ao SO está incorporada nos metadados do binário do aplicativo e o carregador do programa do SO carrega automaticamente os SOs referenciados). As DLLs podem fazer referência a outras DLLs, assim como as SOs podem fazer referência a outras SOs.

No Windows, as DLLs disponibilizarão apenas pontos de entrada específicos. Estes são chamados de "exportações". O desenvolvedor pode usar uma palavra-chave especial do compilador para tornar um símbolo visível externamente (para outros vinculadores e o carregador dinâmico) ou as exportações podem ser listadas em um arquivo de definição de módulo que é usado no momento do link quando a própria DLL é sendo criado. A prática moderna é decorar a definição da função com a palavra-chave para exportar o nome do símbolo. Também é possível criar arquivos de cabeçalho com palavras-chave que declararão esse símbolo como um a ser importado de uma DLL fora da unidade de compilação atual. Procure as palavras-chave __declspec (dllexport) e __declspec (dllimport) para obter mais informações.

Um dos recursos interessantes das DLLs é que elas podem declarar uma função de manipulador padrão "ao carregar / descarregar". Sempre que a DLL é carregada ou descarregada, ela pode executar alguma inicialização ou limpeza, conforme o caso. Isso é muito bom para ter uma DLL como um gerenciador de recursos orientado a objetos, como um driver de dispositivo ou interface de objeto compartilhado.

Quando um desenvolvedor deseja usar uma DLL já criada, deve fazer referência a uma "biblioteca de exportação" (* .LIB) criada pelo desenvolvedor da DLL quando criou a DLL ou carregar explicitamente a DLL no tempo de execução e solicitar o endereço do ponto de entrada por nome por meio dos mecanismos LoadLibrary () e GetProcAddress (). Na maioria das vezes, vincular um arquivo LIB (que simplesmente contém os metadados do vinculador para os pontos de entrada exportados da DLL) é a maneira como as DLLs são usadas. O carregamento dinâmico é reservado normalmente para a implementação de "polimorfismo" ou "configurabilidade de tempo de execução" nos comportamentos do programa (acessando complementos ou funcionalidades definidas posteriormente, também conhecidas como "plugins").

A maneira como o Windows faz as coisas pode causar alguma confusão às vezes; o sistema usa a extensão .LIB para se referir às bibliotecas estáticas normais (arquivos, como arquivos POSIX * .a) e às bibliotecas "export stub" necessárias para vincular um aplicativo a uma DLL no momento do link. Portanto, deve-se sempre verificar se um arquivo * .LIB possui um arquivo * .DLL com o mesmo nome; caso contrário, é provável que o arquivo * .LIB seja um arquivo estático da biblioteca e não exporte os metadados de ligação para uma DLL.


4

Você está certo de que os arquivos estáticos são copiados para o aplicativo no momento do link e que os arquivos compartilhados são apenas verificados no momento do link e carregados no tempo de execução.

A chamada dlopen não é apenas para objetos compartilhados, se o aplicativo desejar fazê-lo em tempo de execução em seu nome, caso contrário, os objetos compartilhados serão carregados automaticamente quando o aplicativo for iniciado. DLLS e .so são a mesma coisa. o dlopen existe para adicionar ainda mais recursos de carregamento dinâmico de granulação fina aos processos. Você não precisa usar o dlopen para abrir / usar as DLLs, o que acontece também na inicialização do aplicativo.


Qual seria um exemplo do uso de dlopen () para obter mais controle de carregamento? Se o SO / DLL for carregado automaticamente na inicialização, o dlopen () fecha e o reabre com permissões ou restrições diferentes, por exemplo? Obrigado.
Cloud

1
Eu acredito que o dlopen é para plugins ou funcionalidade semelhante. As permissões / restrições devem ser as mesmas do carregamento automático e, de qualquer maneira, um dlopen carregará recursivamente as bibliotecas dependentes.
Raphael

DLL e não.so são exatamente a mesma coisa. Veja esta resposta
Basile Starynkevitch
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.