C++ DLL 프로젝트에 대한 관리형 확장을 빌드할 때 링커 경고가 표시됩니다.

이 문서에서는 C++ DLL 프로젝트에 대한 관리형 확장을 빌드할 때 링커 경고를 해결하는 방법에 대한 정보를 제공합니다.

원래 제품 버전: Visual C++
원본 KB 번호: 814472

증상

컴파일 시간 또는 링크 타임에 다음 오류 메시지 중 하나가 수신됩니다.

링커 도구 오류 LNK2001
'해결되지 않은 외부 기호 "symbol" '
링커 도구 경고 LNK4210
'. CRT 섹션이 있습니다. 처리되지 않은 정적 초기화 또는 테미나이터가 있을 수 있습니다.'Managed Extensions for C++ DLL 프로젝트를 빌드할 때 링커 경고가 표시됩니다.
링커 도구 경고 LNK4243
'/clr로 컴파일된 개체를 포함하는 DLL은 /NOENTRY와 연결되지 않습니다. 이미지가 올바르게 실행되지 않을 수 있습니다.'

이러한 경고는 다음과 같은 상황에서 발생할 수 있습니다.

  • /clr 스위치를 사용하여 개체 연결을 컴파일하는 경우
  • 다음 프로젝트 중 하나를 빌드하는 경우:
    • ASP.NET 웹 서비스 템플릿
    • 클래스 라이브러리 템플릿
    • Windows 컨트롤 라이브러리 템플릿
  • 정적 데이터 멤버와 함께 전역 변수 또는 네이티브 클래스(즉, 또는 __value가 아님__gc)를 사용하는 코드를 추가한 경우 예를 들어 ATL(ActiveX 템플릿 라이브러리), MFC(Microsoft Foundation Classs) 및 CRT(C Run-Time) 클래스가 있습니다.

참고

이 문서에 설명된 문제의 영향을 받지 않는 프로젝트에서 LNK2001 및 LNK4210 오류가 발생할 수 있습니다. 그러나 LNK2001 또는 LNK4210 경고를 해결하면 LNK4243 경고가 발생하거나 프로젝트를 연결하면 LNK4243 경고가 발생하는 경우 이 문서에 설명된 문제의 영향을 받습니다.

원인

다음 프로젝트는 기본적으로 네이티브 라이브러리(예: CRT, ATL 또는 MFC)에 대한 링크가 없고 정적 데이터 멤버가 있는 전역 변수 또는 네이티브 클래스 없이 DLL(동적 링크 라이브러리)으로 만들어집니다.

  • ASP.NET 웹 서비스 템플릿
  • 클래스 라이브러리 템플릿
  • Windows 컨트롤 라이브러리 템플릿

정적 데이터 멤버가 있는 전역 변수 또는 네이티브 클래스를 사용하는 코드를 추가하는 경우(예: ATL, MFC 및 CRT 라이브러리에서 전역 변수 사용) 컴파일 시 링커 오류 메시지가 표시됩니다. 이 경우 정적 변수를 수동으로 초기화하는 코드를 추가해야 합니다. 이 작업을 수행하는 방법에 대한 자세한 내용은 이 문서의 해결 섹션을 참조하세요.

편의를 위해 이 문서에서는 네이티브 클래스의 전역 변수 및 정적 데이터 멤버를 이 시점부터 정적또는 정적 변수 로 참조합니다.

이 문제는 혼합 DLL 로드 문제로 인해 발생합니다. 혼합 DLL(즉, 관리 코드와 네이티브 코드를 모두 포함하는 DLL)은 프로세스 주소 공간에 로드될 때, 특히 시스템이 스트레스를 받고 있는 경우에 교착 상태 시나리오가 발생할 수 있습니다. 앞에서 언급한 링커 오류 메시지는 링커에서 사용하도록 설정되어 고객이 교착 상태의 가능성과 이 문서에 설명된 해결 방법을 알고 있는지 확인합니다.

해결 방법

기본적으로 DLL로 만들어지는 C++ 프로젝트에 대한 관리되는 확장은 C 런타임(CRT) 라이브러리, ATL 또는 MFC와 같은 네이티브 C/C++ 라이브러리에 연결되지 않으며 정적 변수를 사용하지 않습니다. 또한 프로젝트 설정은 DLL을 /NOENTRY 옵션을 사용하도록 설정하여 연결하도록 지정합니다.

