Vincular um executável a uma DLL

Um arquivo executável vincula (ou carrega) uma DLL de duas maneiras:

  • Vinculação implícita, em que o sistema operacional carrega a DLL ao mesmo tempo que o executável que a usa. O executável do cliente chama as funções exportadas da DLL da mesma forma que se as funções estivessem estaticamente vinculadas e contidas no executável. Às vezes, a vinculação implícita é conhecida como carga estática ou vinculação dinâmica de tempo de carga.

  • Vinculação explícita, em que o sistema operacional carrega a DLL sob demanda no runtime. Um executável que usa uma DLL por vinculação explícita deve carregar e descarregar explicitamente a DLL. Ele também deve configurar um ponteiro de função para acessar cada função que usa da DLL. Ao contrário das chamadas para funções em uma biblioteca vinculada estaticamente, ou em uma DLL vinculada implicitamente, o executável do cliente deve chamar as funções exportadas em uma DLL explicitamente vinculada por meio de ponteiros de função. Às vezes, a vinculação explícita é conhecida como carga dinâmica ou vinculação dinâmica em tempo de execução.

Um executável pode usar qualquer método de vinculação para vincular à mesma DLL. Além disso, esses métodos não são mutuamente exclusivos; um executável pode vincular implicitamente a uma DLL e outro pode ser anexado explicitamente a ela.

Determine qual método de vinculação usar

Usar a vinculação implícita ou a vinculação explícita é uma decisão de arquitetura que você deve tomar para seu aplicativo. Há vantagens e desvantagens em cada método.

Vinculação implícita

A vinculação implícita ocorre quando o código de um aplicativo chama uma função de DLL exportada. Quando o código-fonte do executável de chamada é compilado ou montado, a chamada de função DLL gera uma referência de função externa no código do objeto. Para resolver essa referência externa, o aplicativo deve vincular-se à biblioteca de importação (arquivo.lib) fornecida pelo criador da DLL.

A biblioteca de importação contém apenas código para carregar a DLL e implementar chamadas para funções na DLL. Localizar uma função externa em uma biblioteca de importação informa ao vinculador que o código para essa função está em uma DLL. Para resolver referências externas a DLLs, o vinculador simplesmente adiciona informações ao arquivo executável, que informa ao sistema onde encontrar o código da DLL quando o processo é iniciado.

Quando o sistema inicia um programa que contém referências vinculadas dinamicamente, ele usa as informações no arquivo executável do programa para localizar as DLLs necessárias. Se não conseguir localizar a DLL, o sistema encerrará o processo e exibirá uma caixa de diálogo que relata o erro. Caso contrário, o sistema mapeia os módulos DLL para o espaço de endereço do processo.

Se qualquer uma das DLLs tiver uma função de ponto de entrada para inicialização e código de encerramento, como DllMain, o sistema operacional chamará a função. Um dos parâmetros passados para a função de ponto de entrada especifica um código que indica que a DLL está sendo anexada ao processo. Se a função de ponto de entrada não retornar TRUE, o sistema encerrará o processo e relatará o erro.

Por fim, o sistema modifica o código executável do processo para fornecer endereços iniciais para as funções DLL.

Como o restante do código de um programa, o carregador mapeia o código DLL para o espaço de endereço do processo quando o processo for iniciado. O sistema operacional carrega-o na memória somente quando necessário. Como resultado, os atributos de código PRELOAD e LOADONCALL, usados pelos arquivos .def para controlar o carregamento em versões anteriores do Windows, não têm mais significado.

Vinculação explícita

A maioria dos aplicativos usa vinculação implícita porque é o método de vinculação mais fácil de ser usado. No entanto, há momentos em que é necessária a vinculação explícita. Seguem alguns motivos comuns para usar a vinculação explícita:

  • O aplicativo só saberá o nome de uma DLL que carregará em tempo de execução. Por exemplo, o aplicativo pode obter o nome da DLL e das funções exportadas de um arquivo de configuração na inicialização.

  • Um processo que usa a vinculação implícita será encerrado pelo sistema operacional se a DLL não for encontrada na inicialização do processo. Um processo que usa a vinculação explícita não é encerrado nessa situação e pode tentar se recuperar do erro. Por exemplo, o processo pode notificar o usuário do erro e fazer com que o usuário especifique outro caminho para a DLL.

  • Um processo que usa a vinculação implícita também será encerrado se qualquer uma das DLLs vinculadas tiver uma função DllMain que falhe. Um processo que usa a vinculação explícita não será encerrado nessa situação.

  • Um aplicativo que é vinculado implicitamente a muitas DLLs pode ser lento para iniciar porque o Windows carrega todas as DLLs quando o aplicativo é carregado. Para melhorar o desempenho da inicialização, um aplicativo só pode usar a vinculação implícita para DLLs necessárias imediatamente após o carregamento. Ele pode usar a vinculação explícita para carregar outras DLLs somente quando forem necessárias.

  • A vinculação explícita elimina a necessidade de vincular o aplicativo usando uma biblioteca de importação. Se as alterações na DLL fizerem com que os ordinais de exportação sejam alterados, os aplicativos não precisarão ser revinculados se chamarem GetProcAddress usando o nome de uma função e não um valor ordinal. Ainda assim, os aplicativos que usam a vinculação implícita devem ser revinculados para a biblioteca de importação alterada.

