Comportamento de DLLs e da biblioteca em tempo de execução do Visual C++

Quando você cria uma DLL (biblioteca de vínculo dinâmico) usando o Visual Studio, por padrão, o vinculador inclui a biblioteca de tempo de execução do Visual C++ (VCRuntime). O VCRuntime contém o código necessário para inicializar e encerrar um executável C/C++. Quando vinculado a uma DLL, o código VCRuntime fornece uma função de ponto de entrada de DLL interna chamada _DllMainCRTStartup, que manipula mensagens do sistema operacional Windows para a DLL a ser anexada ou desanexada de um processo ou thread. A função _DllMainCRTStartup executa tarefas essenciais, como configuração de segurança do buffer de pilha, inicialização e encerramento da CRT (biblioteca de tempo de execução C) e chamadas para construtores e destruidores para objetos estáticos e globais. _DllMainCRTStartup também chama funções de gancho para outras bibliotecas, como WinRT, MFC e ATL, para executar sua própria inicialização e terminação. Sem essa inicialização, a CRT e outras bibliotecas, bem como suas variáveis estáticas, seriam deixadas em um estado não inicializado. As mesmas rotinas de inicialização interna e encerramento do VCRuntime são chamadas se a DLL usa um CRT vinculado estaticamente ou uma DLL de CRT vinculada dinamicamente.

Ponto de entrada DLL padrão _DllMainCRTStartup

No Windows, todas as DLLs podem conter uma função de ponto de entrada opcional, geralmente chamada DllMain, que é chamada para inicialização e encerramento. Isso oferece a oportunidade de alocar ou liberar recursos adicionais conforme necessário. O Windows chama a função de ponto de entrada em quatro situações: anexação de processo, desanexação do processo, anexação de thread e desanexação de thread. Quando uma DLL é carregada em um espaço de endereço de processo, quando um aplicativo que o usa é carregado ou quando o aplicativo solicita a DLL em runtime, o sistema operacional cria uma cópia separada dos dados DLL. Isso é chamado de anexação de processo. A anexação de thread ocorre quando o processo em que a DLL é carregada cria um novo thread. O desanexamento de thread ocorre quando o thread termina e o desanexamento do processo é quando a DLL não é mais necessária e é liberada por um aplicativo. O sistema operacional faz uma chamada separada para o ponto de entrada DLL para cada um desses eventos, passando um argumento de motivo para cada tipo de evento. Por exemplo, o sistema operacional envia DLL_PROCESS_ATTACH como o argumento motivo para sinalizar a anexação do processo.

A biblioteca VCRuntime fornece uma função de ponto de entrada chamada _DllMainCRTStartup para lidar com operações de inicialização e encerramento padrão. Na anexação do processo, a função _DllMainCRTStartup configura verificações de segurança de buffer, inicializa o CRT e outras bibliotecas, inicializa informações de tipo em tempo de execução, inicializa e chama construtores para dados estáticos e não locais, inicializa o armazenamento local do thread, incrementa um contador estático interno para cada anexação e, em seguida, chama um DllMain fornecido pelo usuário ou pela biblioteca. No processo de desanexação, a função passa por essas etapas na ordem inversa. Ele chama DllMain, decrementa o contador interno, chama destruidores, chama funções de encerramento CRT e funções atexit registradas e notifica outras bibliotecas de encerramento. Quando o contador de anexos for a zero, a função retornará FALSE para indicar ao Windows que a DLL pode ser descarregada. A função _DllMainCRTStartup também é chamada durante a anexação de thread e a desanexação do thread. Nesses casos, o código VCRuntime não faz qualquer inicialização ou encerramento adicional por conta própria e apenas chama DllMain para passar a mensagem. Se DllMain retornar FALSE da anexação do processo, sinalizando falha, _DllMainCRTStartup chama DllMain novamente e passa DLL_PROCESS_DETACH como o argumento de motivo, então passará pelo restante do processo de encerramento.

Ao criar DLLs no Visual Studio, o _DllMainCRTStartup do ponto de entrada padrão fornecido pelo VCRuntime é vinculado automaticamente. Você não precisa especificar uma função de ponto de entrada para sua DLL usando a opção do vinculador /ENTRY (símbolo de ponto de entrada).

Observação

Embora seja possível, não recomendamos especificar outra função de ponto de entrada para uma DLL usando a opção do vinculador /ENTRY:, pois sua função de ponto de entrada teria que duplicar tudo o que _DllMainCRTStartup faz, na mesma ordem. O VCRuntime fornece funções que permitem duplicar seu comportamento. Por exemplo, você pode chamar __security_init_cookie imediatamente na anexação do processo para permitir a opção de verificação de buffer /GS (verificação de segurança do buffer). Você pode chamar a função _CRT_INIT, passando os mesmos parâmetros que a função de ponto de entrada, para executar o restante das funções de inicialização ou encerramento de DLL.

Inicializar uma DLL

