C++ DLL projeleri için yönetilen uzantılar oluştururken bağlayıcı uyarıları alırsınız

Bu makalede, C++ DLL projeleri için yönetilen uzantılar oluştururken bağlayıcı uyarılarını çözümleme hakkında bilgi sağlanır.

Orijinal ürün sürümü: Visual C++
Özgün KB numarası: 814472

Belirtiler

Derleme zamanında veya bağlantı zamanında aşağıdaki hata iletilerinden birini alırsınız:

Bağlayıcı Araçları Hatası LNK2001
'çözülmemiş dış simge "sembol" '
Bağlayıcı Araçları Uyarısı LNK4210
'. CRT bölümü var; işlenmeyen statik başlatmalar veya teminatorlar olabilir'C++ için Yönetilen Uzantılar DLL projeleri oluştururken Bağlayıcı Uyarıları alırsınız
Bağlayıcı Araçları Uyarısı LNK4243
'/clr ile derlenen nesneleri içeren DLL , /NOENTRY ile bağlantılı değil; görüntü düzgün çalışmayabilir'.

Bu uyarılar aşağıdaki durumlarda oluşabilir:

  • Bağlama nesnelerini /clr anahtarıyla derlerken.
  • Aşağıdaki projelerden birini oluştururken:
    • web hizmeti şablonunu ASP.NET
    • Sınıf Kitaplığı Şablonu
    • Windows Denetim Kitaplığı Şablonu
  • Statik veri üyeleriyle genel değişkenleri veya yerel sınıfları (veya __valuedeğil__gc) kullanan kod eklediğinizde. Örneğin, ActiveX Şablon Kitaplığı (ATL), Microsoft Foundation Sınıfları (MFC) ve C Run-Time (CRT) sınıfları.

Not

Bu makalede açıklanan sorundan etkilenmeyen projelerle ilgili LNK2001 ve LNK4210 hataları alabilirsiniz. Ancak, bir LNK2001 veya LNK4210 uyarısının çözülmesi LNK4243 uyarıya yol açıyorsa veya projenin bağlanması LNK4243 bir uyarı oluşturuyorsa, proje kesinlikle bu makalede açıklanan sorundan etkilenir.

Neden

Aşağıdaki projeler varsayılan olarak yerel kitaplıklara (CRT, ATL veya MFC gibi) herhangi bir bağlantı olmadan ve statik veri üyelerine sahip genel değişkenler veya yerel sınıflar olmadan dinamik bağlantı kitaplığı (DLL) olarak oluşturulur:

  • web hizmeti şablonunu ASP.NET
  • Sınıf Kitaplığı Şablonu
  • Windows Denetim Kitaplığı Şablonu

Statik veri üyeleriyle (örneğin, ATL, MFC ve CRT kitaplıkları genel değişkenleri kullanır) genel değişkenler veya yerel sınıflar kullanan kod eklerseniz, derleme zamanında bağlayıcı hata iletileri alırsınız. Bu durumda, statik değişkenleri el ile başlatmak için kod eklemeniz gerekir. Bunun nasıl yapacağı hakkında daha fazla bilgi için bu makalenin Çözüm bölümüne bakın.

Kolaylık sağlamak için, bu makale yerel sınıfların genel değişkenlerini ve statik veri üyelerini bu noktadan sonra statik veya statik değişkenler olarak ifade eder.

Bu sorun, karma DLL yükleme sorunundan kaynaklanır. Karma DLL'ler (yani hem yönetilen hem de yerel kod içeren DLL'ler), özellikle sistem stres altındayken işlem adres alanına yüklendiklerinde bazı durumlarda kilitlenme senaryolarıyla karşılaşabilir. Daha önce bahsedilen bağlayıcı hata iletileri, müşterilerin kilitlenme olasılığını ve bu belgede açıklanan geçici çözümlerin farkında olduğundan emin olmak için bağlayıcıda etkinleştirildi.

Çözüm

