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.
lib /list xxx.lib
elink /dump /linkermember xxx.lib
. Veja esta pergunta sobre Stack Overflow .