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

この記事では、C++ DLL プロジェクトのマネージド拡張機能をビルドするときのリンカー警告の解決に関する情報を提供します。

元の製品バージョン: Visual C++
元の KB 番号: 814472

現象

コンパイル時またはリンク時に、次のいずれかのエラー メッセージが表示されます。

リンカー ツール エラー LNK2001
'未解決の外部シンボル "symbol" '
リンカー ツールの警告LNK4210
'.CRT セクションが存在します。ハンドルされない静的初期化またはテニレーターが存在する可能性があります。DLL プロジェクトをビルドするときにリンカーの警告Managed Extensions for C++受け取ります
リンカー ツールの警告LNK4243
/clr でコンパイルされたオブジェクトを含む 'DLL は/NOENTRY とリンクされていません。イメージが正しく実行されない可能性があります。

これらの警告は、次の状況で発生する可能性があります。

  • /clr スイッチを使用してリンク オブジェクトをコンパイルする場合。
  • 次のいずれかのプロジェクトをビルドする場合:
    • ASP.NET Web サービス テンプレート
    • クラス ライブラリ テンプレート
    • Windows コントロール ライブラリ テンプレート
  • 静的データ メンバーと共にグローバル変数またはネイティブ クラス (つまり、または __valueではない__gc) を使用するコードを追加した場合。 たとえば、ActiveX テンプレート ライブラリ (ATL)、Microsoft Foundation クラス (MFC)、C Run-Time (CRT) クラスなどです。

注:

この記事で説明する問題の影響を受けないプロジェクトで、LNK2001エラーとLNK4210エラーが発生する可能性があります。 ただし、プロジェクトは、LNK2001またはLNK4210警告を解決するとLNK4243警告が発生する場合、またはプロジェクトをリンクするとLNK4243警告が生成される場合、この記事で説明されている問題の影響を確実に受けます。

原因

次のプロジェクトは、ネイティブ ライブラリ (CRT、ATL、MFC など) へのリンケージがなく、グローバル変数や静的データ メンバーを持つネイティブ クラスなしで、ダイナミック リンク ライブラリ (DLL) として既定で作成されます。

  • ASP.NET Web サービス テンプレート
  • クラス ライブラリ テンプレート
  • Windows コントロール ライブラリ テンプレート

静的データ メンバーを持つグローバル変数またはネイティブ クラスを使用するコード (ATL、MFC、CRT ライブラリでグローバル変数を使用するなど) を追加すると、コンパイル時にリンカー エラー メッセージが表示されます。 この場合、静的変数を手動で初期化するコードを追加する必要があります。 これを行う方法の詳細については、この記事の 「解決策 」セクションを参照してください。

便宜上、この記事では、ネイティブ クラスのグローバル変数と静的データ メンバーを この時点から静的 変数または静的変数 として参照します。

この問題は、混合 DLL 読み込み問題が原因で発生します。 混合 DLL (つまり、マネージド コードとネイティブ コードの両方を含む DLL) では、特にシステムがストレスを受けている場合に、プロセス アドレス空間に読み込まれる状況によってはデッドロック シナリオが発生する可能性があります。 前に説明したリンカー エラー メッセージは、ユーザーがデッドロックの可能性と、このドキュメントで説明されている回避策を認識していることを確認するために、リンカーで有効にされました。

解決方法

既定で DLL として作成される C++ プロジェクトのマネージド拡張機能は、C ランタイム (CRT) ライブラリ、ATL、MFC などのネイティブ C/C++ ライブラリにはリンクされず、静的変数は使用されません。 さらに、プロジェクト設定では、 /NOENTRY オプションを有効にして DLL をリンクする必要があることを指定します。

これは、エントリ ポイントとリンクすると、 の間に 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 は DLL エクスポート (__declspec(dllexport)) を使用して入力され、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.
    }
    

    共通言語ランタイム サポート コンパイラ オプションを追加するには、次の手順に従います。

    1. [ プロジェクト] をクリックし、[ ProjectName プロパティ] をクリックします。

      注:

      ProjectName は、プロジェクトの名前のプレースホルダーです。

    2. [ 構成プロパティ] を展開し、[ 全般] をクリックします。

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

    4. [ダイヤル プラン (電話のコンテキスト)] ボックスで、[参照] をクリックして、ユーザーのダイヤル プランを見つけます。

    共通言語ランタイム サポート コンパイラ オプションの詳細については、「 /clr (共通言語ランタイム コンパイル)」を参照してください。

    これらの手順は、記事全体に適用されます。

  2. DLL には複数のコンシューマーを含めることができます。 複数のコンシューマーがある場合は、エクスポート セクションの DLL .def ファイルに次のコードを追加します。

    DllEnsureInitPRIVATE
    DllForceTermPRIVATE
    

    これらの行を追加せず、関数をエクスポートする DLL が 2 つある場合、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 に動的にリンクされている場合は、次のようにコードを挿入します。

    • DLL の最初の LoadLibrary の直後にスニペット 1 を挿入します (手順 3 を参照)。
    • DLL の最後の FreeLibrary の直前にスニペット 2 を挿入します (手順 4 を参照)。

COM ベースの DLL を変更する

次のコードで示すように、DLL エクスポート関数 DllCanUnloadNowDllGetClassObjectDllRegisterServer、および 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();
    }