混在アセンブリの初期化Initialization of Mixed Assemblies

Windows 開発者は、でコードを実行するときに、常にローダーロックに注意する必要があり DllMain ます。Windows developers must always be wary of loader lock when running code during DllMain. ただし、C++/clr 混合モードアセンブリを扱う場合は、いくつかの追加の問題を考慮する必要があります。However, there are some additional issues to consider when dealing with C++/clr mixed-mode assemblies.

DllMain内のコードは、.Net 共通言語ランタイム (CLR) にアクセスできません。Code within DllMain must not access the .NET Common Language Runtime (CLR). つまり、は DllMain 、直接的または間接的にマネージ関数を呼び出すことはありません。マネージコードをで宣言または実装する必要はありません。また、では、 DllMain ガベージコレクションや自動ライブラリ読み込みは実行されません DllMainThat means that DllMain should make no calls to managed functions, directly or indirectly; no managed code should be declared or implemented in DllMain; and no garbage collection or automatic library loading should take place within DllMain.

ローダー ロックの原因Causes of Loader Lock

.NET プラットフォームの導入により、実行モジュール (EXE または DLL) を読み込むための2つの異なるメカニズムがあります。1つは Windows 用で、もう1つはアンマネージモジュールで使用されます。もう1つは、.NET アセンブリを読み込む CLR 用です。With the introduction of the .NET platform, there are two distinct mechanisms for loading an execution module (EXE or DLL): one for Windows, which is used for unmanaged modules, and one for the CLR, which loads .NET assemblies. 混在モード DLL 読み込み時の問題は、Microsoft Windows OS ローダーを中心に発生します。The mixed DLL loading problem centers around the Microsoft Windows OS loader.

.NET のコンストラクトだけを含むアセンブリをプロセスに読み込んだ場合、CLR ローダーは、必要なすべての読み込みタスクおよび初期化タスクを自分で実行できます。When an assembly containing only .NET constructs is loaded into a process, the CLR loader can perform all of the necessary loading and initialization tasks itself. しかし、混在アセンブリの場合は、ネイティブ コードやデータが含まれている可能性があるため、Windows ローダーも使用する必要があります。However, for mixed assemblies, because they can contain native code and data, the Windows loader must be used as well.