Seguem dois riscos de vinculação explícita para se ter ciência:

  • Se a DLL tiver uma função de ponto de entrada DllMain, o sistema operacional chamará a função no contexto do thread que chamou LoadLibrary. A função de ponto de entrada não será chamada se a DLL já estiver anexada ao processo devido a uma chamada anterior a LoadLibrary que não teve nenhuma chamada correspondente à função FreeLibrary. A vinculação explícita pode causar problemas se a DLL usar uma função DllMain para inicializar cada thread de um processo, pois todos os threads que já existem quando LoadLibrary (ou AfxLoadLibrary) são chamados e não estão inicializados.

  • Se uma DLL declarar dados de extensão estática como __declspec(thread), poderá causar uma falha de proteção se estiver explicitamente vinculada. Depois que a DLL for carregada por uma chamada a LoadLibrary, ela causa uma falha de proteção sempre que o código faz referência a esses dados. (Os dados de extensão estática incluem itens estáticos globais e locais). É por isso que, ao criar uma DLL, você deve evitar o uso do armazenamento local do thread. Se não puder, informe os usuários da DLL sobre as possíveis armadilhas de carregar dinamicamente sua DLL. Para obter mais informações, consulte Como usar o armazenamento local de thread em uma biblioteca de link dinâmico (SDK do Windows).

Como usar a vinculação implícita

Para usar uma DLL pela vinculação implícita, os executáveis do cliente devem obter esses arquivos do provedor da DLL:

  • Um ou mais arquivos de cabeçalho (arquivos.h) que contêm as declarações das classes de dados, funções e C++ exportadas na DLL. As classes, as funções e os dados exportados pela DLL devem ser marcados como __declspec(dllimport) no arquivo de cabeçalho. Para saber mais, confira dllexport, dllimport.

  • Uma biblioteca de importação para vincular ao executável. O vinculador cria a biblioteca de importação quando a DLL é criada. Para obter mais informações, consulte Arquivos .pdb como entrada do vinculador.

  • O arquivo DLL real.

Para usar os dados, as funções e as classes em uma DLL por meio da vinculação implícita, qualquer arquivo de origem do cliente deve incluir os arquivos de cabeçalho que os declaram. De uma perspectiva de codificação, as chamadas para as funções exportadas são como qualquer outra chamada de função.

Para criar o arquivo executável do cliente, você deverá vincular à biblioteca de importação da DLL. Se você usar um makefile externo ou um sistema de build, especifique a biblioteca de importação junto com os outros arquivos de objeto ou bibliotecas que você vincular.

O sistema operacional deve ser capaz de localizar o arquivo DLL quando carregar o executável de chamada. Isso significa que você deve implantar ou verificar a existência da DLL ao instalar seu aplicativo.

Para usar uma DLL por vinculação explícita, os aplicativos devem fazer uma chamada de função para carregar explicitamente a DLL em tempo de execução. Para vincular explicitamente a uma DLL, um aplicativo deve:

  • Chamar LoadLibraryEx ou uma função semelhante para carregar a DLL e obter um identificador de módulo.

  • Chamar GetProcAddress para obter um ponteiro de função para cada função exportada que o aplicativo chama. Como os aplicativos chamam as funções de DLL por meio de um ponteiro, o compilador não gera referências externas, portanto, não é necessário vincular a uma biblioteca de importação. No entanto, deverá incluir uma instrução typedef ou using que defina a assinatura de chamada das funções exportadas que você chama.

  • Chamar FreeLibrary quando terminar com a DLL.

Por exemplo, essa função de exemplo chama LoadLibrary para carregar uma DLL chamada "MyDLL", chama GetProcAddress para obter um ponteiro para uma função chamada "DLLFunc1", chama a função e salva o resultado e, em seguida, chama FreeLibrary para descarregar a DLL.

#include "windows.h"

typedef HRESULT (CALLBACK* LPFNDLLFUNC1)(DWORD,UINT*);

HRESULT LoadAndCallSomeFunction(DWORD dwParam1, UINT * puParam2)
{
    HINSTANCE hDLL;               // Handle to DLL
    LPFNDLLFUNC1 lpfnDllFunc1;    // Function pointer
    HRESULT hrReturnVal;

    hDLL = LoadLibrary("MyDLL");
    if (NULL != hDLL)
    {
        lpfnDllFunc1 = (LPFNDLLFUNC1)GetProcAddress(hDLL, "DLLFunc1");
        if (NULL != lpfnDllFunc1)
        {
            // call the function
            hrReturnVal = lpfnDllFunc1(dwParam1, puParam2);
        }
        else
        {
            // report the error
            hrReturnVal = ERROR_DELAY_LOAD_FAILED;
        }
        FreeLibrary(hDLL);
    }
    else
    {
        hrReturnVal = ERROR_DELAY_LOAD_FAILED;
    }
    return hrReturnVal;
}

Ao contrário deste exemplo, na maioria dos casos, você deve chamar LoadLibrary e FreeLibrary apenas uma vez em seu aplicativo para uma determinada DLL. É especialmente verdade se você vai chamar várias funções na DLL ou chamar funções DLL repetidamente.

Que mais você deseja saber?

Confira também

Criar DLLs C /C++ no Visual Studio