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.
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.
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.
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
.
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 CWinApp
da 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 |
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.
Criar DLLs C /C++ no Visual Studio
Ponto de entrada DllMain
Melhores práticas da biblioteca de vínculo dinâmico