Windows ローダーは、初期化される前に、その DLL 内のコードまたはデータにアクセスできないことを保証します。また、コードが部分的に初期化されている間に、コードが冗長に DLL を読み込むことはできません。The Windows loader guarantees that no code can access code or data in that DLL before it has been initialized, and that no code can redundantly load the DLL while it's partially initialized. これを行うために、Windows ローダーは、モジュールの初期化中に安全でないアクセスを防止する、プロセスグローバルのクリティカルセクション (多くの場合 "ローダーロック" と呼ばれます) を使用します。To do it, the Windows loader uses a process-global critical section (often called the "loader lock") that prevents unsafe access during module initialization. 結果として、読み込みプロセスでは、典型的な多くのデッドロックのシナリオが発生しやすくなります。As a result, the loading process is vulnerable to many classic deadlock scenarios. 混在アセンブリの場合、次の 2 つのシナリオで、デッドロックの危険性が高くなります。For mixed assemblies, the following two scenarios increase the risk of deadlock:

  • まず、ローダーロックが保持されているときにユーザーが Microsoft 中間言語 (MSIL) にコンパイルされた関数を実行しようとすると、 DllMain デッドロックが発生する可能性があります。First, if users attempt to execute functions compiled to Microsoft intermediate language (MSIL) when the loader lock is held (from DllMain or in static initializers, for example), it can cause deadlock. MSIL 関数が、読み込まれていないアセンブリ内の型を参照している場合を考えてみます。Consider the case in which the MSIL function references a type in an assembly that hasn't been loaded. CLR は、そのアセンブリを自動的に読み込もうとします。これにより、Windows ローダーはローダー ロックをブロックすることが必要になる場合があります。The CLR will attempt to automatically load that assembly, which may require the Windows loader to block on the loader lock. 呼び出しシーケンスの前にコードによってローダーロックが既に保持されているため、デッドロックが発生します。A deadlock occurs, since the loader lock is already held by code earlier in the call sequence. ただし、ローダーロックの下で MSIL を実行すると、デッドロックが発生することは保証されないため、このシナリオの診断や修正が困難になります。However, executing MSIL under loader lock doesn't guarantee that a deadlock will occur, which makes this scenario difficult to diagnose and fix. 参照される型の DLL にネイティブコンストラクトが含まれておらず、そのすべての依存関係にネイティブコンストラクトが含まれていない場合など、状況によっては、参照された型の .NET アセンブリを読み込むために Windows ローダーは必要ありません。In some circumstances, such as when the DLL of the referenced type contains no native constructs and all of its dependencies contain no native constructs, the Windows loader isn't required to load the .NET assembly of the referenced type. さらに、必要なアセンブリやその混在するネイティブまたは .NET の依存関係は、既に他のコードによって読み込まれている可能性があります。Additionally, the required assembly or its mixed native/.NET dependencies may have already been loaded by other code. その結果、デッドロックの発生を予測することが難しくなります。また、デッドロック状態が、対象となるコンピューターの構成によって異なる場合もあります。Consequently, the deadlocking can be difficult to predict, and can vary depending on the configuration of the target machine.

  • 2つ目は、.NET Framework のバージョン1.0 および1.1 で Dll を読み込むときに、CLR はローダーロックが保持されていないと想定し、ローダーロックの下では無効ないくつかのアクションを実行しました。Second, when loading DLLs in versions 1.0 and 1.1 of the .NET Framework, the CLR assumed that the loader lock was not held, and performed several actions that are invalid under loader lock. ローダーロックが保持されていないことを前提として、純粋な .NET Dll に対しては有効な前提としています。ただし、混在する Dll はネイティブの初期化ルーチンを実行するため、ネイティブの Windows ローダーが必要であり、そのためにローダーロックが必要になります。Assuming that the loader lock isn't held is a valid assumption for purely .NET DLLs, but, because mixed DLLs execute native initialization routines, they require the native Windows loader, and therefore the loader lock. したがって、開発者が DLL の初期化中に MSIL 関数を実行しない場合でも、.NET Framework Version 1.0 および 1.1 では、デッドロックが確定的でない場合にデッドロックが発生する可能性がわずかに残っていました。Consequently, even if the developer was not attempting to execute any MSIL functions during DLL initialization, there was still a small possibility of nondeterministic deadlock with versions 1.0 and 1.1 of the .NET Framework.

混在モード DLL の読み込みプロセスで、このような確定的でない場合の問題はなくなりました。All non-determinism has been removed from the mixed DLL loading process. 次のような変更が行われました。It was accomplished with these changes:

  • CLR が、混在モード DLL の読み込み時に誤った想定を行わなくなりました。The CLR no longer makes false assumptions when loading mixed DLLs.

  • アンマネージド初期化とマネージド初期化が、2 つの独立した段階で実行されます。Unmanaged and managed initialization is performed in two separate and distinct stages. アンマネージ初期化は最初に (DllMain を使用して) 行われ、その後、を通じてマネージ初期化が行われます。NET がサポートする .cctor コンストラクト。Unmanaged initialization takes place first (via DllMain), and managed initialization takes place afterwards, through a .NET-supported .cctor construct. 後者は、 /Zl または /NODEFAULTLIB を使用している場合を除いて、ユーザーが意識する必要はありません。The latter is completely transparent to the user unless /Zl or /NODEFAULTLIB are used. 詳細については、「/NODEFAULTLIB (Ignore Libraries) 」と「 /Zl (Omit Default Library Name) 」を参照してください。See/NODEFAULTLIB (Ignore Libraries) and /Zl (Omit Default Library Name) for more information.