Seu DLL pode ter o código de inicialização de DLL que precisa ser executado quando sua DLL é carregada. Para que você execute suas próprias funções de inicialização e encerramento de DLL, _DllMainCRTStartup chama uma função chamada DllMain que você pode fornecer. Seu DllMain precisa ter a mesma assinatura necessária para um ponto de entrada DLL. A função _DllMainCRTStartup de ponto de entrada padrão chama DllMain usando os mesmos parâmetros passados pelo Windows. Por padrão, se você não fornecer uma função DllMain, o Visual Studio fornecerá uma para você e a vinculará para que _DllMainCRTStartup sempre tenha algo para chamar. Isso significa que, se você não precisar inicializar sua DLL, não haverá nada de especial que você precise fazer ao criar sua DLL.

Esta é a assinatura usada para DllMain:

#include <windows.h>

extern "C" BOOL WINAPI DllMain (
    HINSTANCE const instance,  // handle to DLL module
    DWORD     const reason,    // reason for calling function
    LPVOID    const reserved); // reserved

Algumas bibliotecas encapsulam a função DllMain para você. Por exemplo, em uma DLL MFC regular, implemente as funções de membo InitInstance e ExitInstance do objeto CWinApp para executar a inicialização e o encerramento exigidos pela DLL. Para obter mais detalhes, confira a seção Inicializar DLLs MFC regulares.

Aviso

Há limites significativos sobre o que pode ser feito com segurança em um ponto de entrada de DLL. Para obter mais informações sobre APIs específicas do Windows que não são seguras de chamar em DllMain, confira Práticas Recomendadas Gerais. Se precisar de algo além da inicialização mais simples, faça isso em uma função de inicialização para a DLL. Você pode exigir que os aplicativos chamem a função de inicialização após DllMain ser executado e antes que eles chamem outras funções na DLL.

Inicializar DLLs comuns (não MFC)

Para executar sua própria inicialização em DLLs comuns (não MFC) que usam o ponto de entrada _DllMainCRTStartup fornecido pelo VCRuntime, seu código-fonte DLL precisa conter uma função chamada DllMain. O código a seguir apresenta um esqueleto básico mostrando a aparência da definição de DllMain:

#include <windows.h>

extern "C" BOOL WINAPI DllMain (
    HINSTANCE const instance,  // handle to DLL module
    DWORD     const reason,    // reason for calling function
    LPVOID    const reserved)  // reserved
{
    // Perform actions based on the reason for calling.
    switch (reason)
    {
    case DLL_PROCESS_ATTACH:
        // Initialize once for each new process.
        // Return FALSE to fail DLL load.
        break;

    case DLL_THREAD_ATTACH:
        // Do thread-specific initialization.
        break;

    case DLL_THREAD_DETACH:
        // Do thread-specific cleanup.
        break;

    case DLL_PROCESS_DETACH:
        // Perform any necessary cleanup.
        break;
    }
    return TRUE;  // Successful DLL_PROCESS_ATTACH.
}

Observação

A documentação mais antiga do SDK do Windows diz que o nome real da função de ponto de entrada DLL precisa ser especificado na linha de comando do vinculador com a opção /ENTRY. Com o Visual Studio, você não precisará usar a opção /ENTRY se o nome da função de ponto de entrada for DllMain. Na verdade, se você usar a opção /ENTRY e nomear sua função de ponto de entrada com um nome diferente de DllMain, o CRT não será inicializado corretamente, a menos que sua função de ponto de entrada faça as mesmas chamadas de inicialização que _DllMainCRTStartup.

Inicializar DLLs regulares do MFC

Como as DLLs MFC regulares têm um objeto CWinApp, elas devem executar as tarefas de inicialização e encerramento no mesmo local que um aplicativo MFC: nas funções de membro InitInstance e ExitInstance da classe derivada de CWinAppda DLL. Como o MFC fornece uma função DllMain que é chamada por _DllMainCRTStartup para DLL_PROCESS_ATTACH e DLL_PROCESS_DETACH, você não deve escrever sua própria função DllMain. A função DllMain fornecida pelo MFC chama InitInstance quando sua DLL é carregada e chama ExitInstance antes que a DLL seja descarregada.

Uma DLL MFC regular pode controlar vários threads chamando TlsAlloc e TlsGetValue em sua função InitInstance. Essas funções permitem que a DLL acompanhe dados específicos do thread.

Em sua DLL MFC regular vinculada dinamicamente ao MFC, se você estiver usando qualquer MFC do OLE, o banco de dados MFC (ou DAO) ou o suporte a soquetes MFC, respectivamente, MFCOversãoD.dll, MFCDversãoD.dll e MFCNversãoD.dll das DLLs de extensão MFC de depuração (em que versão é o número de versão) será vinculado automaticamente. Você precisa chamar uma das seguintes funções de inicialização predefinidas para cada uma dessas DLLs que você está usando no CWinApp::InitInstance dos DLLs de MFC regulares.

Tipo de suporte do MFC Função de inicialização para chamada
OLE MFC (MFCOversãoD.dll) AfxOleInitModule
Banco de dados MFC (MFCDversãoD.dll) AfxDbInitModule
Soquetes MFC (MFCNversãoD.dll) AfxNetInitModule

Inicializar uma DLL de extensão do MFC

