Como funciona a biblioteca de importação? Detalhes?

88

Eu sei que isso pode parecer bastante básico para geeks. Mas quero deixar isso bem claro.

Quando eu quero usar uma DLL Win32, geralmente eu apenas chamo as APIs como LoadLibrary () e GetProcAdderss (). Mas recentemente, estou desenvolvendo com DirectX9 e preciso adicionar os arquivos d3d9.lib , d3dx9.lib , etc.

Já ouvi o suficiente que LIB é para links estáticos e DLLs para links dinâmicos.

Portanto, meu entendimento atual é que o LIB contém a implementação dos métodos e está estaticamente vinculado no momento do link como parte do arquivo EXE final. Enquanto o DLL é carregado dinamicamente em tempo de execução e não faz parte do arquivo EXE final.

Mas, às vezes, alguns arquivos LIB vêm com os arquivos DLL, então:

  • Para que servem esses arquivos LIB?
  • Como eles alcançam o que pretendem?
  • Existe alguma ferramenta que me permita inspecionar os componentes internos desses arquivos LIB?

Atualização 1

Após verificar a Wikipedia, lembro que esses arquivos LIB são chamados de biblioteca de importação . Mas estou querendo saber como isso funciona com meu aplicativo principal e as DLLs a serem carregadas dinamicamente.

Atualização 2

Exatamente como RBerteig disse, há alguns códigos de stub nos arquivos LIB nascidos com as DLLs. Portanto, a sequência de chamada deve ser assim:

Meu aplicativo principal -> stub no LIB -> DLL de destino real

Então, quais informações devem estar contidas nesses LIBs? Eu poderia pensar no seguinte:

  • O arquivo LIB deve conter o caminho completo da DLL correspondente; Portanto, a DLL pode ser carregada pelo tempo de execução.
  • O endereço relativo (ou deslocamento do arquivo?) De cada ponto de entrada do método de exportação DLL deve ser codificado no stub; Assim, saltos / chamadas de métodos corretos podem ser feitos.

Estou certo sobre isso? Existe algo mais?

BTW: Existe alguma ferramenta que pode inspecionar uma biblioteca de importação? Se eu puder ver, não haverá mais dúvidas.

smwikipedia
fonte
4
Vejo que ninguém respondeu à última parte da sua pergunta, que diz respeito às ferramentas que podem inspecionar uma biblioteca de importação. Com o Visual C ++, existem pelo menos duas maneiras de fazer isso: lib /list xxx.libe link /dump /linkermember xxx.lib. Veja esta pergunta sobre Stack Overflow .
Alan
Além disso, dumpbin -headers xxx.libfornece algumas informações mais detalhadas, em comparação com os utilitários libe link.
m_katsifarakis

Respostas:

102

A vinculação a um arquivo DLL pode ocorrer implicitamente em tempo de link de compilação ou explicitamente em tempo de execução. De qualquer forma, a DLL acaba sendo carregada no espaço de memória dos processos e todos os seus pontos de entrada exportados ficam disponíveis para o aplicativo.

Se usado explicitamente em tempo de execução, você usa LoadLibrary()e GetProcAddress()para carregar manualmente a DLL e obter ponteiros para as funções que precisa chamar.

Se vinculados implicitamente quando o programa é criado, os stubs para cada exportação de DLL usada pelo programa são vinculados ao programa a partir de uma biblioteca de importação e esses stubs são atualizados à medida que o EXE e a DLL são carregados quando o processo é iniciado. (Sim, simplifiquei mais do que um pouco aqui ...)

Esses stubs precisam vir de algum lugar e, na cadeia de ferramentas da Microsoft, eles vêm de uma forma especial de arquivo .LIB chamada biblioteca de importação . O .LIB necessário geralmente é criado ao mesmo tempo que a DLL e contém um esboço para cada função exportada da DLL.