ローダー ロックは依然として発生することがありますが、再現性があり、検出されるようになりました。Loader lock can still occur, but now it occurs reproducibly, and is detected. DllMainに MSIL 命令が含まれている場合、コンパイラは警告コンパイラの警告 (レベル 1) C4747を生成します。If DllMain contains MSIL instructions, the compiler generates warning Compiler Warning (level 1) C4747. さらに、ローダー ロックの状況下で MSIL が実行されようとしている場合、CRT または CLR は検出とレポートを試みます。Furthermore, either the CRT or the CLR will try to detect and report attempts to execute MSIL under loader lock. CRT による検出の結果、実行時の診断として C ランタイム エラー R6033 が発生します。CRT detection results in runtime diagnostic C Run-Time Error R6033.

このドキュメントの残りの部分では、MSIL がローダー ロックの状況で実行できる他のシナリオ、各シナリオでの問題に対する解決策、デバッグ技術について説明します。The remainder of this document describes the remaining scenarios for which MSIL can execute under the loader lock, resolutions for the issue under each of those scenarios, and debugging techniques.

シナリオと回避策Scenarios and Workarounds

ローダー ロックが発生している場合でも、ユーザー コードが MSIL を実行できる状況がいくつかあります。There are several different situations under which user code can execute MSIL under loader lock. 開発者は、そのような状況のそれぞれについて、ユーザー コードの実装が MSIL 命令を実行しないようにする必要があります。The developer must ensure that the user code implementation does not attempt to execute MSIL instructions under each of these circumstances. 以下では、最も一般的な事例で問題を解決する方法を考えつつ、すべての可能性について説明します。The following subsections describe all possibilities with a discussion of how to resolve issues in the most common cases.

DllMainDllMain

DllMain関数は、DLL 用のユーザー定義のエントリポイントです。The DllMain function is a user-defined entry point for a DLL. ユーザーがそれ以外の関数を指定しない限り、プロセスやスレッドを DLL にアタッチするか、プロセスやスレッドを DLL からデタッチするたびに、 DllMain が呼び出されます。Unless the user specifies otherwise, DllMain is invoked every time a process or thread attaches to or detaches from the containing DLL. この呼び出しは、ローダー ロックが保持されているときに行われる可能性もあるため、ユーザー指定の DllMain 関数は MSIL にコンパイルしないでください。Since this invocation can occur while the loader lock is held, no user-supplied DllMain function should be compiled to MSIL. さらに、コール ツリー内で DllMain をルートにしている関数も、MSIL にコンパイルできません。Furthermore, no function in the call tree rooted at DllMain can be compiled to MSIL. この問題を解決するには、 DllMain が定義されているコード ブロックを、#pragma unmanagedで修飾する必要があります。To resolve issues here, the code block that defines DllMain should be modified with #pragma unmanaged. DllMain によって呼び出されるすべての関数にも、同じ処理を行う必要があります。The same should be done for every function that DllMain calls.

このような関数で、他の呼び出しコンテキスト用に MSIL 実装を必要とする関数を呼び出す必要がある場合には、同じ関数の .NET バージョンとネイティブ バージョンを重複して作成する方法を使用できます。In cases where these functions must call a function that requires an MSIL implementation for other calling contexts, a duplication strategy can be used where both a .NET and a native version of the same function are created.

また、 DllMain が不要な場合またはローダー ロックの状況でこれを実行する必要がない場合は、ユーザー指定の DllMain の実装を削除できます。そうすることで、問題が発生しなくなります。Alternatively, if DllMain is not required or if it does not need to be executed under loader lock, the user-provided DllMain implementation can be removed, which will eliminate the problem.

DllMain で直接 MSIL を実行しようとすると、 Compiler Warning (level 1) C4747 が発生します。If DllMain attempts to execute MSIL directly, Compiler Warning (level 1) C4747 will result. ただし、コンパイラは、DllMain が別のモジュール内の関数を呼び出し、その関数が MSIL を実行するような事例は検出できません。However, the compiler cannot detect cases where DllMain calls a function in another module that in turn attempts to execute MSIL.

このシナリオの詳細については、「診断に対する障害」を参照してください。For more information on this scenario, see Impediments to Diagnosis.

静的オブジェクトの初期化Initializing Static Objects

