Partilhar via


DLLs e comportamento da biblioteca de execução do Visual C++

Quando você cria uma biblioteca de vínculo dinâmico (DLL) 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 interna de ponto de entrada DLL chamada _DllMainCRTStartup que manipula mensagens do sistema operacional Windows para a DLL para anexar ou desanexar de um processo ou thread. A função _DllMainCRTStartup executa tarefas essenciais, como a configuração de segurança do buffer de pilha, a inicialização e encerramento da biblioteca de tempo de execução C (CRT), e chamadas para os construtores e destruidores de objetos estáticos e globais. _DllMainCRTStartup também chama funções de hook para outras bibliotecas, como WinRT, MFC e ATL, para executar a sua própria inicialização e encerramento. Sem essa inicialização, o CRT e outras bibliotecas, bem como suas variáveis estáticas, seriam deixados em um estado não inicializado. As mesmas rotinas internas de inicialização e terminação VCRuntime são chamadas se sua DLL usa uma CRT vinculada estaticamente ou uma DLL CRT vinculada dinamicamente.

Ponto de entrada padrão da DLL _DllMainCRTStartup

No Windows, todas as DLLs podem conter uma função de ponto de entrada opcional, geralmente chamada DllMain, que é invocada tanto para inicialização como para terminação. Isso lhe dá 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 de processo, conexão de thread e desanexação de thread. Quando uma DLL é carregada em um espaço de endereçamento de processo, quando um aplicativo que a usa é carregado ou quando o aplicativo solicita a DLL em tempo de execução, o sistema operacional cria uma cópia separada dos dados da DLL. Isso é chamado anexação de processo. Thread attach ocorre quando o processo em que a DLL é carregada cria um novo thread. A desanexação de thread ocorre quando o thread termina e a desanexação de processo ocorre 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 reason para cada tipo de evento. Por exemplo, o sistema operacional envia DLL_PROCESS_ATTACH como argumento de razão para sinalizar o processo anexar.

A biblioteca VCRuntime fornece uma função de ponto de entrada chamada _DllMainCRTStartup para lidar com operações de inicialização e terminação padrão. Na ligação ao processo, a função _DllMainCRTStartup configura verificações de segurança do 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 a armazenagem local de threads, incrementa um contador estático interno para cada conexão e, em seguida, chama o DllMain fornecido pelo utilizador ou pela biblioteca. Quando o processo é desanexado, a função passa por essas etapas em ordem inversa. Ele chama DllMain, diminui o contador interno, chama destrutores, chama funções de terminação CRT e funções registradas atexit, e notifica qualquer outra biblioteca sobre a terminação. Quando o contador de anexos vai para zero, a função retorna FALSE para indicar ao Windows que a DLL pode ser descarregada. A _DllMainCRTStartup função também é chamada durante a conexão de rosca e desanexação de rosca. Nesses casos, o código VCRuntime não faz nenhuma inicialização ou terminação adicional por conta própria, e apenas chama DllMain para passar a mensagem. Se DllMain retorna FALSE do processo anexado, sinalizando falha, _DllMainCRTStartup chama DllMain novamente e passa DLL_PROCESS_DETACH como o argumento do motivo , então passa pelo resto do processo de encerramento.

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

Observação

Embora seja possível especificar outra função de ponto de entrada para uma DLL usando a opção /ENTRY:linker, não a recomendamos, porque 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 o seu comportamento. Por exemplo, você pode chamar __security_init_cookie imediatamente ao anexar o processo para suportar a opção de verificação de buffer /GS (Verificação de segurança do buffer). Você pode chamar a _CRT_INIT função, passando os mesmos parâmetros que a função de ponto de entrada, para executar o restante das funções de inicialização ou terminação da DLL.

Inicializar uma DLL

Sua DLL pode ter código de inicialização que deve ser executado quando sua DLL é carregada. Para que você execute suas próprias funções de inicialização e terminação de DLL, _DllMainCRTStartup chama uma função chamada DllMain que você pode fornecer. O seu DllMain deve ter a assinatura necessária para um ponto de entrada de uma 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 DllMain função, 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 precisa inicializar sua DLL, não há nada de especial que você tem que 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 envolvem a função DllMain para você. Por exemplo, numa DLL MFC regular, implemente as funções membro CWinApp e InitInstance do objeto ExitInstance para efetuar a inicialização e encerramento necessários pela sua DLL. Para obter mais detalhes, consulte a seção Inicializar DLLs MFC regulares .