Como as DLLs de extensão MFC não têm um objeto derivado de CWinApp (assim como as DLLs MFC regulares), você deve adicionar seu código de inicialização e encerramento à função DllMain gerada pelo Assistente de DLL do MFC.

O assistente fornece o código a seguir para DLLs de extensão MFC. No código, PROJNAME é um espaço reservado para o nome do seu projeto.

#include "pch.h" // For Visual Studio 2017 and earlier, use "stdafx.h"
#include <afxdllx.h>

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
static AFX_EXTENSION_MODULE PROJNAMEDLL;

extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
   if (dwReason == DLL_PROCESS_ATTACH)
   {
      TRACE0("PROJNAME.DLL Initializing!\n");

      // MFC extension DLL one-time initialization
      AfxInitExtensionModule(PROJNAMEDLL,
                                 hInstance);

      // Insert this DLL into the resource chain
      new CDynLinkLibrary(Dll3DLL);
   }
   else if (dwReason == DLL_PROCESS_DETACH)
   {
      TRACE0("PROJNAME.DLL Terminating!\n");
   }
   return 1;   // ok
}

Criar um novo objeto CDynLinkLibrary durante a inicialização permite que a DLL de extensão MFC exporte objetos ou recursos do CRuntimeClass para o aplicativo cliente.

Se você quer usar seu DLL de extensão MFC de uma ou mais DLLs MFC regulares, você precisa exportar uma função de inicialização que cria um objeto CDynLinkLibrary. Essa função precisa ser chamada de cada uma das DLLs MFC regulares que usam a DLL de extensão MFC. Um local apropriado para chamar essa função de inicialização é a função membro de InitInstance do objeto derivado de CWinApp da DLL MFC regular antes de usar qualquer uma das classes ou funções exportadas da extensão MFC DLL.

No DllMain que o Assistente de DLL MFC gera, a chamada para AfxInitExtensionModule capturar as classes de tempo de execução do módulo (estruturas de CRuntimeClass) bem como seus objetos (objetos de COleObjectFactory) para uso quando o objeto CDynLinkLibrary é criado. Você deve verificar o valor retornado de AfxInitExtensionModule. Se um valor zero for retornado de AfxInitExtensionModule, retorne zero da sua função DllMain.

Se a DLL da extensão MFC estiver explicitamente vinculada a um executável (o que significa que AfxLoadLibrary das chamadas executáveis para vincular à DLL), você deverá adicionar uma chamada para AfxTermExtensionModule em DLL_PROCESS_DETACH. A função permite que o MFC limpe a DLL de extensão de MFC quando cada processo é desanexado da DLL de extensão de MFC (o que acontece quando o processo é encerrado ou quando a DLL é descarregada como resultado de uma chamada AfxFreeLibrary). Se a DLL da extensão MFC for vinculada implicitamente ao aplicativo, a chamada para AfxTermExtensionModule não será necessária.

Os aplicativos que vinculam explicitamente às DLLs de extensão MFC precisa chamar AfxTermExtensionModule ao liberar a DLL. Ele também deve usar AfxLoadLibrary e AfxFreeLibrary (em vez das funções LoadLibrary e FreeLibrary do Win32) se o aplicativo usar vários threads. Usar AfxLoadLibrary e AfxFreeLibrary garante que o código de inicialização e desligamento executado quando a DLL da extensão MFC é carregada e descarregada não corrompa o estado MFC global.

Como o MFCx0.dll é totalmente inicializado quando é DllMain chamado, você pode alocar memória e chamar funções MFC no DllMain (ao contrário da versão de 16 bits do MFC).

As DLLs de extensão podem cuidar do multithreading manipulando os casos DLL_THREAD_ATTACH e DLL_THREAD_DETACH na função DllMain. Esses casos são passados para DllMain quando os threads são anexados e desanexados da DLL. Chamar TlsAlloc quando uma DLL está anexando permite que a DLL mantenha índices de TLS (armazenamento local de thread) para cada thread anexado à DLL.

Observe que o arquivo de cabeçalho Afxdllx.h contém definições especiais para estruturas usadas em DLLs de extensão MFC, como a definição de AFX_EXTENSION_MODULE e CDynLinkLibrary. Você deve incluir esse arquivo de cabeçalho na DLL da extensão MFC.

Observação

É importante que você não defina nem cancele a definação de nenhuma das macros _AFX_NO_XXX em pch.h (stdafx.h no Visual Studio 2017 e anterior). Essas macros existem apenas para verificar se uma plataforma de destino específica permite esse recurso ou não. Você pode escrever seu programa para verificar essas macros (por exemplo, #ifndef _AFX_NO_OLE_SUPPORT), mas seu programa nunca deve definir ou cancelar a definição dessas macros.

Uma função de inicialização de exemplo que manipula o multithreading está incluída em Como usar o armazenamento local de thread em uma biblioteca de vínculo dinâmico no SDK do Windows. Observe que o exemplo contém uma função de ponto de entrada chamada LibMain, mas você deve nomear essa função DllMain para que ela funcione com as bibliotecas de tempo de execução MFC e C.

Confira também

Criar DLLs C /C++ no Visual Studio
Ponto de entrada DllMain
Melhores práticas da biblioteca de vínculo dinâmico