진입점으로 연결하면 관리 코드가 동안 DllMain실행되므로 안전하지 않습니다(scope 동안 수행할 수 있는 제한된 작업 집합 참조DllMain).

진입점이 없는 DLL은 정수와 같은 단순 형식을 제외하고 정적 변수를 초기화할 방법이 없습니다. 일반적으로 /NOENTRY DLL에는 정적 변수가 없습니다.

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의 링크입니다. 입력 페이지(이전 단계와 동일한 페이지)에서 Force Symbol References 속성에 __DllMainCRTStartup@12 추가합니다.

    명령 프롬프트를 사용하는 경우 다음을 사용하여 위의 프로젝트 설정을 지정합니다.

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

수동 초기화를 위해 DLL을 사용하는 구성 요소 수정

명시적 진입점을 제거한 후 DLL이 구현되는 방식에 따라 수동 초기화를 위해 DLL을 사용하는 구성 요소를 수정해야 합니다.

  • DLL 내보내기(__declspec(dllexport))를 사용하여 DLL을 입력하고 소비자는 DLL에 정적 또는 동적으로 연결된 경우 관리 코드를 사용할 수 없습니다.
  • DLL은 COM 기반 DLL입니다.
  • DLL의 소비자는 관리 코드를 사용할 수 있으며 DLL에는 DLL 내보내기 또는 관리되는 진입점이 포함됩니다.

관리 코드를 사용할 수 없는 DLL 내보내기 및 소비자를 사용하여 입력한 DLL 수정

dll 내보내기(__declspec(dllexport)) 및 관리 코드를 사용할 수 없는 소비자를 사용하여 입력한 DLL을 수정하려면 다음 단계를 수행합니다.

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

    공용 언어 런타임 지원 컴파일러 옵션을 추가하려면 다음 단계를 수행합니다.

    1. 프로젝트를 클릭한 다음 ProjectName 속성을 클릭합니다.

      참고

      ProjectName 은 프로젝트 이름의 자리 표시자입니다.

    2. 구성 속성을 확장한 다음 일반을 클릭합니다.

    3. 오른쪽 창에서 공용 언어 런타임 지원 프로젝트 설정에서 공용 언어 런타임 지원, 이전 구문(/clr:oldSyntax)을 클릭하여 선택합니다.

    4. 사용자에 대한 다이얼 플랜을 찾으려면 다이얼 플랜(전화 컨텍스트) 상자에서 찾아보기를 클릭합니다.

    공용 언어 런타임 지원 컴파일러 옵션에 대한 자세한 내용은 /clr(공용 언어 런타임 컴파일)을 참조하세요.

    이러한 단계는 전체 문서에 적용됩니다.

  2. DLL에는 여러 소비자가 있을 수 있습니다. 소비자가 여러 개인 경우 내보내기 섹션의 DLL .def 파일에 다음 코드를 추가합니다.

    DllEnsureInitPRIVATE
    DllForceTermPRIVATE
    

    이러한 줄을 추가하지 않고 함수를 내보내는 두 개의 DLL이 있는 경우 DLL에 연결되는 애플리케이션에 링크 오류가 발생합니다. 일반적으로 내보낸 함수의 이름은 동일합니다. 다중 구성 요소의 경우 각 소비자는 DLL에 정적으로 또는 동적으로 연결할 수 있습니다.

  3. 소비자가 DLL에 정적으로 연결된 경우, 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에 동적으로 연결된 경우 다음과 같이 코드를 삽입합니다.

    • DLL의 첫 번째 LoadLibrary 바로 뒤에 코드 조각 1(3단계 참조)을 삽입합니다.
    • DLL의 마지막 FreeLibrary 바로 앞에 코드 조각 2(4단계 참조)를 삽입합니다.

COM 기반 DLL 수정

다음 코드에 설명된 대로 , , DllRegisterServer및 DLL 내보내기 함수를 수정합니다.DllCanUnloadNowDllGetClassObjectDllUnregisterServer

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