Varsayılan olarak DLL olarak oluşturulan C++ projeleri için yönetilen uzantılar C çalışma zamanı (CRT) kitaplığı, ATL veya MFC gibi yerel C/C++ kitaplıklarına bağlanmaz ve statik değişken kullanmaz. Ayrıca, proje ayarları DLL'lerin /NOENTRY seçeneği etkin olarak bağlanması gerektiğini belirtir.

Bunun nedeni, bir giriş noktasıyla bağlantı kurmanın güvenli olmayan yönetilen kodun sırasında DllMainçalışmasına neden olmasıdır (kapsamı sırasında yapabileceğiniz sınırlı sayıda işlem için bkz DllMain . ).

Giriş noktası olmayan bir DLL'nin, tamsayılar gibi basit türler dışında statik değişkenleri başlatma yolu yoktur. Genellikle bir /NOENTRY DLL'sinde statik değişkenleriniz yoktur.

ATL, MFC ve CRT kitaplıklarının tümü statik değişkenleri kullanır, bu nedenle bu kitaplıkları önce değişiklik yapmadan bu DLL'lerin içinden de kullanamazsınız.

Karma mod DLL'nizin statiklere (ATL, MFC veya CRT gibi) bağlı statikleri veya kitaplıkları kullanması gerekiyorsa, statiklerin el ile başlatılması için DLL'nizi değiştirmeniz gerekir.

El ile başlatmanın ilk adımı, karma DLL'lerle güvenli olmayan ve kilitlenmeye neden olabilen otomatik başlatma kodunu devre dışı bırakmaktır. Başlatma kodunu devre dışı bırakmak için adımları izleyin.

Yönetilen DLL'nin giriş noktasını kaldırma

  1. /NOENTRY ile bağlantı. Çözüm Gezgini proje düğümüne sağ tıklayın ve Özellikler'e tıklayın. Özellik Sayfaları iletişim kutusunda Bağlayıcı'ya tıklayın, Komut Satırı'na tıklayın ve ardından bu anahtarı Ek Seçenekler alanına ekleyin.

  2. Msvcrt.lib dosyasını bağlayın. Özellik Sayfaları iletişim kutusunda Bağlayıcı'ya tıklayın, Giriş'e tıklayın ve ardından msvcrt.lib dosyasını Ek Bağımlılıklar özelliğine ekleyin.

  3. nochkclr.obj kaldırın. Giriş sayfasında (önceki adımda olduğu gibi), Ek Bağımlılıklar özelliğinden nochkclr.obj kaldırın.

  4. CRT'deki bağlantı. Giriş sayfasında (önceki adımda olduğu gibi), Simge Başvurularını Zorla özelliğine __DllMainCRTStartup@12 ekleyin.

    Komut istemini kullanıyorsanız yukarıdaki proje ayarlarını aşağıdakilerle belirtin:

    LINK /NOENTRY msvcrt.lib /NODEFAULTLIB:nochkclr.obj /INCLUDE:__DllMainCRTStartup@12
    

El ile başlatma için DLL kullanan bileşenleri değiştirme

Açık giriş noktasını kaldırdıktan sonra, DLL'nizin uygulanma şekline bağlı olarak el ile başlatma için DLL kullanan bileşenleri değiştirmeniz gerekir:

  • DLL'niz DLL dışarı aktarmaları ()__declspec(dllexport) kullanılarak girilir ve tüketicileriniz DLL'nize statik veya dinamik olarak bağlıysa yönetilen kodu kullanamaz.
  • DLL'niz COM tabanlı bir DLL'dir.
  • DLL'nizin tüketicileri yönetilen kodu kullanabilir ve DLL'niz DLL dışarı aktarmaları veya yönetilen giriş noktaları içerir.

DLL dışarı aktarmalarını ve yönetilen kodu kullanamıyor tüketicileri kullanarak girdiğiniz DLL'leri değiştirme

