Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Den här artikeln innehåller information om hur du löser länkningsvarningar när du skapar hanterade tillägg för C++ DLL-projekt.
Ursprunglig produktversion: Visual C++
Ursprungligt KB-nummer: 814472
Symptom
Du får något av följande felmeddelanden vid kompileringstillfället eller vid länktiden:
Fel LNK2001 för länkverktyg
"olöst extern symbol "symbol" '
Länkverktyg Varning LNK4210
'. CRT-avsnittet finns; det kan finnas ohanterade statiska initieringar eller teminatorer'Du får Linker-varningar när du skapar hanterade tillägg för C++ DLL-projekt
Varning LNK4243 för länkverktyg
"DLL som innehåller objekt som kompilerats med /clr är inte länkad till /NOENTRY; avbildningen kanske inte körs korrekt".
Dessa varningar kan inträffa under följande omständigheter:
- När du kompilerar länkning av objekt med växeln /clr .
- När du skapar något av följande projekt:
- ASP.NET webbtjänstmall
- Mall för klassbibliotek
- Mall för Windows-kontrollbibliotek
- När du har lagt till kod som använder globala variabler eller interna klasser (dvs. inte
__gceller__value) med statiska datamedlemmar. Till exempel ActiveX-mallbiblioteket (ATL), Microsoft Foundation-klasserna (MFC) och CRT-klasserna (C Run-Time).
Kommentar
Du kan få LNK2001 och LNK4210 fel med projekt som inte påverkas av problemet som beskrivs i den här artikeln. Projektet påverkas dock definitivt av problemet som beskrivs i den här artikeln om lösningen av en LNK2001 eller LNK4210 varning leder till en LNK4243 varning, eller om länkning av projektet genererar en LNK4243 varning.
Orsak
Följande projekt skapas som standard som ett dynamiskt länkbibliotek (DLL) utan någon koppling till interna bibliotek (till exempel CRT, ATL eller MFC) och utan några globala variabler eller interna klasser med statiska datamedlemmar:
- ASP.NET webbtjänstmall
- Mall för klassbibliotek
- Mall för Windows-kontrollbibliotek
Om du lägger till kod som använder globala variabler eller interna klasser med statiska datamedlemmar (till exempel ATL-, MFC- och CRT-biblioteken använder globala variabler) får du felmeddelanden om länkningsfel vid kompileringstillfället. När detta inträffar måste du lägga till kod för att initiera de statiska variablerna manuellt. Mer information om hur du gör detta finns i avsnittet Lösning i den här artikeln.
För enkelhetens skull refererar den här artikeln till globala variabler och statiska datamedlemmar i interna klasser som statiska eller statiska variabler från och med nu.
Det här problemet orsakas av det blandade DLL-inläsningsproblemet. Blandade DLL:er (dLL:er som innehåller både hanterad och intern kod) kan stöta på dödlägesscenarier under vissa omständigheter när de läses in i processadressutrymmet, särskilt när systemet är under stress. De länkfelmeddelanden som nämndes tidigare aktiverades i länkaren för att se till att kunderna är medvetna om risken för dödläge och de lösningar som beskrivs i det här dokumentet.
Åtgärd
Hanterade tillägg för C++-projekt som skapas som DLL-filer länkar som standard inte till interna C/C++-bibliotek, till exempel CRT-biblioteket (C Run-time), ATL eller MFC och använder inga statiska variabler. Dessutom anger projektinställningarna att DLL:er ska länkas med alternativet /NOENTRY aktiverat.
Detta görs eftersom länkning till en startpunkt gör att hanterad kod körs under DllMain, vilket inte är säkert (se DllMain för den begränsade uppsättningen saker du kan göra under dess omfång).
En DLL utan startpunkt kan inte initiera statiska variabler förutom enkla typer som heltal. Du har vanligtvis inte statiska variabler i en /NOENTRY DLL.
ATL-, MFC- och CRT-biblioteken förlitar sig alla på statiska variabler, så du kan inte heller använda dessa bibliotek inifrån dessa DLL:er utan att först göra ändringar.
Om DLL:et för blandat läge behöver använda statiska objekt eller bibliotek som är beroende av statiska objekt (till exempel ATL, MFC eller CRT) måste du ändra DLL-filen så att statiska objekt initieras manuellt.
Det första steget för manuell initiering är att se till att du inaktiverar den automatiska initieringskoden, som är osäker med blandade DLL:er och kan orsaka dödläge. Om du vill inaktivera initieringskoden följer du stegen.
Ta bort startpunkten för den hanterade DLL:en
Länka med /NOENTRY. Högerklicka på projektnoden i Solution Explorer och klicka på Egenskaper. I dialogrutan Egenskapssidor klickar du på Länkare, klickar på Kommandorad och lägger sedan till den här växeln i fältet Ytterligare alternativ.
Länka msvcrt.lib. I dialogrutan Egenskapssidor klickar du på Länkare, klickar på Indata och lägger sedan till msvcrt.lib i egenskapen Ytterligare beroenden.
Ta bort nochkclr.obj. På sidan Indata (samma sida som i föregående steg) tar du bort nochkclr.obj från egenskapen Ytterligare beroenden .
Länk i CRT. På sidan Indata (samma sida som i föregående steg) lägger du till __DllMainCRTStartup@12 i egenskapen Tvinga symbolreferenser .
Om du använder kommandotolken anger du ovanstående projektinställningar med följande:
LINK /NOENTRY msvcrt.lib /NODEFAULTLIB:nochkclr.obj /INCLUDE:__DllMainCRTStartup@12
Ändra komponenter som använder DLL för manuell initiering
När du har avlägsnat den explicita startpunkten måste du ändra komponenter som använder DLL:en för manuell initiering, beroende på hur DLL-filen implementeras:
- Din DLL anges med DLL-exporter (
__declspec(dllexport)), och dina konsumenter kan inte använda hanterad kod om de är statiskt eller dynamiskt länkade till din DLL. - Din DLL är en COM-baserad DLL.
- Konsumenter av din DLL kan använda hanterad kod och DLL-filen innehåller antingen DLL-exporter eller hanterade startpunkter.
Ändra DLL:er som du anger med hjälp av DLL-exporter och konsumenter som inte kan använda hanterad kod
Följ dessa steg om du vill ändra DLL:er som du anger med hjälp av dll-exporter (__declspec(dllexport)) och konsumenter som inte kan använda hanterad kod:
Lägg till två nya exporter till din DLL, som du ser i följande kod:
// 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. }Följ dessa steg om du vill lägga till det vanliga alternativet för språkkörningsstöd för kompilator:
Klicka på Projekt och sedan på ProjectName-egenskaper.
Kommentar
ProjectName är en platshållare för projektets namn.
Expandera Konfigurationsegenskaper och klicka sedan på Allmänt.
I den högra rutan klickar du för att välja Stöd för common language runtime, gammal syntax (/clr:oldSyntax) i inställningarna för Common Language Runtime-supportprojekt .
Klicka först på Använd och sedan på OK.
Mer information om vanliga alternativ för språkkörningsstöd för kompilator finns i /clr (Common Language Runtime Compil).
De här stegen gäller för hela artikeln.
Din DLL kan ha flera konsumenter. Om den har flera konsumenter lägger du till följande kod i DLL.def-filen i exportavsnittet:
DllEnsureInitPRIVATE DllForceTermPRIVATEOm du inte lägger till dessa rader, och om du har två DLL:er som exporterar funktioner, får programmet som länkar till DLL-filen länkfel. Normalt har de exporterade funktionerna samma namn. I ett multiconsumer-fall kan varje konsument länkas statiskt eller dynamiskt till din DLL.
Om konsumenten är statiskt länkad till DLL:en, innan du använder DLL-filen första gången eller innan du använder något som är beroende av den i ditt program, lägger du till följande anrop:
// 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 }Efter den senaste användningen av DLL:en i ditt program lägger du till följande kod:
// 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 }Om konsumenten är dynamiskt länkad till DLL-filen infogar du kod på följande sätt:
- Infoga kodfragment 1 (se steg 3) omedelbart efter den första LoadLibrary för DLL:en.
- Infoga kodfragment 2 (se steg 4) omedelbart före den sista FreeLibrary för DLL:en.
Ändra COM-baserade DLL:er
Ändra DLL-exportfunktionerna DllCanUnloadNow, DllGetClassObject, DllRegisterServeroch DllUnregisterServer som visas i följande kod:
// 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;
}
Ändra DLL som innehåller konsumenter som använder hanterad kod och DLL-exporter eller hanterade startpunkter
Följ dessa steg om du vill ändra DLL som innehåller konsumenter som använder hanterad kod och dll-exporter eller hanterade startpunkter:
Implementera en hanterad klass med statiska medlemsfunktioner för initiering och avslutning. Lägg till en .cpp fil i projektet och implementera en hanterad klass med statiska medlemmar för initiering och avslutning:
// 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 */Anropa dessa funktioner innan du refererar till DLL och när du har använt den. Anropa initierings- och avslutningsmedlemsfunktionerna i
main:// Main.cpp #using <mscorlib.dll> using namespace System; using namespace System::Reflection; #using "ijwdll.dll"; int main() { int retval = ManagedWrapper::minitialize(); ManagedWrapper::mterminate(); }