Surpreendentemente, uma versão estática da mesma biblioteca também seria enviada como um arquivo .LIB. Não há uma maneira trivial de diferenciá-los, exceto que os LIBs que são bibliotecas de importação para DLLs geralmente serão menores (geralmente muito menores) do que o LIB estático correspondente seria.

Se você usa o conjunto de ferramentas GCC, por acaso, você não precisa importar bibliotecas para corresponder às suas DLLs. A versão do vinculador Gnu transferida para o Windows entende DLLs diretamente e pode sintetizar quase todos os stubs necessários na hora.

Atualizar

Se você simplesmente não consegue resistir a saber onde todas as porcas e parafusos realmente estão e o que realmente está acontecendo, sempre há algo no MSDN para ajudar. Artigo de Matt Pietrek Uma análise detalhada do formato de arquivo executável portátil Win32 é uma visão geral muito completa do formato do arquivo EXE e de como ele é carregado e executado. Até foi atualizado para cobrir o .NET e muito mais, desde que apareceu originalmente na MSDN Magazine ca. 2002

Além disso, pode ser útil saber como aprender exatamente quais DLLs são usadas por um programa. A ferramenta para isso é Dependency Walker, também conhecido como Depends.exe. Uma versão dele está incluída no Visual Studio, mas a versão mais recente está disponível com seu autor em http://www.dependencywalker.com/ . Ele pode identificar todas as DLLs que foram especificadas no momento do link (carregamento antecipado e atraso no carregamento) e também pode executar o programa e observar se há DLLs adicionais carregadas no tempo de execução.

Atualização 2

Eu reformulei parte do texto anterior para esclarecê-lo na releitura e para usar os termos da arte vinculação implícita e explícita para consistência com o MSDN.

Portanto, temos três maneiras pelas quais as funções da biblioteca podem ser disponibilizadas para serem usadas por um programa. A pergunta de acompanhamento óbvia é: "Como escolher qual caminho?"

A vinculação estática é como a maior parte do programa em si é vinculada. Todos os seus arquivos de objeto são listados e coletados juntos no arquivo EXE pelo vinculador. Ao longo do caminho, o vinculador cuida de tarefas menores, como consertar referências a símbolos globais para que seus módulos possam chamar as funções uns dos outros. As bibliotecas também podem ser vinculadas estaticamente. Os arquivos de objeto que constituem a biblioteca são coletados por um bibliotecário em um arquivo .LIB no qual o vinculador procura por módulos contendo símbolos necessários. Um efeito da vinculação estática é que apenas os módulos da biblioteca usados ​​pelo programa são vinculados a ela; outros módulos são ignorados. Por exemplo, a biblioteca matemática tradicional C inclui muitas funções de trigonometria. Mas se você ligar contra ele e usarcos(), você não acaba com uma cópia do código para sin()ou a tan()menos que também tenha chamado essas funções. Para grandes bibliotecas com um rico conjunto de recursos, essa inclusão seletiva de módulos é importante. Em muitas plataformas, como sistemas embarcados, o tamanho total do código disponível para uso na biblioteca pode ser grande em comparação com o espaço disponível para armazenar um executável no dispositivo. Sem a inclusão seletiva, seria mais difícil gerenciar os detalhes da construção de programas para essas plataformas.

No entanto, ter uma cópia da mesma biblioteca em todos os programas em execução sobrecarrega o sistema que normalmente executa muitos processos. Com o tipo certo de sistema de memória virtual, as páginas de memória com conteúdo idêntico precisam existir apenas uma vez no sistema, mas podem ser usadas por vários processos. Isso cria um benefício para aumentar as chances de que as páginas que contêm código sejam provavelmente idênticas a alguma página em tantos outros processos em execução quanto possível. Mas, se os programas se vinculam estaticamente à biblioteca de tempo de execução, então cada um tem uma combinação diferente de funções, cada uma disposta no mapa de memória de processos em locais diferentes, e não há muitas páginas de código compartilháveis, a menos que seja um programa que por si só é executar em mais de um processo. Portanto, a ideia de um DLL ganhou outra vantagem importante.