Dll dışarı aktarmalarını (__declspec(dllexport)) ve yönetilen kodu kullanamayan tüketicileri kullanarak girdiğiniz DLL'leri değiştirmek için şu adımları izleyin:

  1. Aşağıdaki kodda gösterildiği gibi DLL'nize iki yeni dışarı aktarma ekleyin:

    // 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.
    }
    

    Ortak dil çalışma zamanı desteği derleyici seçeneğini eklemek için şu adımları izleyin:

    1. Project'e ve ardından ProjectName Özellikleri'ne tıklayın.

      Not

      ProjectName , projenin adı için bir yer tutucudur.

    2. Yapılandırma Özellikleri'ni genişletin ve genel'e tıklayın.

    3. Sağ bölmede, Ortak Dil Çalışma Zamanı destek projesi ayarlarında Ortak Dil Çalışma Zamanı Desteği, Eski Söz Dizimi (/clr:oldSyntax) öğesini seçmek için tıklayın.

    4. Uygula'yı ve ardından Tamam'ı tıklatın.

    Ortak dil çalışma zamanı desteği derleyici seçenekleri hakkında daha fazla bilgi için bkz. /clr (Ortak Dil Çalışma Zamanı Derlemesi).

    Bu adımlar makalenin tamamı için geçerlidir.

  2. DLL'nizin çeşitli tüketicileri olabilir. Birden çok tüketicisi varsa, dışarı aktarmalar bölümündeki DLL .def dosyasına aşağıdaki kodu ekleyin:

    DllEnsureInitPRIVATE
    DllForceTermPRIVATE
    

    Bu satırları eklemezseniz ve işlevleri dışarı aktaran iki DLL'niz varsa, DLL'ye bağlanan uygulamada bağlantı hataları olur. Genellikle, dışarı aktarılan işlevler aynı adlara sahiptir. Çok kapsayıcılı bir durumda, her tüketici DLL'nize statik veya dinamik olarak bağlanabilir.

  3. Tüketici DLL'ye statik olarak bağlıysa, DLL'yi ilk kez kullanmadan önce veya uygulamanızda buna bağlı herhangi bir şey kullanmadan önce aşağıdaki çağrıyı ekleyin:

    // 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. DLL'nin uygulamanızda son kullanımından sonra aşağıdaki kodu ekleyin:

    // 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. Tüketici DLL'ye dinamik olarak bağlıysa, kodu aşağıdaki gibi ekleyin:

    • DLL için ilk LoadLibrary'nin hemen arkasına kod parçacığı 1 (bkz. adım 3) ekleyin.
    • DLL için son FreeLibrary'nin hemen önüne kod parçacığı 2 (bkz. adım 4) ekleyin.

COM tabanlı DLL'leri değiştirme

DLL dışarı aktarma işlevlerini DllCanUnloadNow, DllGetClassObject, DllRegisterServerve DllUnregisterServer işlevlerini aşağıdaki kodda gösterildiği gibi değiştirin:

// 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;
}

Yönetilen kod ve DLL dışarı aktarmaları veya yönetilen giriş noktaları kullanan tüketicileri içeren DLL'leri değiştirme

Yönetilen kod ve dll dışarı aktarmaları veya yönetilen giriş noktaları kullanan tüketicileri içeren DLL'yi değiştirmek için şu adımları izleyin:

  1. Başlatma ve sonlandırma için statik üye işlevleriyle yönetilen bir sınıf uygulayın. Başlatma ve sonlandırma için statik üyelere sahip yönetilen bir sınıf uygulayarak projenize bir .cpp dosyası ekleyin:

    // 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. DLL'ye başvurmadan önce ve kullanmayı bitirdikten sonra bu işlevleri çağırın. içinde başlatma ve sonlandırma üyesi işlevlerini mainçağırın:

    // Main.cpp
    
    #using <mscorlib.dll>
    using namespace System;
    using namespace System::Reflection;
    #using "ijwdll.dll";
    
    int main()
    {
        int retval = ManagedWrapper::minitialize();
        ManagedWrapper::mterminate();
    }