Você recebe avisos do vinculador ao criar extensões gerenciadas para projetos de DLL C++

Este artigo fornece informações sobre como resolver avisos do vinculador ao criar extensões gerenciadas para projetos de DLL C++.

Versão original do produto: Visual C++
Número de KB original: 814472

Sintomas

Você recebe uma das seguintes mensagens de erro no momento da compilação ou no momento do link:

Erro LNK2001 de ferramentas do vinculador
Símbolo externo "símbolo" não resolvido
LNK4210 de aviso de ferramentas do vinculador
'. A seção CRT existe; pode haver inicializações estáticas não tratadas ou teminadores'Você recebe avisos do Linker ao criar projetos de DLL Managed Extensions for C++
LNK4243 de aviso de ferramentas do vinculador
'A DLL que contém 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:

  • Ao compilar objetos de vinculação com o comutador /clr .
  • Quando você estiver criando um dos seguintes projetos:
    • modelo de serviço Web ASP.NET
    • Modelo de Biblioteca de Classes
    • Modelo da Biblioteca de Controle do Windows
  • Quando você tiver adicionado um código que usa variáveis globais ou classes nativas (ou seja, não __gcou __value) com membros de dados estáticos. Por exemplo, as classes ATL (Biblioteca de Modelos) do ActiveX, MFC (Microsoft Foundation) e c Run-Time (CRT).

Observação

Você pode receber os erros LNK2001 e LNK4210 com projetos que não são afetados pelo problema descrito neste artigo. No entanto, o projeto definitivamente será 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.

Motivo

Os projetos a seguir são criados por padrão como uma DLL (biblioteca de link 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:

  • modelo de serviço Web ASP.NET
  • Modelo de Biblioteca de Classes
  • Modelo da 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), você receberá mensagens de erro do vinculador no tempo de compilação. Quando isso ocorrer, 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.

Para conveniência, este artigo refere-se a variáveis globais e membros de dados estáticos de classes nativas como variáveis estáticas ou estáticas a partir deste ponto.

Esse problema é causado pelo problema de carregamento de DLL misto. DLLs misturadas (ou seja, DLLs que contêm código gerenciado e nativo) podem encontrar cenários de impasse 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 impasse e das soluções alternativas descritas neste documento.

Resolução

Extensões gerenciadas para projetos C++ que são criados como DLLs por padrão não se vinculam a bibliotecas C/C++ nativas, como a biblioteca de CRT (tempo de execução) 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 à opção /NOENTRY habilitada.

Isso é feito porque a vinculação com um ponto de entrada faz com que o código gerenciado seja executado durante DllMain, o que não é seguro (confira DllMain o conjunto limitado de coisas que você pode fazer durante o 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 antes fazer modificações.

Se sua DLL de modo misto precisar usar bibliotecas estáticas ou que dependem de estáticas (como ATL, MFC ou CRT), então você deve modificar sua DLL para que a estática seja inicializada manualmente.

A primeira etapa para inicialização manual é garantir que você desabilite o código de inicialização automática, que não é seguro com DLLs misturadas e pode causar um impasse. Para desabilitar o código de inicialização, siga as etapas.

Remover o ponto de entrada da DLL gerenciada

  1. Link com /NOENTRY. Em Gerenciador de Soluções, clique com o botão direito do mouse no nó do projeto, clique em Propriedades. Na caixa de diálogo Páginas da Propriedade , clique em Linker, clique em Linha de Comando e adicione essa opção ao campo Opções Adicionais .

  2. Link msvcrt.lib. Na caixa de diálogo Páginas da Propriedade , clique em Linker, clique em Entrada e adicione msvcrt.lib à propriedade Dependências Adicionais .

  3. Remova nochkclr.obj. Na página Entrada (mesma página da etapa anterior), remova nochkclr.obj da propriedade Dependências Adicionais .

  4. Link no CRT. Na página Entrada (mesma página da etapa anterior), adicione __DllMainCRTStartup@12 à propriedade Force Symbol References .

    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 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 estaticamente 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 as DLLs inseridas usando exportações dll (__declspec(dllexport)) e consumidores que não podem usar código gerenciado, siga estas etapas:

  1. Adicione duas novas exportações à sua DLL, conforme mostrado no seguinte código:

    // 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 do common language runtime, siga estas etapas:

    1. Clique em Projeto e clique em Propriedades do ProjectName.

      Observação

      ProjectName é um espaço reservado para o nome do projeto.

    2. Expanda Propriedades de Configuração e clique em Geral.

    3. No painel direito, clique para selecionar Suporte ao Common Language Runtime, Sintaxe Antiga (/clr:oldSyntax) nas configurações de projeto de suporte do Common Language Runtime .

    4. Na caixa Plano de discagem (Contexto de telefone), clique em Procurar para localizar o plano de discagem do usuário.

    Para obter mais informações sobre as opções do compilador de suporte do common language runtime, consulte /clr (Common Language Runtime Compilation).

    Essas etapas se aplicam a todo o artigo.

  2. Sua DLL pode ter vários consumidores. Se ele tiver vários consumidores, adicione o seguinte código ao arquivo .def da DLL na seção exportações:

    DllEnsureInitPRIVATE
    DllForceTermPRIVATE
    

    Se você não adicionar essas linhas e 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 multiconsumer, cada consumidor pode ser vinculado estaticamente ou dinamicamente à sua DLL.

  3. Se o consumidor estiver estaticamente vinculado à DLL, antes de usar a DLL pela primeira vez ou antes de usar qualquer coisa que dependa dele 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
    }
    
  4. 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
    }
    
  5. Se o consumidor estiver vinculado dinamicamente à DLL, insira o código da seguinte maneira:

    • Insira snippet 1 (consulte etapa 3) imediatamente após o primeiro LoadLibrary para a DLL.
    • Insira snippet 2 (consulte etapa 4) imediatamente antes do último FreeLibrary para a DLL.

Modificar DLLs baseadas em COM

Modifique as funções DllCanUnloadNowde exportação de DLL , DllGetClassObject, DllRegisterServere, DllUnregisterServer conforme demonstrado no seguinte código:

// 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 a 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 o código gerenciado e exportações de dll ou pontos de entrada gerenciados, siga estas etapas:

  1. Implemente uma classe gerenciada com funções de membro estático para inicialização e término. Adicione um arquivo .cpp ao seu projeto, implementando uma classe gerenciada com membros estáticos para inicialização e término:

    // 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 */
    
  2. 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 término 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();
    }