Uma DLL para uma biblioteca contém todas as suas funções, prontas para serem usadas por qualquer programa cliente. Se muitos programas carregarem essa DLL, todos eles poderão compartilhar suas páginas de código. Todo mundo ganha. (Bem, até você atualizar uma DLL com a nova versão, mas isso não faz parte desta história. Google DLL Hell para esse lado da história.)

Portanto, a primeira grande escolha a fazer ao planejar um novo projeto é entre a vinculação dinâmica e estática. Com a vinculação estática, você tem menos arquivos para instalar e fica imune à atualização de uma DLL usada por terceiros. No entanto, seu programa é maior e não é tão bom cidadão do ecossistema do Windows. Com a vinculação dinâmica, você tem mais arquivos para instalar, pode ter problemas com terceiros atualizando uma DLL que você usa, mas geralmente você está sendo mais amigável com outros processos no sistema.

Uma grande vantagem de uma DLL é que ela pode ser carregada e usada sem recompilar ou mesmo religar o programa principal. Isso pode permitir que um provedor de biblioteca de terceiros (pense na Microsoft e no tempo de execução C, por exemplo) consertar um bug em sua biblioteca e distribuí-lo. Depois que um usuário final instala a DLL atualizada, ele imediatamente obtém o benefício dessa correção de bug em todos os programas que usam essa DLL. (A menos que quebre as coisas. Veja DLL Hell.)

A outra vantagem vem da distinção entre carregamento implícito e explícito. Se você fizer um esforço extra de carregamento explícito, a DLL pode nem mesmo ter existido quando o programa foi escrito e publicado. Isso permite mecanismos de extensão que podem descobrir e carregar plug-ins, por exemplo.

RBerteig
fonte
3
Excluindo minha postagem e votando nisso, porque você explica as coisas de uma maneira melhor do que eu;) Boa resposta.
Em
2
@RBerteig: Obrigado por sua ótima resposta. Basta um pouco de correção, de acordo com o aqui ( msdn.microsoft.com/en-us/library/9yd93633.aspx ), lá está 2 tipos de dinâmica com links para uma DLL, em tempo de carga vinculação implícita e tempo de execução vinculação explícita . Sem vinculação em tempo de compilação . Agora estou me perguntando qual é a diferença entre a vinculação estática tradicional (vinculação a um arquivo * .lib que contém a implementação completa) e a vinculação dinâmica em tempo de carregamento a uma DLL (por meio de uma biblioteca de importação)?
smwikipedia
1
Continue: Quais são os prós e os contras da vinculação estática e da vinculação dinâmica em tempo de carregamento ? Parece que essas duas abordagens carregam todos os arquivos necessários no espaço de endereço no início de um processo. Por que precisamos de 2 deles? Obrigado.
smwikipedia
1
talvez você possa usar uma ferramenta como "objdump" para espiar dentro de um arquivo .lib e descobrir se é uma biblioteca de importação ou uma verdadeira biblioteca estática. no Linux durante a compilação cruzada para um destino do Windows, é possível executar 'ar' ou 'nm' nos arquivos .a (versão mingw dos arquivos .lib) e observar que as bibliotecas de importação têm nomes de arquivo .o genéricos e nenhum código (apenas uma instrução 'jmp'), enquanto as bibliotecas estáticas têm muitas funções e código dentro.
vestir o dia
1
Pequena correção: você também pode vincular implicitamente em tempo de execução. O suporte do vinculador para DLLs carregadas com atraso explica isso em detalhes. Isso é útil se você deseja alterar dinamicamente o caminho de pesquisa da DLL ou lidar com falhas de resolução de importação (para oferecer suporte a novos recursos do sistema operacional, mas ainda executar em versões mais antigas, por exemplo).
Inspecionável,
5

