При сборке управляемых расширений для проектов DLL C++ поступают предупреждения компоновщика.

В этой статье содержатся сведения об устранении предупреждений компоновщика при создании управляемых расширений для проектов DLL C++.

Исходная версия продукта: Visual C++
Исходный номер базы знаний: 814472

Симптомы

Во время компиляции или во время компоновки появляется одно из следующих сообщений об ошибке:

LNK2001 ошибок средств компоновщика
"неразрешенный внешний символ "symbol"
LNK4210 предупреждений средств компоновщика
'. Раздел CRT существует; могут быть необработанными статические инициализации или теминаторы. При сборке управляемых расширений для проектов DLL C++ вы получаете предупреждения компоновщика.
LNK4243 предупреждений средств компоновщика
"БИБЛИОТЕКА DLL, содержащая объекты, скомпилированные с помощью /clr, не связана с /NOENTRY; изображение может работать неправильно.

Эти предупреждения могут возникать в следующих случаях:

  • При компиляции связывания объектов с помощью параметра /clr .
  • При создании одного из следующих проектов:
    • Шаблон веб-службы ASP.NET
    • Шаблон библиотеки классов
    • Шаблон библиотеки элементов управления Windows
  • При добавлении кода, использующего глобальные переменные или собственные классы (т. е. не __gcили __value), со статическими элементами данных. Например, библиотека шаблонов ActiveX (ATL), классы Microsoft Foundation (MFC) и C Run-Time (CRT).

Примечание.

Вы можете получить ошибки LNK2001 и LNK4210 с проектами, на которые не влияет проблема, описанная в этой статье. Однако на проект определенно влияет проблема, описанная в этой статье, если разрешение предупреждения LNK2001 или LNK4210 приводит к LNK4243 предупреждение или если связывание проекта создает предупреждение LNK4243.

Причина

Следующие проекты создаются по умолчанию в виде библиотеки динамической компоновки (DLL) без привязки к собственным библиотекам (таким как CRT, ATL или MFC) и без глобальных переменных или собственных классов со статическими элементами данных:

  • Шаблон веб-службы ASP.NET
  • Шаблон библиотеки классов
  • Шаблон библиотеки элементов управления Windows

При добавлении кода, использующего глобальные переменные или собственные классы со статическими элементами данных (например, библиотеки ATL, MFC и CRT используют глобальные переменные), во время компиляции будут отображаться сообщения об ошибках компоновщика. В этом случае необходимо добавить код для инициализации статических переменных вручную. Дополнительные сведения о том, как это сделать, см. в разделе Разрешение этой статьи.

Для удобства в этой статье глобальные переменные и статические элементы данных собственных классов называются статическими или статическими переменными с этого момента вперед.

Эта проблема вызвана проблемой загрузки смешанной библиотеки DLL. Смешанные библиотеки DLL (т. е. библиотеки DLL, содержащие как управляемый, так и машинный код) могут столкнуться с сценариями взаимоблокировки при некоторых обстоятельствах, когда они загружаются в адресное пространство процесса, особенно когда система находится под нагрузкой. Упомянутые ранее сообщения об ошибках компоновщика были включены в компоновщике, чтобы убедиться, что клиенты знают о возможности взаимоблокировки и обходных решениях, описанных в этом документе.

Разрешение

Управляемые расширения для проектов C++, созданные как библиотеки DLL по умолчанию, не связываются с собственными библиотеками C/C++, такими как библиотека времени выполнения C (CRT), ATL или MFC, и не используют статические переменные. Кроме того, параметры проекта указывают, что библиотеки DLL должны быть связаны с включенным параметром /NOENTRY .

Это делается потому, что связывание с точкой входа приводит к запуску управляемого кода во время DllMain, что не безопасно (смDllMain. ограниченный набор действий, которые можно выполнить во время его область).

Библиотека DLL без точки входа не может инициализировать статические переменные, кроме простых типов, таких как целые числа. Как правило, в библиотеке DLL /NOENTRY нет статических переменных.

Библиотеки ATL, MFC и CRT зависят от статических переменных, поэтому вы также не можете использовать эти библиотеки из этих библиотек DLL без внесения изменений.

Если библиотеке DLL смешанного режима необходимо использовать статические библиотеки или библиотеки, зависящие от статики (например, ATL, MFC или CRT), необходимо изменить библиотеку DLL, чтобы она была инициализирована вручную.

Первым шагом к инициализации вручную является отключение кода автоматической инициализации, что небезопасно для смешанных библиотек DLL и может привести к взаимоблокировке. Чтобы отключить код инициализации, выполните следующие действия.

