Dynamic-link library (Biblioteca de ligação dinâmica) ou DLL, é a implementação feita pela Microsoft para o conceito de bibliotecas compartilhadas nos sistemas operacionais Microsoft Windows e OS/2. Essas bibliotecas geralmente tem as extensões DLL, OCX (para bibliotecas que contêm controles ActiveX), ou DRV (para drivers de sistema legados).
Os formatos de arquivos para DLL são os mesmos dos arquivos executáveis para Windows. Assim como os executáveis (EXE), as DLL podem conter códigos, dados, e recursos (ícones, fontes, cursores, entre outros) em qualquer combinação.
No sentido amplo do termo, qualquer arquivo de dados com esse mesmo formato, pode ser chamado de DLL de recursos. Exemplos dessas DLL incluem bibliotecas de ícones, podendo ter a extensão ICL, e os arquivos de fontes, quem têm as extensões FON e FOT.
Histórico
O propósito original das DLL era economizar espaço em disco e memória necessária para aplicativos, armazenando-os localmente no disco rígido. Em uma biblioteca padrão não-compartilhada, trechos de código são adicionados ao programa que faz a chamada; se dois programas usam a mesma rotina, o código deve ser incluído em ambos. Ao contrário disso, códigos que vários aplicativos compartilham podem ser separados em uma DLL que existe como apenas um único arquivo, carregado apenas uma vez na memória durante o uso. Devido ao uso extensivo de DLL, as versões iniciais do Windows puderam rodar em máquinas com pouca memória.
As DLL proveem os benefícios comuns de bibliotecas compartilhadas, como a modularidade. Esta modularidade permite que alterações sejam feitas no código ou dados em uma DLL auto-contida, compartilhada por vários aplicativos, sem que qualquer modificação seja feita nos aplicativos em si. Essa forma básica de modularidade permite a criação de patches e service packs relativamente pequenos para grandes aplicativos, como Microsoft Office, Microsoft Visual Studio, e mesmo o próprio Microsoft Windows.
Outro benefício da modularidade é o uso de interfaces genéricas para plug-ins. Uma única interface pode ser desenvolvida para permitir que módulos novos e antigos possam sem integrados em aplicativos pré-existentes, sem qualquer modificação no próprio aplicativo. Este conceito de extensibilidade dinâmica é levado ao extremos com o ActiveX.
Com todos estes benefícios, vem também um problema significante, conhecido como "Inferno de DLLs", que ocorre quando vários aplicativos entram em conflito sobre qual versão de uma biblioteca deve ser utilizada. Estes conflitos podem ser resolvidos facilmente se forem colocadas as diferentes versões da DLL em conflito dentro das pastas dos aplicativos, em vez de uma pasta para todo o sistema; entretanto, essa solução anula a economia conseguida com o uso dessas bibliotecas. Atualmente o framework Microsoft .NET é apontado como uma solução para o problema do Inferno de DLLs ao permitir a coexistência de diferentes versões de uma mesma biblioteca. Com a quantidade de espaço em disco dos computadores atuais, esta pode ser uma abordagem razoável.
Funcionalidade
Gerenciamento de memória
Nos sistemas Win32, os arquivos DLL são organizados em seções. Cada seção tem seu próprio conjunto de atributos, como pode ser escrito ou somente-leitura, executável (para código) ou não-executável (para dados) e outros.
O código em uma DLL geralmente é compartilhado entre todos os processos que a utilizam; isto é, ela ocupa um espaço único na memória física, não ocupando espaço no arquivo de paginação. Se a memória física ocupada por uma seção de código for solicitado, seu conteúdo é descartado e mais tarde é recarregado diretamente do arquivo DLL quando necessário.
Ao contrário das seções de código, as seções de dados em geral são privadas, ou seja, cada processo que utiliza a DLL tem sua própria cópia de todos os dados da mesma. Opcionalmente as seções de dados podem ser compartilhadas, permitindo a comunicação entre os processos, através desta área de memória compartilhada. Entretanto, devido ao fato das restrições de usuários não serem aplicadas ao uso da memória das DLL compartilhadas, existe o risco de uma falha de segurança; por exemplo, um processo rodando sob uma conta de visitante, pode assim corromper outro processo que esteja rodando sob uma conta com privilégios. Este é um importante motivo para se evitar ao máximo o uso destas seções compartilhadas em DLL.
Se uma DLL é comprimida utilizando determinados empacotadores de executáveis (exemplo: UPX), todas as suas seções de código são marcadas com escrita-e-leitura, e serão não-compartilhadas. Seções de código com marcados com escrita-e-leitura, bem como seções privadas de dados, são privadas para cada processo. Desta forma, a compressão de DLL aumenta o consumo de memória e deve ser evitado quando estas contém seções de dados compartilhadas.
Resolução de símbolos e binding
Cada função exportada por uma DLL é identificada por um numeral ordinal e, opcionalmente, um nome. Similarmente, as funções podem ser importadas da DLL tanto pelo numeral quanto pelo nome. Esse numeral representa a posição do ponteiro de endereçamento das funções, na Tabela de Endereços de Exportação da DLL. É comum que as funções internas sejam exportadas apenas pelo numeral. Para a maioria das funções da API do Windows, apenas os nomes são preservados entre diferentes versões do sistema; os numerais são sujeitos a mudanças, portanto não são uma maneira confiável de se importar as funções da API.
A importação de funções pelo numeral alcança apenas uma pequena vantagem de performance em relação à importação por nome: as tabelas de exportação das DLL são ordenadas por nome, portanto uma busca binária pode ser usada para achar uma função. O índice do nome encontrado é usado então para achar o numeral na tabela de Ordinais de Exportação. Nos sistemas Windows de 16 bits, a tabela de nomes não era ordenada, tornando o atraso na busca por nomes sensivelmente maior.
Ainda há a possibilidade de fazer um binding de um executável a uma versão específica de uma DLL, para que os endereços das funções importadas sejam resolvidos em tempo de compilação. Para importações limitadas, o ligador salva a data e a soma de verificação da DLL à qual a importação é limitada. Em tempo de execução, o Windows checa se a mesma versão da biblioteca está em uso; caso esteja, o sistema não processa as importações, caso contrário, se a versão da biblioteca é diferente, o Windows faz o processamento das importações normalmente.
Executáveis criados dessa maneira carregam um pouco mais rápidos se eles rodarem no mesmo ambiente para o qual foram compilados, e exatamente o mesmo tempo se o ambiente é diferente, não existindo desvantagem em usar o binding para importações. Por exemplo, todos os aplicativos padrões no Windows são ligados as DLL das suas respectivas versões do sistema. Uma boa oportunidade de fazer o binding de um aplicativo a seu ambiente de destino é durante a instalação do mesmo.
Ligação explícita em tempo de execução
Arquivos DLL podem ser carregados explicitamente em tempo de execução, um processo chamado pela Microsoft como ligação dinâmica em tempo de execução, utilizando a função LoadLibrary (ou LoadLibraryEx) de sua API. A função GetProcAddress é usada para buscar por nome dos símbolos exportados, e a FreeLibrary — para descarregar a DLL. Essas funções são análogas às dlopen, dlsym, e dlclose na API padrão do POSIX.
Note que com a ligação implícita em tempo de execução, chamada pela Microsoft como ligação dinâmica em tempo de carregamento, se o arquivo DLLDLL ligados implicitamente pelo ligador em tempo de compilação. Por outro lado, com a ligação explícita em tempo de execução, os desenvolvedores tem a oportunidade de criar um tratamento para esse tipo de exceção. não for encontrado, o Windows mostrará uma mensagem de erro e não carregará o aplicativo. O desenvolvedor não tem maneiras de tratar a ausência de arquivos
A rotina de ligação explícita em tempo de execução é a mesma em qualquer linguagem, pois ela depende da API do Windows e não dos construtores das linguagens.
Considerações sobre compiladores e linguagens
Delphi
No cabeçalho do código fonte, a palavra reservada library no lugar de program. No fim do arquivo, as funções a serem exportadas são listadas na cláusula exports.
O Delphi não requer que os arquivos LIB importem funções de DLL. Para fazer uma ligação a uma DLL, a palavra reservada external é usada na declaração da função.
Microsoft Visual Basic
No Visual Basic (VB), apenas a ligação em tempo de execução é suportada, porém, com o uso das funções LoadLibrary e GetProcAddress, declarações de funções importadas são permitidas.
Ao importar funções de DLL através de declarações, o VB irá gerar um erro em tempo de execução se o arquivo DLL não for encontrado. O desenvolvedor pode então tratar o erro apropriadamente.
C e C++
O Microsoft Visual C++ (MSVC) provê várias extensões ao C++ padrão, o que permite que funções sejam importadas ou exportadas diretamente no código. Este modelo foi adotado por outros compiladores C e C++ para Windows, incluindo a versão Windows do GCC. Essas extensões usam o atributo __declspec[1] antes de uma declaração de função. Quando nomes externos seguem as convenções de nomes do C, eles também devem ser declarados como extern "C" no código em C++, para prevenir o uso das convenções de nomes do C++.
Além da especificação das funções como importadas ou exportadas ao usar os atributos da __declspec, elas podem ser listadas nas seções IMPORT ou EXPORTS do arquivo DEF usado pelo projeto. Este arquivo é processado pelo ligador, e não pelo compilador, e portanto não é específico do C++.
A compilação de DLL gerará um arquivo DLL e um LIB. O LIB é usado para a ligação de uma DLL em tempo de compilação; ele não é necessário para a ligação em tempo de execução. A não ser que sua DLL seja um servidor COM, o arquivo DLL deve ser colocado em um dos diretórios listados na variável de ambiente PATH, ou no diretório de sistema padrão, ou no mesmo diretório do aplicativo. As DLL servidores COM são registradas usando o regsvr32.exe
, que coloca a localização e seu ID global único (GUID) no registro. Os programas podem então usar a DLL se buscarem pelo seu GUID no registro para acharem sua localização.
Exemplos de programas
Criando DLL
Os exemplos seguintes mostram as maneiras específicas de exportação de DLL.
- Delphi
library Exemplo;
// Função que soma 2 números
function SomaNumeros(a, b: Double): Double; cdecl;
begin
SomaNumeros := a + b
end;
// Exporta esta função
exports
SomaNumeros;
// código de inicialização da DLL: nenhuma função especial é necessária
begin
end.
- C e C++
#include
// Exporta esta função
extern "C" __declspec(dllexport) double SomaNumeros(double a, double b);
// função de inicialização da DLL
BOOL APIENTRY
DllMain(HANDLE hModule, DWORD dwReason, LPVOID lpReserved)
{
return TRUE;
}
// Função que soma dois números
double SomaNumeros(double a, double b)
{
return a + b;
}
Usando funções importadas de DLL
Os exemplos a seguir mostram como usar funções específicas das linguagens para importar as funções de DLL em tempo de compilação.
- Delphi
program Exemplo;
{$APPTYPE CONSOLE}
// Importa função que soma dois números
function SomaNumeros(a, b: Double): Double; cdecl; external 'Exemplo.dll';
var resultado: Double;
begin
resultado := SomaNumeros(1, 2);
Writeln('O resultado é: ', resultado)
end.
- C e C++
#include
#include
// Importa função que soma dois números
extern "C" __declspec(dllimport) double SomaNumeros(double a, double b);
int main(int argc, char **argv)
{
double resultado = SomaNumeros(1, 2);
printf("O resultado é: %f\n", resultado);
return 0;
}
Usando ligação explícita em tempo de execução
Os exemplos a seguir mostram exemplos de como usar o carregamento em tempo de execução e auxílios de ligação usando bindings específicas das linguagens para a API WIN32.
- Microsoft Visual Basic
Option Explicit
Declare Function SomaNumeros Lib "Exemplo.dll" _
(ByVal a As Double, ByVal b As Double) As Double
Sub Main()
Dim Resultado As Double
Resultado = SomaNumeros(1, 2)
Debug.Print "O resultado é: " & Resultado
End Sub
- C e C++
#include
#include
// Assinatura da função da DLL
typedef double (*importFunction)(double, double);
int main(int argc, char **argv)
{
importFunction SomaNumeros;
double resultado;
// Carrega arquivo DLL
HINSTANCE hinstLib = LoadLibrary("Exemplo.dll");
if (hinstLib == NULL) {
printf("ERRO: não foi possível carregar a DLL\n");
return 1;
}
// Obtém o ponteiro da função
SomaNumeros = (importFunction)GetProcAddress(hinstLib, "SomaNumeros");
if (SomaNumeros == NULL) {
printf("ERRO: não foi possível achar a função na DLL\n");
FreeLibrary(hinstLib);
return 1;
}
// Chama função.
resultado = SomaNumeros(1, 2);
// Descarrega arquivo DLL
FreeLibrary(hinstLib);
// Mostra o resultado
printf("O resultado é: %f\n", resultado);
return 0;
}
Component Object Model
O Component Object Model (COM) estende o conceito de DLL para a programação orientada a objetos. Objetos podem ser chamados de outro processo, ou hospedados em outra máquina. Objetos COM tem GUID únicos e podem ser usados para implementar back-ends poderosos para front-ends de GUI simples como Visual Basic e ASP. Eles podem também ser programados em linguagens de scripting. Objetos COM são mais complexos de ser criados e usados em comparação com as DLL.
- Para baixar arquivos DLL, acesse o site: http://www.dll-files.com/
0 comentários:
Enviar um comentário