Esses arquivos de biblioteca de importação .LIB são usados ​​na seguinte propriedade do projeto Linker->Input->Additional Dependencies, ao construir um monte de dlls que precisam de informações adicionais no momento do link que são fornecidas pelos arquivos .LIB da biblioteca de importação. No exemplo abaixo, para não obter erros do vinculador, preciso fazer referência às dll's A, B, C e D por meio de seus arquivos lib. (nota para o vinculador encontrar esses arquivos, você pode precisar incluir seu caminho de implantação, caso Linker->General->Additional Library Directoriescontrário, você obterá um erro de compilação sobre ser incapaz de encontrar qualquer um dos arquivos lib fornecidos.)

Ligante-> Entrada-> Dependências Adicionais

Se sua solução for construir todas as bibliotecas dinâmicas, você pode ter conseguido evitar essa especificação de dependência explícita, confiando nos sinalizadores de referência expostos na Common Properties->Framework and Referencescaixa de diálogo. Esses sinalizadores parecem fazer a vinculação automaticamente em seu nome usando os arquivos * .lib. Estrutura e referências

No entanto, isso é como diz as Propriedades Comuns , que não é uma configuração ou plataforma específica. Se você precisar oferecer suporte a um cenário de construção mista como em nosso aplicativo, tínhamos uma configuração de construção para renderizar uma construção estática e uma configuração especial que construía uma construção restrita de um subconjunto de montagens que foram implantadas como bibliotecas dinâmicas. Eu tinha usado os sinalizadores Use Library Dependency Inputs e Link Library Dependenciesdefinidos como verdadeiros em vários casos para fazer as coisas serem construídas e, posteriormente, percebendo como simplificar as coisas, mas ao apresentar meu código às construções estáticas, introduzi uma tonelada de avisos do linker e a construção foi incrivelmente lenta para as construções estáticas. Acabei introduzindo um monte desses tipos de avisos ...

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

E acabei usando a especificação manual de Additional Dependenciespara satisfazer o vinculador para as compilações dinâmicas enquanto mantinha os construtores estáticos felizes por não usar uma propriedade comum que os tornava mais lentos. Quando implanto a compilação de subconjunto dinâmico, implanto apenas os arquivos dll, pois esses arquivos lib são usados ​​apenas no tempo de link, não no tempo de execução.

Jxramos
fonte
3

Existem três tipos de bibliotecas: bibliotecas estáticas, compartilhadas e carregadas dinamicamente.

As bibliotecas estáticas são vinculadas ao código na fase de vinculação, portanto, elas estão realmente no executável, ao contrário da biblioteca compartilhada, que tem apenas stubs (símbolos) para procurar no arquivo da biblioteca compartilhada, que é carregado em tempo de execução antes do a função principal é chamada.

As carregadas dinamicamente são muito parecidas com as bibliotecas compartilhadas, exceto que são carregadas quando e se a necessidade surgir pelo código que você escreveu.

Zoltán Szőcs
fonte
@ Obrigado zacsek. Mas não tenho certeza sobre sua declaração sobre biblioteca compartilhada.
smwikipedia
@smwikipedia: Linux os tem, eu os uso, então eles definitivamente existem. Leia também: en.wikipedia.org/wiki/Library_(computing)
Zoltán Szőcs
3
É uma diferença sutil. Bibliotecas compartilhadas e dinâmicas são arquivos DLL. A diferença é quando eles são carregados. Bibliotecas compartilhadas são carregadas pelo sistema operacional junto com o EXE. Bibliotecas dinâmicas são carregadas por chamada de código LoadLibrary()e as APIs relacionadas.
RBerteig
Li em [1] que o DLL é a implementação do conceito de Biblioteca Compartilhada da Microsoft. [1]: en.wikipedia.org/wiki/Dynamic-link_library#Import_libraries
smwikipedia
Eu discordo que seja uma diferença sutil, do ponto de vista da programação faz uma grande diferença se a biblioteca compartilhada é carregada dinamicamente ou não (se for carregada dinamicamente, então você tem que adicionar código clichê para acessar as funções).
Zoltán Szőcs