C++ の DLL プロジェクトでマネージ拡張をビルドするときにリンカの警告が表示される


現象


コンパイル時またはリンク時に、以下のいずれかのエラー メッセージが表示されることがあります。
リンカ ツール エラー LNK2001

'外部シンボル "symbol" は未解決です。'


リンカ ツールの警告 LNK4210

'CRT セクションが存在します。静的初期化子、または終末記号がハンドルされていない可能性があります。'
C++ の DLL プロジェクトでマネージ拡張をビルドすると、リンカの警告が表示されます。
リンカ ツールの警告 LNK4243

'DLL containing objects compiled with /clr is not linked with /NOENTRY; image may not run correctly'.
これらの警告は、次の状況で発生する場合があります。
  • /clr スイッチを使用して、リンクしているオブジェクトをコンパイルした場合。
  • ASP.NET Web サービス テンプレート、クラス ライブラリ テンプレートまたは Windows コントロール ライブラリ テンプレートのいずれかのプロジェクトをビルドしている場合。
  • グローバル変数、または静的データ メンバを含むネイティブ クラス (__gc または __value 以外) を使用するコードを追加した場合。たとえば、ActiveX Template Library (ATL)、Microsoft Foundation Classes (MFC)、C ランタイム (CRT) クラスなどです。
: この資料に記載されている問題の影響を受けないプロジェクトでも、LNK2001 および LNK4210 エラーが発生することがあります。ただし、LNK2001 または LNK4210 の警告を解決した後に LNK4243 の警告が表示される場合、またはプロジェクトのリンク時に LNK4243 の警告が表示される場合、そのプロジェクトはこの資料に記載された問題の影響を受けています。

原因


次のプロジェクトは、デフォルトでは、ネイティブ ライブラリ (CRT、ATL、MFC など) へのリンケージを持たず、かつグローバル変数や静的データ メンバを持つネイティブ クラスを使用しないダイナミック リンク ライブラリ (DLL) として作成されます。
  • ASP.NET Web Service テンプレート
  • クラス ライブラリ テンプレート
  • Windows コントロール ライブラリ テンプレート
グローバル変数、または静的データ メンバを含むネイティブ クラスを使用するコードを追加すると (たとえば、ATL、MFC、および CRT の各ライブラリはグローバル変数を使用します)、コンパイル時にリンカのエラー メッセージが表示されます。これが発生した場合、静的変数を手動で初期化するためのコードを追加する必要があります。この方法の詳細については、この資料の「解決方法」を参照してください。


便宜上、この資料では、これ以降グローバル変数およびネイティブ クラスの静的データ メンバを "静的変数" と呼びます。


この問題は、混合モードの DLL (マネージ コードとネイティブ コードの両方を含む DLL) の読み込みの問題が原因で発生します。混合モードの DLL は、プロセスのアドレス空間に読み込まれたときに、一部の状況で (特にシステムが高負荷の状態にあるときに) デッドロックを引き起こす可能性があります。上記のリンカのエラー メッセージが表示されるのは、デッドロックの可能性およびこの資料に記載された回避策をユーザーが認識するようにするためです。混合モードの DLL の読み込みに関する問題の詳細については、次のホワイト ペーパーを参照してください。
混在モード DLL 読み込み時の問題

http://msdn2.microsoft.com/en-us/library/aa290048(vs.71).aspx

解決方法


デフォルトで DLL として作成される C++ プロジェクトのマネージ拡張は、C ランタイム (CRT) ライブラリ、ATL、MFC などのネイティブの C/C++ ライブラリにリンクせず、静的変数も使用しません。また、プロジェクト設定では、DLL は /NOENTRY オプションを有効にしてリンクされるように指定されています。


このようになっているのは、エントリ ポイントとリンクすると DllMain の実行中にマネージ コードが実行されるためです。この状況は安全ではありません (DllMain 内の制限事項については、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 にリンクします。[入力] ページ (前の手順と同じページ) で、[シンボル参照の強制] プロパティに __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. 次のコードに示すように、2 つの新しいエクスポートを 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.
    }
    : Visual C++ 2005 で、上記のコードのコンパイルを正常終了するためには、共通言語ランタイム サポート コンパイラ オプション (/clr:oldSyntax) を追加する必要があります。 共通言語ランタイム サポート コンパイラ オプションを追加するには、次の手順を実行します。

    1. [プロジェクト] メニューの [ProjectName のプロパティ] をクリックします。

      : ProjectName には、プロジェクトの名前が入ります。
    2. [構成プロパティ] を展開し、[全般] をクリックします。

    3. 右側のウィンドウで、[共通言語ラインタイム サポート] のプロジェクト設定で [共通言語ランタイム サポート、古い構文 (/clr:oldSyntax)] をクリックします。
    4. [適用] をクリックし、[OK] をクリックします。

    共通言語ランタイムサポート コンパイラ オプションの詳細については、次の Microsoft Developer Network (MSDN) Web サイトを参照してください。
    ここに示す手順は資料全体に適用されます。
  2. DLL は複数のコンシューマを持つことができます。複数のコンシューマがある場合、DLL の .def ファイルの EXPORTS セクションに次のコードを追加します。
    DllEnsureInitPRIVATE
    DllForceTermPRIVATE
    上記の行を追加しないと、関数をエクスポートする DLL が 2 つある場合に、その 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 の直後に Snippet 1 (手順 3. を参照) を挿入します。
    • DLL に対する最後の FreeLibrary の直前に Snippet 2 (手順 4. を参照) を挿入します。

COM ベースの DLL を修正する

  • 次のコードに示すように、DLL のエクスポート関数 DllCanUnloadNow、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 の参照前および DLL の使用後に、これらの関数を呼び出します。初期化用のメンバ関数と終了用のメンバ関数をメインで呼び出します。
    // Main.cpp

    #using <mscorlib.dll>
    using namespace System;
    using namespace System::Reflection;
    #using "ijwdll.dll";

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

Visual C++ .NET 2002 ユーザー

: リンカのエラー メッセージ LNK4243 は Visual C++ .NET 2002 製品版には存在しませんが、Visual C++ .NET 2002 ユーザーも混合モードの DLL の開発時に上記のガイドラインに従うことを推奨します。


Visual C++ .NET 2003 製品版には、手動での初期化を行いやすくする追加のヘッダーが含まれています。この資料に記載した方法を Visual C++ .NET 2002 で実行するには、次のテキストを入力した vcclrit.h という名前のヘッダー ファイルをプロジェクトに追加する必要があります。
/***
* _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);
}

関連情報


関連情報を参照するには、以下の「サポート技術情報」 (Microsoft Knowledge Base) をクリックしてください。

309694 [BUG] Visual C++ コンポーネントのマネージ拡張を使用すると AppDomainUnloaded 例外が発生する