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 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 |
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