Propojovací program upozornění se zobrazí při vytváření spravovaného rozšíření pro C++ DLL projektů

Příznaky

V době kompilace, nebo v době spojení zobrazí jedna z následujících chybových zpráv:
LNK2001 Chyba nástroje Linker
"nevyřešené externí symbolem"symbol""

Propojovací program nástroje Upozornění LNK4210
'. CRT oddíl existuje; může být neošetřené statické inicializuje nebo teminators'You obdrží upozornění propojovací program při vytváření spravovaného rozšíření pro C++ DLL projektů

Propojovací program nástroje Upozornění LNK4243
"DLL obsahující objekty kompilována s/clr není spojen s /NOENTRY; Obrázek nemusí správně fungovat ".
Tato upozornění může dojít během následujících okolností:
  • Při sestavování propojení objektů pomocí přepínače/CLR .
  • Když vytváříte jeden z následujících projektů: technologie ASP.NET webové služby šablony; Šablona knihovny tříd; nebo šablona knihovny Windows ovládací prvek.
  • Když jste přidali kód který používá globální proměnné nebo nativní tříd (to znamená, že není __gc nebo __value) s statické datové členy. Například ActiveX šablonu knihovny (ATL) Microsoft Foundation Classes (MFC) a třídy C Runtime (CRT)
Poznámka: může dojít k chybám LNK2001 a LNK4210 s projekty, které není vliv problému popsaného v tomto článku. Nicméně projekt definitivně vliv problému popsaného v tomto článku, pokud řešení LNK2001 nebo upozornění LNK4210 vede k LNK4243 varování, nebo -li propojení projektu generuje upozornění LNK4243.

Příčina

Tyto projekty jsou vytvořeny ve výchozím nastavení jako dynamické propojení knihovny (DLL) bez jakékoli vazby na nativní knihovny (například CRT, ATL nebo MFC) a bez jakékoli globální proměnné nebo nativní tříd pomocí statické datové členy:
  • Šablony ASP.NET Web Service
  • Šablona knihovny tříd
  • Šablona knihovny ovládacího prvku Windows
Pokud přidáte kód, který používá globální proměnné nebo nativní tříd s statické datové členy (například knihovny CRT, ATL a MFC použít globální proměnné), obdržíte chybové zprávy linker v době kompilace. Pokud k tomu dojde, je třeba přidat kód ručně inicializovat statické proměnné. Další informace o tomto postupu naleznete v části "Řešení" tohoto článku.

Pro přehlednost tento článek odkazuje na globální proměnné a statické datové členy tříd nativní jako "Statistika" nebo "statické proměnné" od této chvíle.

Tento problém je způsoben problém smíšeného načítání DLL Knihovny. Smíšené knihovny DLL (to znamená, že knihovny DLL, které obsahují spravované a nativní kód) může dojít k zablokování scénáře za určitých okolností při načtení do adresového prostoru procesu, zejména v případě, že systém je pod zátěžové. Výše uvedené chybové zprávy linker byly povoleny v propojovací program a ujistěte se, že zákazníci nejsou vědomi potenciální zablokování a řešení, které jsou popsány v tomto dokumentu. Podrobný popis Smíšený problém načtení knihovny DLL naleznete v následujícím dokumentu whitepaper:
Problém načtení knihovny DLL kombinovaném
http://msdn2.microsoft.com/en-us/library/aa290048(vs.71).aspx

Řešení

Řízená rozšíření pro C++ projekty, které jsou vytvořeny jako knihovny DLL ve výchozím nastavení nejsou připojeny k nativní C/C++ knihovny jako C Runtime (CRT) knihovny ATL nebo knihovny MFC a nepoužívejte žádné statické proměnné. Navíc nastavení projektu určit, že jsou knihovny DLL by měly být propojeny s povolenou možností /NOENTRY .

To se provádí protože propojení s vstupní bod způsobí, že spravovaný kód pracoval během DllMain, který není bezpečný (viz DllMain pro omezenou sadu způsobů, jak během její oblast působnosti).