Advertência

Há limites significativos sobre o que você pode fazer com segurança em um ponto de entrada DLL. Para obter mais informações sobre APIs específicas do Windows que não são seguras para chamar , DllMainconsulte Práticas recomendadas gerais. Se você 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 depois DllMain de ter sido executado e antes de chamar quaisquer outras funções na DLL.

Inicializar DLLs comuns (não MFC)

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

#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 deve ser especificado na linha de comando do vinculador com a opção /ENTRY. Com o Visual Studio, você não precisa usar a opção /ENTRY se o nome da sua 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 como algo 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 faz.

Inicializar DLLs MFC regulares

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

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

Em uma DLL MFC regular que se liga dinamicamente ao MFC, se estiver a usar suporte MFC OLE, MFC Database (ou DAO) ou MFC Sockets, respetivamente, as DLLs de extensão MFC de depuração MFCOversãoD.dll, MFCDversãoD.dlle MFCNversãoD.dll (onde versão é o número da versão) são ligadas automaticamente. Você deve chamar uma das seguintes funções de inicialização predefinidas para cada uma dessas DLLs que você está usando em sua DLL MFC regular.CWinApp::InitInstance

Tipo de suporte MFC Função de inicialização a ser chamada
MFC OLE (MFCOversãoD.dll) AfxOleInitModule
Banco de dados MFC (versão MFCDD.dll ) AfxDbInitModule
Soquetes MFC (versão MFCND.dll ) AfxNetInitModule

Inicializar DLLs de extensão MFC

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

O assistente fornece o seguinte código 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
}

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

Se fores usar a tua DLL de extensão MFC a partir de uma ou mais DLLs MFC regulares, deves exportar uma função de inicialização que cria um objeto CDynLinkLibrary. Essa função deve ser chamada de cada uma das DLLs MFC regulares que usam a extensão MFC DLL. Um local apropriado para chamar esta função de inicialização é na função membro InitInstance do objeto derivado da MFC DLL regular CWinApp antes de usar qualquer uma das classes ou funções exportadas das DLLs de extensão MFC.

No DllMain gerado pelo Assistente de DLL MFC, a chamada para AfxInitExtensionModule captura as classes de tempo de execução do módulo (CRuntimeClass estruturas), bem como as suas fábricas de objetos (COleObjectFactory objetos), para uso quando o objeto CDynLinkLibrary é criado. Você deve verificar o valor de retorno de AfxInitExtensionModule; se um valor zero for retornado de AfxInitExtensionModule, retorne zero da sua DllMain função.

Se a sua DLL de extensão MFC for explicitamente vinculada a um executável (ou seja, o executável faz chamadas AfxLoadLibrary para se vincular à DLL), deverá adicionar uma chamada para AfxTermExtensionModule em DLL_PROCESS_DETACH. Esta função permite que o MFC faça a limpeza da DLL de extensão MFC quando cada processo se desliga dela (o que ocorre quando o processo termina ou quando a DLL é descarregada como resultado de uma chamada AfxFreeLibrary). Se sua DLL de extensão MFC será vinculada implicitamente ao aplicativo, a chamada para AfxTermExtensionModule não é necessária.

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

Como o MFCx0.dll é totalmente inicializado no momento DllMain em que é chamado, você pode alocar memória e chamar funções MFC dentro 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 as threads se conectam e desanexarem da DLL. Chamar TlsAlloc quando uma DLL está a anexar permite que a DLL mantenha índices de armazenamento local de thread (TLS) para cada thread ligado à 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 para AFX_EXTENSION_MODULE e CDynLinkLibrary. Você deve incluir esse arquivo de cabeçalho em sua DLL de extensão MFC.

Observação

É importante não definir nem anular a definição de nenhuma das _AFX_NO_XXX macros em pch.h (stdafx.h no Visual Studio 2017 e anteriores). Essas macros existem apenas com a finalidade de verificar se uma determinada plataforma de destino suporta 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 desdefinir essas macros.

Uma função de inicialização de exemplo que lida com multithreading está incluída em Usando armazenamento local de thread em uma biblioteca de Dynamic-Link 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.

Ver também

criar DLLs C/C++ no Visual Studio
DllPonto de entrada principal
Práticas recomendadas da biblioteca de vínculo dinâmico