Удаление точки входа управляемой библиотеки DLL

  1. Ссылка с помощью /NOENTRY. В Обозреватель решений щелкните правой кнопкой мыши узел проекта и выберите пункт Свойства. В диалоговом окне Страницы свойств щелкните Компоновщик, выберите Пункт Командная строка, а затем добавьте этот параметр в поле Дополнительные параметры .

  2. Ссылка msvcrt.lib. В диалоговом окне Страницы свойств щелкните Компоновщик, щелкните Входные данные, а затем добавьте msvcrt.lib в свойство Дополнительные зависимости .

  3. Удалите nochkclr.obj. На странице Входные данные (на той же странице, что и на предыдущем шаге) удалите nochkclr.obj из свойства Дополнительные зависимости .

  4. Ссылка в CRT. На странице Входные данные (на той же странице, что и на предыдущем шаге) добавьте __DllMainCRTStartup@12 в свойство Force Symbol References .

    Если вы используете командную строку, укажите указанные выше параметры проекта следующим образом:

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

Изменение компонентов, которые используют библиотеку DLL для инициализации вручную

После удаления явной точки входа необходимо изменить компоненты, использующие библиотеку DLL для инициализации вручную, в зависимости от способа реализации библиотеки DLL:

  • Библиотека DLL вводится с помощью экспортов DLL (__declspec(dllexport)), и потребители не могут использовать управляемый код, если они связаны статически или динамически с библиотекой DLL.
  • Библиотека DLL является библиотекой DLL на основе COM.
  • Потребители библиотеки DLL могут использовать управляемый код, а библиотека DLL содержит либо экспорт dll, либо управляемые точки входа.

Изменение библиотек DLL, которые вы вводите с помощью экспортов DLL и потребителей, которые не могут использовать управляемый код

Чтобы изменить библиотеки DLL, которые вы вводите с помощью экспортов DLL (__declspec(dllexport)), и потребителей, которые не могут использовать управляемый код, выполните следующие действия.

  1. Добавьте два новых экспорта в библиотеку DLL, как показано в следующем коде:

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

    Чтобы добавить параметр компилятора поддержки среды CLR, выполните следующие действия.

    1. Щелкните Проект, а затем — Свойства ProjectName.

      Примечание.

      ProjectName — это заполнитель для имени проекта.

    2. Разверните узел Свойства конфигурации, а затем щелкните Общие.

    3. В правой области щелкните , чтобы выбрать Поддержка common Language Runtime, Old Syntax (/clr:oldSyntax) в параметрах проекта поддержки среды CLR .

    4. Чтобы выполнить поиск абонентской группы для пользователя в поле Абонентская группа (телефонный контекст), нажмите кнопку Обзор.

    Дополнительные сведения о параметрах компилятора поддержки среды CLR см. в разделе /clr (компиляция CLR).

    Эти действия применимы ко всей статье.

  2. Библиотека DLL может иметь несколько потребителей. Если у него есть несколько потребителей, добавьте следующий код в ФАЙЛ DLL с расширением DEF в разделе экспортов:

    DllEnsureInitPRIVATE
    DllForceTermPRIVATE
    

    Если вы не добавите эти строки и у вас есть два библиотеки DLL, которые экспортируют функции, приложение, ссылающееся на библиотеку DLL, будет иметь ошибки связи. Как правило, экспортированные функции имеют одинаковые имена. В случае с несколькими получателями каждый потребитель может быть связан статически или динамически с библиотекой DLL.

  3. Если объект-получатель статически связан с библиотекой DLL, перед первым использованием библиотеки DLL или перед использованием всех элементов, которые зависят от нее в приложении, добавьте следующий вызов:

    // 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 в приложении добавьте следующий код:

    // 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. Если потребитель динамически связан с библиотекой DLL, вставьте код следующим образом:

    • Вставьте фрагмент 1 (см. шаг 3) сразу после первого LoadLibrary для библиотеки DLL.
    • Вставьте фрагмент 2 (см. шаг 4) непосредственно перед последней библиотекой FreeLibrary для библиотеки DLL.

Изменение библиотек DLL на основе COM

Измените функции DllCanUnloadNowэкспорта DLL , DllGetClassObject, DllRegisterServerи DllUnregisterServer , как показано в следующем коде:

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

Изменение библиотеки DLL, содержащей потребителей, которые используют управляемый код и экспорт DLL или управляемые точки входа

Чтобы изменить библиотеку DLL, содержащую потребителей, которые используют управляемый код и экспорт DLL или управляемые точки входа, выполните следующие действия.

  1. Реализуйте управляемый класс со статическими функциями-членами для инициализации и завершения. Добавьте файл .cpp в проект, реализуя управляемый класс со статическими элементами для инициализации и завершения:

    // 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 и после завершения ее использования. Вызовите функции-члены инициализации и завершения в main:

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