Bez vstupní bod knihovny DLL nijak inicializovat statické proměnné s výjimkou velmi jednoduché typy jako jsou celá čísla. Nemáte obvykle statické proměnné v /NOENTRY knihovny DLL.

Všechny knihovny ATL, MFC a CRT spoléhat na statické proměnné, tak také nelze použít tyto knihovny z v rámci těchto knihoven DLL bez první provedení úprav.

Pokud vaše knihovna DLL kombinovaném režimu musí používat statické nebo knihoven, které závisí na statické (například ATL, MFC nebo CRT), je třeba, aby Statistika jsou inicializovány ručně upravit vaší knihovny DLL.

Prvním krokem k ruční inicializace je zajistit zakázat automatické inicializace kód, který nebezpečný s smíšené knihovny DLL a může dojít k vzájemnému zablokování. Inicializace kód zakázat, postupujte podle pokynů.

Odebrat vstupní bod knihovny DLL spravované

  1. Propojení s /NOENTRY. V Průzkumníku řešeníklikněte pravým tlačítkem myši na uzel projektu, klepněte na příkaz Vlastnosti. V dialogovém okně Stránky vlastnostíklepněte na tlačítko Linkera příkazového řádkupřidejte přepínač pole Další možnosti.
  2. Odkaz msvcrt.lib. V dialogovém okně Stránky vlastností klepněte na tlačítko
    Propojovací program, klepněte na tlačítko vstup. a poté přidat msvcrt.lib do Další závislosti
    property.
  3. Odeberte nochkclr.obj. Na vstupní stránku (stejné jako v předchozím kroku), odebrat Další závislosti vlastnost nochkclr.obj .
  4. Odkaz v CRT. Na vstupní stránku (stejné jako v předchozím kroku), přidat __DllMainCRTStartup@12 vlastnost Odkazy Symbol síly.

    Pokud používáte příkazový řádek, zadejte výše uvedené nastavení projektu následující:
    LINK /NOENTRY msvcrt.lib /NODEFAULTLIB:nochkclr.obj /INCLUDE:__DllMainCRTStartup@12

Změnit komponenty, které spotřebovávají knihovny DLL pro ruční Initializiation

Po odebrání explicitní vstupní bod, je nutné upravit komponenty, které spotřebovávají knihovny DLL pro ruční inicializace podle způsobu, že je implementována knihovny DLL:
  • Knihovny DLL je zadán pomocí knihovny DLL vývozu (__declspec(dllexport)) a vaši spotřebitelé nemohou používat spravovaný kód, pokud jsou staticky nebo dynamicky propojeny do vaší knihovny DLL.
  • Vaše knihovna DLL je knihovna DLL založené na modelu COM.
  • Spotřebitelé knihovny DLL, můžete použít spravovaný kód a knihovny DLL obsahuje buď vývozy knihovny DLL nebo spravované vstupních bodů.

Upravit soubory DLL, které zadáte pomocí knihovny DLL vývozu a spotřebitelů, které nelze používat spravovaný kód