静的オブジェクトを初期化すると、動的初期化子が必要な場合にデッドロックが発生することがあります。Initializing static objects can result in deadlock if a dynamic initializer is required. コンパイル時に既知の値に静的変数が割り当てられている場合など、単純なケースでは、動的な初期化は必要ないため、デッドロックのリスクはありません。For simple cases, such as when a static variable is assigned to a value known at compile time, no dynamic initialization is required, so there is no risk of deadlock. ただし、関数呼び出し、コンストラクター呼び出し、またはコンパイル時に評価できない式によって初期化される静的変数は、モジュールの初期化中にコードを実行する必要があります。However, static variables initialized by function calls, constructor invocations, or expressions that cannot be evaluated at compile time all require code to execute during module initialization.

動的な初期化を必要とする静的初期化子の例 (関数呼び出し、オブジェクト構築、およびポインター初期化) を次のコードに示します。The code below shows examples of static initializers that require dynamic initialization: a function call, object construction, and a pointer initialization. (これらの例は静的ではありませんが、グローバルスコープで定義されていると見なされますが、これは同じ効果があります)。(These examples aren't static, but are assumed as defined in the global scope, which has the same effect.)

// dynamic initializer function generated
int a = init();
CObject o(arg1, arg2);
CObject* op = new CObject(arg1, arg2);

このデッドロックの危険性は、該当モジュールが /clr を指定してコンパイルされているかどうかと MSIL が実行されるかどうかによって決まります。This risk of deadlock depends on whether the containing module is compiled with /clr and whether MSIL will be executed. 具体的には、静的変数を /clr を指定せずにコンパイルする場合 (または静的変数が #pragma unmanaged ブロックに存在する場合) に、その変数の初期化に必要な動的初期化子によって MSIL 命令が実行されると、デッドロックが発生することがあります。Specifically, if the static variable is compiled without /clr (or resides in a #pragma unmanaged block), and the dynamic initializer required to initialize it results in the execution of MSIL instructions, deadlock may occur. これは、 /clrを使用せずにコンパイルされたモジュールの場合、DllMain によって静的変数の初期化が実行されるためです。It's because, for modules compiled without /clr, the initialization of static variables is performed by DllMain. これに対し、 /clrでコンパイルされた静的変数は、 .cctor アンマネージ初期化段階が完了し、ローダーロックが解除された後に、によって初期化されます。In contrast, static variables compiled with /clr are initialized by the .cctor, after the unmanaged initialization stage has completed and the loader lock has been released.

静的変数の動的な初期化が原因で発生するデッドロックに対する解決策は多数あります (問題解決にかかる時間が短い順に記載しています)。There are a number of solutions to deadlock caused by the dynamic initialization of static variables (arranged roughly in order of time required to fix the problem):

  • 静的変数を含むソース ファイルは、 /clrを指定してコンパイルします。The source file containing the static variable can be compiled with /clr.

  • 静的変数によって呼び出されるすべての関数は、#pragma unmanaged ディレクティブを使用してネイティブ コードにコンパイルします。All functions called by the static variable can be compiled to native code using the #pragma unmanaged directive.

  • 静的変数が依存するコードを手動で複製して、.NET バージョンとネイティブ バージョンを作成し、それぞれに異なる名前を指定します。Manually clone the code that the static variable depends upon, providing both a .NET and a native version with different names. その後、開発者は、ネイティブな静的初期化子からネイティブ バージョンを呼び出し、それ以外の場所から .NET バージョンを呼び出します。Developers can then call the native version from native static initializers and call the .NET version elsewhere.

起動に影響を与える、ユーザーが指定した関数User-Supplied Functions Affecting Startup

起動時の初期化でライブラリが依存する、ユーザー指定の関数がいくつかあります。There are several user-supplied functions on which libraries depend for initialization during startup. たとえば、演算子や演算子など、C++ の演算子をグローバルにオーバーロードする場合、 new delete C++ 標準ライブラリの初期化と破棄など、すべての場所でユーザー指定のバージョンが使用されます。For example, when globally overloading operators in C++ such as the new and delete operators, the user-provided versions are used everywhere, including in C++ Standard Library initialization and destruction. その結果、C++ 標準ライブラリとユーザー指定の静的初期化子は、これらの演算子のユーザー指定バージョンを呼び出します。As a result, C++ Standard Library and user-provided static initializers will invoke any user-provided versions of these operators.

ユーザー指定バージョンが MSIL にコンパイルされると、これらの初期化子は、ローダー ロックが保持されているときに MSIL 命令を実行しようとします。If the user-provided versions are compiled to MSIL, then these initializers will be attempting to execute MSIL instructions while the loader lock is held. ユーザーが指定した場合 malloc も、同じ結果になります。A user-supplied malloc has the same consequences. この問題を解決するには、これらのオーバーロードまたはユーザー指定定義を、#pragma unmanaged ディレクティブを使用してネイティブ コードとして実装する必要があります。To resolve this problem, any of these overloads or user-supplied definitions must be implemented as native code using the #pragma unmanaged directive.

このシナリオの詳細については、「診断に対する障害」を参照してください。For more information on this scenario, see Impediments to Diagnosis.

カスタム ロケールCustom Locales

ユーザーがカスタムグローバルロケールを提供する場合、このロケールは、静的に初期化されたストリームを含む、今後のすべての i/o ストリームを初期化するために使用されます。If the user provides a custom global locale, this locale will be used for initializing all future I/O streams, including streams that are statically initialized. このグローバルなロケール オブジェクトを MSIL にコンパイルすると、MSIL にコンパイルされたロケール オブジェクト メンバー関数が、ローダー ロックが保持されているときに呼び出されることがあります。If this global locale object is compiled to MSIL, then locale-object member functions compiled to MSIL may be invoked while the loader lock is held.

この問題を解決するためのオプションが 3 つあります。There are three options for solving this problem:

グローバル入出力ストリームの定義をすべて含むソース ファイルを、 /clr オプションを使用してコンパイルします。The source files containing all global I/O stream definitions can be compiled using the /clr option. これにより、ローダーロックの下で静的初期化子が実行されるのを防ぐことができます。It prevents their static initializers from being executed under loader lock.

カスタム ロケールの関数定義は、#pragma unmanaged ディレクティブを使用することによって、ネイティブ コードにコンパイルできます。The custom locale function definitions can be compiled to native code by using the #pragma unmanaged directive.

ローダー ロックが解除されるまで、カスタム ロケールをグローバル ロケールとして設定しないようにします。Refrain from setting the custom locale as the global locale until after the loader lock is released. その後、初期化中に作成された入出力ストリームをカスタム ロケールで明示的に構成します。Then explicitly configure I/O streams created during initialization with the custom locale.

診断に対する障害Impediments to Diagnosis

場合によっては、デッドロックの原因を検出することが困難です。In some cases, it's difficult to detect the source of deadlocks. 以下では、そのようなシナリオとそれらの問題の解決策について説明します。The following subsections discuss these scenarios and ways to work around these issues.

ヘッダーでの実装Implementation in Headers

特殊なケースで、ヘッダー ファイル内に関数を実装すると、診断が困難になる場合があります。In select cases, function implementations inside header files can complicate diagnosis. インライン関数とテンプレート コードの両方で、その関数をヘッダー ファイルに指定する必要があります。Inline functions and template code both require that functions be specified in a header file. C++ 言語では、単一定義規則を指定します。単一定義規則を指定すると、同じ名前で実装されているすべての関数が、強制的に同じ意味にされます。The C++ language specifies the One Definition Rule, which forces all implementations of functions with the same name to be semantically equivalent. その結果、C++ リンカーでは、特定の関数を重複して実装しているオブジェクト ファイルをマージする際に特別に注意する必要がなくなります。Consequently, the C++ linker need not make any special considerations when merging object files that have duplicate implementations of a given function.

Visual Studio 2005 より前では、リンカーは単にこれらの意味的に等しい定義の中から最大のものを選択し、事前宣言に対応しています。また、さまざまなソースファイルに対して異なる最適化オプションが使用されているシナリオもあります。Prior to Visual Studio 2005, the linker simply chooses the largest of these semantically equivalent definitions, to accommodate forward declarations, and scenarios when different optimization options are used for different source files. ネイティブ/.NET Dll が混在している場合に問題が発生します。It creates a problem for mixed native/.NET DLLs.

/Clrが有効になっていて無効になっている C++ ファイルと同じヘッダーが含まれている可能性があるため、または #include をブロック内にラップできるため #pragma unmanaged 、ヘッダーに実装を提供する MSIL とネイティブの両方のバージョンの関数を使用することができます。Because the same header may be included both by C++ files with /clr enabled and disabled, or a #include can be wrapped inside a #pragma unmanaged block, it is possible to have both MSIL and native versions of functions that provide implementations in headers. MSIL の実装とネイティブの実装は、ローダー ロックの状態での初期化については異なる意味を持ちます。これは、事実上、単一定義規則に違反しています。MSIL and native implementations have different semantics with respect to initialization under the loader lock, which effectively violates the one definition rule. その結果、リンカーが最大の実装を選択すると、他の場所で #pragma unmanaged ディレクティブを使用して明示的にネイティブ コードにコンパイルした場合でも、関数の MSIL バージョンが選択されます。Consequently, when the linker chooses the largest implementation, it may choose the MSIL version of a function, even if it was explicitly compiled to native code elsewhere using the #pragma unmanaged directive. テンプレートまたはインライン関数の MSIL バージョンがローダーロックの下で呼び出されないようにするには、ローダーロックの下で呼び出されるすべての関数のすべての定義を、ディレクティブを使用して変更する必要があり #pragma unmanaged ます。To ensure that an MSIL version of a template or inline function is never called under loader lock, every definition of every such function called under loader lock must be modified with the #pragma unmanaged directive. ヘッダーファイルがサードパーティからのものである場合、この変更を行う最も簡単な方法は、問題のある #pragma unmanaged ヘッダーファイルの #include ディレクティブの周囲にディレクティブをプッシュしてポップすることです。If the header file is from a third party, the easiest way to make this change is to push and pop the #pragma unmanaged directive around the #include directive for the offending header file. (例については、「マネージ、アンマネージ」を参照してください)。ただし、この方法は、.NET Api を直接呼び出す必要がある他のコードが含まれているヘッダーに対しては機能しません。(See managed, unmanaged for an example.) However, this strategy will not work for headers that contain other code that must directly call .NET APIs.

ローダー ロックを扱うユーザーの負担を減らすため、マネージドとネイティブの両方の実装が存在する場合、リンカーはネイティブの実装を選択するようになっています。As a convenience for users dealing with loader lock, the linker will choose the native implementation over the managed when presented with both. この既定では、上記の問題は回避されます。This default avoids the above issues. ただし、このリリースではコンパイラに未解決の問題が 2 つ残っているため、この規則には、次の 2 つの例外があります。However, there are two exceptions to this rule in this release due to two unresolved issues with the compiler:

  • インライン関数の呼び出しは、グローバルな静的関数ポインターを介して行われます。The call to an inline function is through a global static function pointer. このシナリオは、仮想関数がグローバル関数ポインターを介して呼び出されるため、注目してください。This scenario is notable because virtual functions are called through global function pointers. たとえば、次のように入力します。For example,
#include "definesmyObject.h"
#include "definesclassC.h"

typedef void (*function_pointer_t)();

function_pointer_t myObject_p = &myObject;

#pragma unmanaged
void DuringLoaderlock(C & c)
{
    // Either of these calls could resolve to a managed implementation,
    // at link-time, even if a native implementation also exists.
    c.VirtualMember();
    myObject_p();
}

デバッグ モードでの診断Diagnosing in Debug Mode

ローダー ロックに関する問題の診断はすべて、デバッグ ビルドで行う必要があります。All diagnoses of loader lock problems should be done with Debug builds. リリース ビルドは診断を生成しない場合があります。また、リリース モードで実行される最適化によって、ローダー ロックのシナリオで MSIL の一部が隠されることがあります。Release builds may not produce diagnostics, and the optimizations performed in Release mode may mask some of the MSIL under loader lock scenarios.

ローダーロックに関する問題をデバッグする方法How to debug loader lock issues

MSIL 関数が呼び出されたときに、CLR が生成する診断は、CLR の実行を中断します。The diagnostic that the CLR generates when an MSIL function is invoked causes the CLR to suspend execution. その結果、デバッグ対象をインプロセスで実行するときに、Visual C++ 混合モードのデバッガーも中断されます。In turn, that causes the Visual C++ mixed-mode debugger to be suspended as well when running the debuggee in-process. ただし、プロセスにアタッチするときに、混合デバッガーを使用してデバッグ対象のマネージコールバックを取得することはできません。However, when attaching to the process, it isn't possible to obtain a managed callstack for the debuggee using the mixed debugger.

ローダー ロック中に呼び出された特定の MSIL 関数を識別するには、開発者が次の手順を実行する必要があります。To identify the specific MSIL function that was called under loader lock, developers should complete the following steps:

  1. mscoree.dll と mscorwks.dll のシンボルを使用できるようにします。Ensure that symbols for mscoree.dll and mscorwks.dll are available.

    シンボルを使用できるようにするには、次の2つの方法があります。You can make the symbols available in two ways. 1 つ目の方法として、mscoree.dll と mscorwks.dll の PDB をシンボル検索パスに追加します。First, the PDBs for mscoree.dll and mscorwks.dll can be added to the symbol search path. シンボルを追加するには、[シンボル検索パスのオプション] ダイアログを開きます。To add them, open the symbol search path options dialog. ([ツール] メニューの [オプション] をクリックします。(From the Tools menu, choose Options. [オプション] ダイアログボックスの左ペインで、[デバッグ] ノードを開き、[シンボル] を選択します。mscoree.dll のパスと mscorwks.dll PDB ファイルを検索リストに追加します。In the left pane of the Options dialog box, open the Debugging node and choose Symbols.) Add the path to the mscoree.dll and mscorwks.dll PDB files to the search list. これらの PDB は、%VSINSTALLDIR%\SDK\v2.0\symbols にインストールされます。These PDBs are installed to the %VSINSTALLDIR%\SDK\v2.0\symbols. [OK] をクリックします。Choose OK.

    2 つ目の方法として、mscoree.dll と mscorwks.dll の PDB を Microsoft Symbol Server からダウンロードします。Second, the PDBs for mscoree.dll and mscorwks.dll can be downloaded from the Microsoft Symbol Server. Symbol Server を構成するには、シンボル検索パスのオプションのダイアログ ボックスを開きます。To configure Symbol Server, open the symbol search path options dialog. ([ツール] メニューの [オプション] をクリックします。(From the Tools menu, choose Options. [オプション] ダイアログボックスの左ペインで、[デバッグ] ノードを開き、[シンボル] を選択します。検索パスを検索リストに追加し https://msdl.microsoft.com/download/symbols ます。In the left pane of the Options dialog box, open the Debugging node and choose Symbols.) Add this search path to the search list: https://msdl.microsoft.com/download/symbols. シンボルのキャッシュ ディレクトリをシンボル サーバーのキャッシュのテキスト ボックスに追加します。Add a symbol cache directory to the symbol server cache text box. [OK] をクリックします。Choose OK.

  2. デバッガーのモードをネイティブのみに設定します。Set debugger mode to native-only mode.

    ソリューションのスタートアッププロジェクトのプロパティグリッドを開きます。Open the Properties grid for the startup project in the solution. [構成プロパティ] > [デバッグ] を選択します。Select Configuration Properties > Debugging. デバッガーの種類ネイティブのみに設定します。Set the Debugger Type to Native-Only.

  3. デバッガーを起動します (F5 キー)。Start the debugger (F5).

  4. /Clr診断が生成されたら、[再試行] を選択し、[中断] を選択します。When the /clr diagnostic is generated, choose Retry and then choose Break.

  5. [呼び出し履歴] ウィンドウを開きます。Open the call stack window. (メニューバーで [デバッグ > ] を選択します。Windows > 呼び出し履歴)問題の DllMain あるまたは静的な初期化子は、緑色の矢印で示されます。(On the menu bar, choose Debug > Windows > Call Stack.) The offending DllMain or static initializer is identified with a green arrow. 問題のある関数が示されない場合は、以下の手順を実行して検索する必要があります。If the offending function is not identified, the following steps must be taken to find it.

  6. [イミディエイト] ウィンドウを開きます (メニューバーで [Windows イミディエイトのデバッグ] を選択 > Windows > Immediateします)。Open the Immediate window (On the menu bar, choose Debug > Windows > Immediate.)

  7. .load sos.dll[イミディエイト] ウィンドウに「」と入力して、SOS デバッグサービスを読み込みます。Enter .load sos.dll into the Immediate window to load the SOS debugging service.

  8. !dumpstack[イミディエイト] ウィンドウに「」と入力して、内部 /clrスタックの完全な一覧を取得します。Enter !dumpstack into the Immediate window to obtain a complete listing of the internal /clr stack.

  9. _CorDllMain ( DllMain 問題が発生した場合) または _VTableBootstrapThunkInitHelperStub または GetTargetForVTableEntry (静的初期化子によって問題が発生した場合) のいずれかの最初のインスタンス (スタックの一番下にあるもの) を探します。Look for the first instance (closest to the bottom of the stack) of either _CorDllMain (if DllMain causes the issue) or _VTableBootstrapThunkInitHelperStub or GetTargetForVTableEntry (if a static initializer causes the issue). この呼び出しのすぐ下にあるスタック エントリが、ローダー ロック中に実行しようとした、MSIL 実装の関数の呼び出しです。The stack entry just below this call is the invocation of the MSIL implemented function that attempted to execute under loader lock.

  10. 前の手順で特定したソースファイルと行番号にアクセスし、「シナリオ」セクションで説明されているシナリオと解決策を使用して問題を修正します。Go to the source file and line number identified in the previous step and correct the problem using the scenarios and solutions described in the Scenarios section.

Example

説明Description

次の例は、からグローバルオブジェクトのコンストラクターにコードを移動して、ローダーロックを回避する方法を示して DllMain います。The following sample shows how to avoid loader lock by moving code from DllMain into the constructor of a global object.

このサンプルでは、最初はマネージオブジェクトを含むコンストラクターを持つグローバルマネージオブジェクトがあり DllMain ます。In this sample, there is a global managed object whose constructor contains the managed object that was originally in DllMain. このサンプルの2番目の部分では、アセンブリを参照し、マネージオブジェクトのインスタンスを作成して、初期化を実行するモジュールコンストラクターを呼び出します。The second part of this sample references the assembly, creating an instance of the managed object to invoke the module constructor that does the initialization.

コードCode

// initializing_mixed_assemblies.cpp
// compile with: /clr /LD
#pragma once
#include <stdio.h>
#include <windows.h>
struct __declspec(dllexport) A {
   A() {
      System::Console::WriteLine("Module ctor initializing based on global instance of class.\n");
   }

   void Test() {
      printf_s("Test called so linker does not throw away unused object.\n");
   }
};

#pragma unmanaged
// Global instance of object
A obj;

extern "C"
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) {
   // Remove all managed code from here and put it in constructor of A.
   return true;
}

この例では、混在アセンブリの初期化に関する問題を示します。This example demonstrates issues in initialization of mixed assemblies:

// initializing_mixed_assemblies_2.cpp
// compile with: /clr initializing_mixed_assemblies.lib
#include <windows.h>
using namespace System;
#include <stdio.h>
#using "initializing_mixed_assemblies.dll"
struct __declspec(dllimport) A {
   void Test();
};

int main() {
   A obj;
   obj.Test();
}

このコードを実行すると、次の出力が生成されます。This code produces the following output:

Module ctor initializing based on global instance of class.

Test called so linker does not throw away unused object.

関連項目See also

混合 (ネイティブおよびマネージ) アセンブリMixed (Native and Managed) Assemblies