Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Este artigo fornece informações sobre como resolver avisos de vinculador ao criar extensões gerenciadas para projetos DLL C++.
Versão original do produto: Visual C++
Número original do KB: 814472
Sintomas
Você recebe uma das seguintes mensagens de erro em tempo de compilação ou no momento da vinculação:
Erro das ferramentas de vinculador LNK2001
'símbolo externo não resolvido 'símbolo' '
Aviso das ferramentas de vinculador LNK4210
'. A seção CRT existe; pode haver inicializações estáticas sem tratamento ou teminators'Você recebe avisos do vinculador ao criar extensões gerenciadas para projetos de DLL C++
LNK4243 de aviso das ferramentas do vinculador
'DLL contendo objetos compilados com /clr não está vinculada a /NOENTRY; a imagem pode não ser executada corretamente'.
Esses avisos podem ocorrer durante as seguintes circunstâncias:
- Quando você compila objetos de vinculação com a opção /clr .
- Quando você está construindo um dos seguintes projetos:
- ASP.NET Modelo de serviço Web
- Modelo de biblioteca de classes
- Modelo de Biblioteca de Controle do Windows
- Quando você tiver adicionado código que usa variáveis globais ou classes nativas (ou seja, not
__gcou__value) com membros de dados estáticos. Por exemplo, as classes ATL (ActiveX Template Library), Microsoft Foundation Classes (MFC) e CRT (C Run-Time).
Observação
Você pode receber os erros de LNK2001 e LNK4210 com projetos que não são afetados pelo problema descrito neste artigo. No entanto, o projeto definitivamente é afetado pelo problema descrito neste artigo se a resolução de um aviso de LNK2001 ou LNK4210 levar a um aviso de LNK4243 ou se vincular o projeto gerar um aviso de LNK4243.
Causa
Os projetos a seguir são criados por padrão como uma DLL (biblioteca de vínculo dinâmico) sem qualquer vinculação a bibliotecas nativas (como CRT, ATL ou MFC) e sem variáveis globais ou classes nativas com membros de dados estáticos:
- ASP.NET Modelo de serviço Web
- Modelo de biblioteca de classes
- Modelo de Biblioteca de Controle do Windows
Se você adicionar código que usa variáveis globais ou classes nativas com membros de dados estáticos (por exemplo, as bibliotecas ATL, MFC e CRT usam variáveis globais), receberá mensagens de erro do vinculador em tempo de compilação. Quando isso ocorre, você deve adicionar código para inicializar manualmente as variáveis estáticas. Para obter mais informações sobre como fazer isso, consulte a seção Resolução deste artigo.
Por conveniência, este artigo refere-se a variáveis globais e membros de dados estáticos de classes nativas como estáticas ou variáveis estáticas deste ponto em diante.
Esse problema é causado pelo problema de carregamento de DLL mista. DLLs mistas (ou seja, DLLs que contêm código gerenciado e nativo) podem encontrar cenários de deadlock em algumas circunstâncias quando são carregadas no espaço de endereço do processo, especialmente quando o sistema está sob estresse. As mensagens de erro do vinculador mencionadas anteriormente foram habilitadas no vinculador para garantir que os clientes estejam cientes do potencial de deadlock e das soluções alternativas descritas neste documento.
Solução
As extensões gerenciadas para projetos C++ criados como DLLs por padrão não são vinculadas a bibliotecas C/C++ nativas, como a biblioteca CRT (C), ATL ou MFC, e não usam variáveis estáticas. Além disso, as configurações do projeto especificam que as DLLs devem ser vinculadas com a opção /NOENTRY habilitada.
Isso é feito porque a vinculação a um ponto de entrada faz com que o código gerenciado seja executado durante DllMaino , o que não é seguro (consulte DllMain o conjunto limitado de coisas que você pode fazer durante seu escopo).
Uma DLL sem um ponto de entrada não tem como inicializar variáveis estáticas, exceto para tipos simples, como inteiros. Normalmente, você não tem variáveis estáticas em uma DLL /NOENTRY .
As bibliotecas ATL, MFC e CRT dependem de variáveis estáticas, portanto, você também não pode usar essas bibliotecas de dentro dessas DLLs sem primeiro fazer modificações.
Se a DLL de modo misto precisar usar estáticas ou bibliotecas que dependem de estáticas (como ATL, MFC ou CRT), você deverá modificar sua DLL para que a estática seja inicializada manualmente.
A primeira etapa para a inicialização manual é certificar-se de desabilitar o código de inicialização automática, que não é seguro com DLLs mistas e pode causar deadlock. Para desativar o código de inicialização, siga as etapas.
Remover o ponto de entrada da DLL gerenciada
Link com /NOENTRY. No Gerenciador de Soluções, clique com o botão direito do mouse no nó do projeto e clique em Propriedades. Na caixa de diálogo Páginas de Propriedades , clique em Vinculador, clique em Linha de Comando e adicione essa opção ao campo Opções Adicionais.
Vincule msvcrt.lib. Na caixa de diálogo Páginas de Propriedades , clique em Vinculador, clique em Entrada e adicione msvcrt.lib à propriedade Dependências Adicionais.
Remova nochkclr.obj. Na página Entrada (mesma página da etapa anterior), remova nochkclr.obj da propriedade Dependências Adicionais.
Link no CRT. Na página Entrada (mesma página da etapa anterior), adicione __DllMainCRTStartup@12 à propriedade Forçar Referências de Símbolo .
Se você estiver usando o prompt de comando, especifique as configurações de projeto acima com o seguinte:
LINK /NOENTRY msvcrt.lib /NODEFAULTLIB:nochkclr.obj /INCLUDE:__DllMainCRTStartup@12
Modificar componentes que consomem a DLL para inicialização manual
Depois de remover o ponto de entrada explícito, você deve modificar os componentes que consomem a DLL para inicialização manual, dependendo da maneira como sua DLL é implementada:
- Sua DLL é inserida usando exportações de DLL (
__declspec(dllexport)) e seus consumidores não podem usar código gerenciado se estiverem vinculados estática ou dinamicamente à sua DLL. - Sua DLL é uma DLL baseada em COM.
- Os consumidores de sua DLL podem usar código gerenciado e sua DLL contém exportações de DLL ou pontos de entrada gerenciados.
Modificar DLLs inseridas usando exportações de DLL e consumidores que não podem usar código gerenciado
Para modificar DLLs inseridas usando exportações de dll (__declspec(dllexport)) e consumidores que não podem usar código gerenciado, siga estas etapas:
Adicione duas novas exportações à sua DLL, conforme mostrado no código a seguir:
// init.cpp #include <windows.h> #include <_vcclrit.h> // Call this function before you call anything in this DLL. // It is safe to call from multiple threads; it is not reference // counted; and is reentrancy safe. __declspec(dllexport) void __cdecl DllEnsureInit(void) { // Do nothing else here. If you need extra initialization steps, // create static objects with constructors that perform initialization. __crt_dll_initialize(); // Do nothing else here. } // Call this function after this whole process is totally done // calling anything in this DLL. It is safe to call from multiple // threads; is not reference counted; and is reentrancy safe. // First call will terminate. __declspec(dllexport) void __cdecl DllForceTerm(void) { // Do nothing else here. If you need extra terminate steps, // use atexit. __crt_dll_terminate(); // Do nothing else here. }Para adicionar a opção do compilador de suporte ao Common Language Runtime, siga estas etapas:
Clique em Projeto e, em seguida, clique em Propriedades do NomeDoProjeto.
Observação
ProjectName é um espaço reservado para o nome do projeto.
Expanda Propriedades de Configuração e clique em Geral.
No painel direito, clique para selecionar Suporte ao Common Language Runtime, Sintaxe Antiga (/clr:oldSyntax) nas configurações do projeto de suporte ao Common Language Runtime.
Clique em Aplicar e em OK.
Para obter mais informações sobre as opções do compilador de suporte ao Common Language Runtime, consulte /clr (Compilação do Common Language Runtime).
Essas etapas se aplicam a todo o artigo.
Sua DLL pode ter vários consumidores. Se ele tiver vários consumidores, adicione o seguinte código ao arquivo .def DLL na seção exportações:
DllEnsureInitPRIVATE DllForceTermPRIVATESe você não adicionar essas linhas e se tiver duas DLLs que exportam funções, o aplicativo vinculado à DLL terá erros de link. Normalmente, as funções exportadas têm os mesmos nomes. Em um caso de vários consumidores, cada consumidor pode ser vinculado estaticamente ou dinamicamente à sua DLL.
Se o consumidor estiver vinculado estaticamente à DLL, antes de usar a DLL pela primeira vez ou antes de usar qualquer coisa que dependa dela em seu aplicativo, adicione a seguinte chamada:
// Snippet 1 typedef void (__stdcall *pfnEnsureInit)(void); typedef void (__stdcall *pfnForceTerm)(void); { // ... initialization code HANDLE hDll=::GetModuleHandle("mydll.dll"); If(!hDll) { // Exit, return; there is nothing else to do. } pfnEnsureInit pfnDll=::( pfnEnsureInit) GetProcAddress(hDll, "DllEnsureInit"); if(!pfnDll) { // Exit, return; there is nothing else to do. } pfnDll(); // ... more initialization code }Após o último uso da DLL em seu aplicativo, adicione o seguinte código:
// Snippet 2 { // ... termination code HANDLE hDll=::GetModuleHandle("mydll.dll"); If(!hDll) { // exit, return; there is nothing else to do } pfnForceTerm pfnDll=::( pfnForceTerm) GetProcAddress(hDll, "DllForceTerm"); if(!pfnDll) { // exit, return; there is nothing else to do } pfnDll(); // ... more termination code }Se o consumidor estiver vinculado dinamicamente à DLL, insira o código da seguinte maneira:
- Insira o snippet 1 (consulte a etapa 3) imediatamente após o primeiro LoadLibrary para a DLL.
- Insira o snippet 2 (consulte a etapa 4) imediatamente antes da última FreeLibrary para a DLL.
Modificar DLLs baseadas em COM
Modifique as funções DllCanUnloadNowde exportação de DLL , DllGetClassObject, DllRegisterServere DllUnregisterServer conforme demonstrado no código a seguir:
// Implementation of DLL Exports.
#include <_vcclrit.h>
STDAPI DllCanUnloadNow(void)
{
if ( _Module.GetLockCount() == 0 )
{
__crt_dll_terminate();
return S_OK;
}
else
{
return S_FALSE;
}
}
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
if ( !( __crt_dll_initialize()) )
{
return E_FAIL;
}
else
{
return _Module.GetClassObject(rclsid, riid, ppv);
}
}
STDAPI DllRegisterServer(void)
{
if ( !( __crt_dll_initialize()) )
{
return E_FAIL;
}
// Call your registration code here
HRESULT hr = _Module.RegisterServer(TRUE)
return hr;
}
STDAPI DllUnregisterServer(void)
{
HRESULT hr = S_OK;
__crt_dll_terminate();
// Call your unregistration code here
hr = _Module.UnregisterServer(TRUE);
return hr;
}
Modificar DLL que contém consumidores que usam código gerenciado e exportações de DLL ou pontos de entrada gerenciados
Para modificar a DLL que contém consumidores que usam código gerenciado e exportações de dll ou pontos de entrada gerenciados, siga estas etapas:
Implemente uma classe gerenciada com funções de membro estático para inicialização e encerramento. Adicione um arquivo .cpp ao seu projeto, implementando uma classe gerenciada com membros estáticos para inicialização e encerramento:
// ManagedWrapper.cpp // This code verifies that DllMain is not automatically called // by the Loader when linked with /noentry. It also checks some // functions that the CRT initializes. #include <windows.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <math.h> #include "_vcclrit.h" #using <mscorlib.dll> using namespace System; public __gc class ManagedWrapper { public: static BOOL minitialize() { BOOL retval = TRUE; try { retval = __crt_dll_initialize(); } catch(System::Exception* e) { Console::WriteLine(e->Message); retval = FALSE; } return retval; } static BOOL mterminate() { BOOL retval = TRUE; try { retval = __crt_dll_terminate(); } catch(System::Exception* e) { Console::WriteLine(e->Message); retval = FALSE; } return retval; } }; BOOL WINAPI DllMain(HINSTANCE hModule, DWORD dwReason, LPVOID lpvReserved) { Console::WriteLine(S"DllMain is called..."); return TRUE; } /* DllMain */Chame essas funções antes de se referir à DLL e depois de terminar de usá-la. Chame as funções de membro de inicialização e encerramento em
main:// Main.cpp #using <mscorlib.dll> using namespace System; using namespace System::Reflection; #using "ijwdll.dll"; int main() { int retval = ManagedWrapper::minitialize(); ManagedWrapper::mterminate(); }