Chcete-li upravit soubory DLL, které zadáte pomocí knihovny dll vývozu (__declspec(dllexport)) a spotřebitelů, které nelze používat spravovaný kód, postupujte takto:
  1. Přidáte dvě nové vývozy knihovny DLL, jak je znázorněno v následujícím kódu:
    // 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.
    }

    Poznámka: V aplikaci Visual C++ 2005 je třeba přidat společného jazyka runtime podporu možnost kompilátoru (/clr:oldSyntax) úspěšně zkompilovat předchozí ukázkový kód. Chcete-li přidat možnost kompilátoru podporu runtime běžné jazyk, postupujte takto:

    1. Klepněte na tlačítko projekta klepněte na tlačítko

      ProjectName vlastnosti.



      Poznámka: Název projektu je zástupný symbol pro název projektu.
    2. Rozbalte uzel Vlastnosti konfiguracea potom klepněte na tlačítko

      Obecné.

    3. V pravém podokně klepnutím vyberte Common Language Runtime Support, staré syntaxe (/ CLR: oldSyntax) v

      Podpora společného jazykového modulu Runtime nastavení projektu.
    4. Klepněte na tlačítko

      Použíta klepněte na tlačítko OK.

    Další informace o možnostech podpory kompilátoru společného jazyka runtime naleznete následujícím webu Microsoft Developer Network (MSDN):

    Tyto kroky platí pro celý tento článek.
  2. Knihovny DLL může mít několik spotřebitelů. Pokud má více spotřebitelů, přidejte následující kód v oddíle exports souboru .def knihovny DLL:
    DllEnsureInitPRIVATEDllForceTermPRIVATE

    Nepřidávat tyto řádky a pokud máte dvě knihovny DLL, které exportovat funkce, bude mít aplikace, která odkazuje na knihovnu DLL chyby propojení. Obvykle exportované funkce mají stejné názvy. V případě multiconsumer jednotlivé spotřebitele lze propojit staticky nebo dynamicky knihovny DLL.
  3. Pokud spotřebitel je staticky propojena ke knihovně DLL před první použitím knihovny DLL nebo před použitím cokoli, co závisí na ní ve vaší aplikaci, přidejte následující volání:
    // 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. Po posledním použití knihovny DLL v aplikaci přidejte následující kód:
    // 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. Pokud spotřebitel je dynamicky propojen ke knihovně DLL, vložte následující kód:
    • Vložit fragment 1 (viz krok 3) bezprostředně po první Funkce LoadLibrary u knihovny DLL.
    • Vložte fragment 2 (viz krok 4) těsně před poslední FreeLibrary pro knihovnu DLL.

Chcete-li upravit založená na modelu COM DLL

  • Změna funkce export DLL DllCanUnloadNow, DllGetClassObject, funkce DllRegisterServera DllUnregisterServer jak je ukázáno v následujícím kódu:
    //  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;
    }

Upravit knihovny DLL obsahující spotřebitelů, které používají spravovaný kód a vývozy knihovny DLL nebo spravované vstupních bodů

Chcete-li upravit knihovny DLL obsahující spotřebitelů, které používají spravovaný kód a knihovny dll exportuje nebo spravované vstupní body, postupujte takto:
  1. Provádění spravované třídy s statické členské funkce pro inicializaci a ukončení. Soubor CPP přidáte do projektu implementace spravované třídy pomocí statické členy pro inicializaci a ukončení:
    // 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. Volejte tyto funkce před odkazovat na knihovnu DLL a po dokončení jeho použití. Volejte člen inicializační a ukončovcí v hlavní funkce:
    // Main.cpp
    #using <mscorlib.dll>
    using namespace System;
    using namespace System::Reflection;
    #using "ijwdll.dll";

    int main() {
    int retval = ManagedWrapper::minitialize();
    ManagedWrapper::mterminate();
    }

Uživatelé Visual C++ .NET 2002

Poznámka: Přestože chybová zpráva linker LNK4243 neexistuje ve verzi produktu Visual C++ .NET 2002, by měli uživatelé Visual C++ .NET 2002, postupujte podle pokynů uvedených dříve při vývoji smíšené knihovny DLL.


Visual C++ .NET 2003 verze produktu obsahuje další záhlaví provést ruční inicializace pohodlnější. Chcete-li řešení uvedených v tomto článku Práce s Visual C++ .NET 2002, nutné přidat záhlaví souboru do projektu _vcclrit.h s tímto názvem:
/**** _vcclrit.h
*
* Copyright (c) Microsoft Corporation. All rights reserved.
*
* Purpose:
* This file defines the functions and variables used by user
* to initialize CRT and the dll in IJW scenario.
*
****/

#pragma once

#ifdef __cplusplus
extern "C" {
#endif

extern IMAGE_DOS_HEADER __ImageBase;

BOOL WINAPI _DllMainCRTStartup(
HANDLE hDllHandle,
DWORD dwReason,
LPVOID lpreserved
);
#ifdef __cplusplus
}
#endif

#ifdef _cplusplus
#define __USE_GLOBAL_NAMESPACE ::
#else
#define __USE_GLOBAL_NAMESPACE
#endif

// Used to lock
__declspec( selectany ) LONG volatile __lock_handle = 0;

// Init called
__declspec(selectany) BOOL volatile __initialized = FALSE;

// Term called
__declspec( selectany ) BOOL volatile __terminated = FALSE;

__inline BOOL WINAPI __crt_dll_initialize()
{
// Try to make the variable names unique, so that the variables
// do not even clash with macros.
static BOOL volatile (__retval) = FALSE;
static DWORD volatile (__lockThreadId) = 0xffffffff;
DWORD volatile (__currentThreadId) = __USE_GLOBAL_NAMESPACE(GetCurrentThreadId)();
int (__int_var)=0;

// Take Lock; this is needed for multithreaded scenario.
// Additionally, the threads need to wait here to make sure that the dll
// is initialized when they get past this function.
while ( __USE_GLOBAL_NAMESPACE(InterlockedExchange)( &(__lock_handle), 1) == 1 )
{
++(__int_var);
if ((__lockThreadId) == (__currentThreadId))
{
return TRUE;
}
__USE_GLOBAL_NAMESPACE(Sleep)( (__int_var)>1000?100:0 );

// If you hang in this loop, this implies that your
// dllMainCRTStartup is hung on another thread.
// The most likely cause of this is a hang in one of your
// static constructors or destructors.
}
// Note: you do not really need any interlocked stuff here because the
// writes are always in the lock. Only reads are outside the lock.
(__lockThreadId) = (__currentThreadId);
__try {
if ( (__terminated) == TRUE )
{
(__retval) = FALSE;
}
else if ( (__initialized) == FALSE )
{
(__retval) = (_DllMainCRTStartup)( ( HINSTANCE )( &__ImageBase ), DLL_PROCESS_ATTACH, 0 );
(__initialized) = TRUE;
}

} __finally {
// revert the __lockThreadId
(__lockThreadId) = 0xffffffff;
// Release Lock
__USE_GLOBAL_NAMESPACE(InterlockedExchange)(&(__lock_handle),0);
}
return (__retval);
}

__inline BOOL WINAPI __crt_dll_terminate()
{
static BOOL volatile (__retval) = TRUE;
static DWORD volatile (__lockThreadId) = 0xffffffff;
DWORD volatile (__currentThreadId) = __USE_GLOBAL_NAMESPACE(GetCurrentThreadId)();
int (__int_var)=0;

// Take Lock; this lock is needed to keep Terminate
// in sync with Initialize.
while ( __USE_GLOBAL_NAMESPACE(InterlockedExchange)( &(__lock_handle), 1) == 1 )
{
++(__int_var);
if ((__lockThreadId) == (__currentThreadId))
{
return TRUE;
}
__USE_GLOBAL_NAMESPACE(Sleep)( (__int_var)>1000?100:0 );

// If you hang in this loop, this implies that your
// dllMainCRTStartup is hung on another thread. The most likely
// cause of this is a hang in one of your static constructors
// or destructors.
}
// Note: you do not really need any interlocked stuff here because the
// writes are always in the lock. Only reads are outside the lock.
(__lockThreadId) = (__currentThreadId);
__try {
if ( (__initialized) == FALSE )
{
(__retval) = FALSE;
}
else if ( (__terminated) == FALSE )
{
(__retval) = _DllMainCRTStartup( ( HINSTANCE )( &(__ImageBase) ), DLL_PROCESS_DETACH, 0 );
(__terminated) = TRUE;
}
} __finally {
// revert the __lockThreadId
(__lockThreadId) = 0xffffffff;
// Release Lock
__USE_GLOBAL_NAMESPACE(InterlockedExchange)(&(__lock_handle),0);
}
return (__retval);
}

Odkazy

Další informace získáte v následujícím článku znalostní báze Microsoft Knowledge Base:

309694 Chyba: výjimka AppDomainUnloaded při použití spravovaného rozšíření pro Visual C++ součásti

Vlastnosti

ID článku: 814472 - Poslední kontrola: 16. 1. 2017 - Revize: 1

Váš názor