方法: /clr に移行する
このトピックでは、ネイティブ コードを /clr でコンパイルしたときに発生する問題について説明します (詳細については、「/clr (共通言語ランタイムのコンパイル)」を参照してください)。/clr では、アンマネージ モジュールとの互換性を維持しながら、Visual C++ モジュールから .NET アセンブリを呼び出したり .NET アセンブリから Visual C++ モジュールを呼び出したりできます。/clr でコンパイルする利点の詳細については、「混在 (ネイティブおよびマネージ) アセンブリ」および「ネイティブと .NET の相互運用性」を参照してください。
/clr でライブラリ プロジェクトをコンパイルする場合の既知の問題
Visual Studio では、ライブラリ プロジェクトを /clr でコンパイルすると次のような既知の問題が発生します。
コードで CRuntimeClass::FromName を使用することにより、実行時に型を照会できます。ただし、(/clr を指定してコンパイルされた) MSIL .dll 内の型の場合、静的コンストラクターがマネージ .dll で実行される前に CRuntimeClass::FromName の呼び出しが行われると、呼び出しに失敗することがあります (この問題は、コードがマネージ .dll で実行された後に FromName 呼び出しが行われた場合は発生しません)。この問題を回避するには、マネージ静的コンストラクターを強制的に構築します。それには、マネージ .dll で関数を定義してエクスポートし、その関数をネイティブ MFC アプリケーションから呼び出します。次に例を示します。
// Extension DLL Header file: __declspec( dllexport ) void EnsureManagedInitialization () { // managed code that won't be optimized away System::GC::KeepAlive(System::Int32::MaxValue); }
Visual C++ でのコンパイル
プロジェクトのモジュールに対して /clr を使用する前に、まずネイティブ プロジェクトを Visual Studio 2010 でコンパイルおよびリンクしてください。
以下の手順に従って順番に操作すると、/clr コンパイルを簡単に実行できます。これらの手順では、手順ごとにプロジェクトをコンパイルして実行することが重要です。
Visual C++ 2003 以前のバージョン
Visual C++ 2003 以前のバージョンから Visual Studio 2010 にアップグレードする場合、Visual C++ 2003 の C++ 標準に対する準拠が強化されたことに関連するコンパイル エラーが発生することがあります。
Visual C++ 2003 からのアップグレード
以前に Visual C++ 2003 で作成されたプロジェクトも、まず /clr を使用しないでコンパイルする必要があります。これは、現在の Visual Studio では ANSI/ISO 準拠が強化され、いくつか大きな変更があるためです。最も注意が必要な変更は、CRT のセキュリティ機能 です。CRT を使用するコードでは、廃止警告が生成される可能性があります。これらの警告は抑制できますが、新しい CRT 関数のセキュリティが強化されたバージョン の方がセキュリティに優れ、コード内のセキュリティ上の問題が明らかになる場合もあるので、新しいバージョンへの移行をお勧めします。
C++ マネージ拡張からのアップグレード
C++ のマネージ拡張を使用した Visual C++ .NET または Visual C++ 2003 で作成されたプロジェクトは、マネージ拡張が廃止されたため、最低 1 つのプロジェクト設定を変更する必要があります。その結果、C++ マネージ拡張で作成されたコードは /clr ではコンパイルされません。代わりに、/clr:oldSyntax を使用してください。
C コードから C++ への変換
Visual Studio では C ファイルをコンパイルできますが、/clr でコンパイルするには、これらのファイルを C++ に変換する必要があります。実際のファイル名を変更する必要はなく、/Tp を使用できます (「/Tc、/Tp、/TC、/TP (ソース ファイル タイプの指定)」を参照)。/clr は C++ ソース コード ファイルを必要としますが、オブジェクト指向パラダイムを使用するようにコードを再適用する必要はありません。
C コードは、C++ ファイルとしてコンパイルするときは変更を必要とする可能性があります。C++ の型保証の規則は厳密なので、型の変換はキャストで明示的に行う必要があります。たとえば、malloc は void ポインターを返しますが、キャストによる C では任意の型のポインターに割り当てることができます。
int* a = malloc(sizeof(int)); // C code
int* b = (int*)malloc(sizeof(int)); // C++ equivalent
また C++ では関数ポインターの型も厳密に保証されているので、次のような C コードは変更が必要です。C++ では、関数ポインターの型を定義する typedef を作成し、その後その型を使用して関数ポインターをキャストすることをお勧めします。
NewFunc1 = GetProcAddress( hLib, "Func1" ); // C code
typedef int(*MYPROC)(int); // C++ equivalent
NewFunc2 = (MYPROC)GetProcAddress( hLib, "Func2" );
また C++ では、関数を参照したり呼び出すには、その前にプロトタイプを作成するか完全に定義する必要があります。
C コードの識別子で、C++ のキーワードとなっているもの (virtual、new、delete、bool、true、false など) は名前を変更する必要があります。これは一般的に、単純な検索置換操作だけで行うことができます。
最後に、C 形式の COM 呼び出しでは、v-table および this ポインターの明示的な使用が必要でしたが、C++ では必要ありません。
COMObj1->lpVtbl->Method(COMObj, args); // C code
COMObj2->Method(args); // C++ equivalent
プロジェクト設定の再構成
プロジェクトを Visual Studio 2010 でコンパイルして実行した後は、既定の構成を変更するのではなく、/clr 対応の新しいプロジェクト構成を作成する必要があります。/clr と互換性のないコンパイラ オプションがあるため、別の構成を作成することによってネイティブ型またはマネージ型としてプロジェクトをビルドできます。プロパティ ページのダイアログ ボックスで /clr が選択されると、/clr と互換性のあるプロジェクト設定は無効になります (無効になったオプションは、/clr がその後選択が解除された場合、自動的に復元されます)。
新しいプロジェクト構成の作成
[新規プロジェクト構成] ダイアログ ボックスの [設定のコピー元] オプションを使用して、既存のプロジェクト設定に基づくプロジェクト構成を作成できます。これを、デバッグ構成に対して 1 回、リリース構成に対して 1 回実行してください。この後の変更は /clr 固有の構成だけに適用され、元のプロジェクト設定をそのまま保持できます。
カスタム ビルド規則を使用するプロジェクトには、特別な注意が必要な場合があります。
この手順は、メイクファイルを使用するプロジェクトにとって別の意味があります。この場合、別のビルド ターゲットを構成するか、または元のコピーから /clr コンパイル固有のバージョンを作成できます。
プロジェクトの設定の変更
開発環境で /clr を選択するには、/clr (共通言語ランタイムのコンパイル) の指示に従ってください。既に説明したように、この手順によって競合するプロジェクト設定は自動的に無効になります。
[!メモ]
マネージ ライブラリまたは Web サービス プロジェクトを Visual C++ 2003 からアップグレードすると、/Zl コンパイラ オプションが [コマンド ライン] プロパティ ページに追加されます。これによって LNK2001 が発生します。解決するには、/Zl を [コマンド ライン] プロパティ ページから削除します。詳細については、「/Zl (既定のライブラリ名の省略)」および「方法 : プロジェクト プロパティ ページを開く」を参照してください。または、msvcrt.lib および msvcmrt.lib をリンカーの [Additional Dependencies] プロパティに追加します。
メイクファイルで生成されたプロジェクトの場合、互換性のないコンパイラ オプションは /clr 追加後に手動で無効にする必要があります。/clr と互換性のあるコンパイラ オプションの詳細については、「/clr の制約」を参照してください。
プリコンパイル済みヘッダー
/clr ではプリコンパイル済みヘッダーがサポートされています。しかし、CPP ファイルの一部だけを /clr でコンパイル (残りのファイルはネイティブとしてコンパイル) する場合、/clr で生成されたプリコンパイル済みヘッダーには /clr なしで生成されたプリコンパイル済みヘッダーとの互換性がないため、多少の変更が必要になります。この非互換性の原因は、/clr がメタデータを生成することとメタデータを必要とすることにあります。/clr でコンパイルしたモジュールは、したがって、メタデータを含まないプリコンパイル済みヘッダーを使用せず、/clr でコンパイルしなかったモジュールはメタデータを含むプリコンパイル済みヘッダー ファイルを使用できません。
モジュールの一部が /clr でコンパイルされたプロジェクトをコンパイルする最も簡単な方法は、プリコンパイル済みのヘッダー全体を無効にすることです(プロジェクトの [プロパティ ページ] ダイアログで C/C++ ノードを開き、[プリコンパイル済みヘッダー] を選択します。次に、[プリコンパイル済みヘッダーの作成/使用] プロパティを 「プリコンパイル済みヘッダーを使用しない」 に変更します)。
しかし、特に大きなプロジェクトの場合、プリコンパイル済みヘッダーを使用するとコンパイルの処理速度がかなり短縮されるので、この機能を無効化することはお勧めしません。この場合は、/clr ファイルと非 /clr ファイルで別のプリコンパイル済みヘッダーを使用するように設定してください。この操作を行うには、ソリューション エクスプローラーを使用して /clr でコンパイルするモジュールを複数選択し、グループを右クリックしてからプロパティを選択します。次に、[ファイルを使用して PCH を作成/使用] プロパティと [プリコンパイル済みヘッダー ファイル] プロパティの両方を変更し、それぞれ異なるヘッダー ファイル名と PCH ファイルを使用するように設定します。
エラーの修正
/clr でコンパイルすると、コンパイラ、リンカー、またはランタイム エラーになることがあります。このセクションでは、最も一般的な問題について説明します。
メタデータのマージ
データ型のバージョンが異なる場合、2 つの型に対して生成されるメタデータが一致しないため、リンカーのエラーが発生することがあります(通常、型のメンバーが条件付きで定義されているのに、その型を使用するすべての CPP ファイルの条件が同じでない場合に発生します)。このような場合、リンカーのエラーが発生し、シンボル名と、型が定義されている 2 番目の OBJ ファイルの名前だけが報告されます。多くの場合、OBJ ファイルがリンカーに送られる順序を変更し、他のデータ型バージョンの位置がリンカーにわかるようにすると対処できます。
ローダー ロックのデッドロック
Visual C++ .NET および Visual C++ 2003 では、/clr での初期化は非確定的なデッドロックを発生する可能性がありました。この問題は "ローダー ロックのデッドロック" と呼ばれます。Visual Studio 2010 では、このデッドロックが簡単に回避できるようになり、ランタイムに検出され報告されるため、非確定的ではなくなりました。ローダー ロックの問題が発生する可能性は依然としてありますが、回避および修正はかなり容易です。背景、ガイダンス、および解決方法の詳細については、「混在アセンブリの初期化」を参照してください。
データのエクスポート
DLL データのエクスポートはエラーの原因にもなりやすいため、お勧めしません。これは、DLL のデータ セクションは、DLL のマネージ部分の一部が実行されるまで初期化されていると保証されないためです。「#using ディレクティブ (C++)」でメタデータを参照してください。
型の可視性
ネイティブ型は既定でプライベートになりました。Visual C++ .NET 2002 および Visual C++ 2003 では、ネイティブ型は既定ではパブリックでした。これで、ネイティブ型は DLL の外側では見えなくなります。このエラーを解決するには、これらの型に public を追加します。詳細については、「型およびメンバーの可視性」を参照してください。
浮動小数点と配置の問題
共通言語ランタイムでは __controlfp はサポートされていません (詳細については「_control87、_controlfp、__control87_2」を参照)。また CLR は align (C++) を守りません。
COM の初期化
共通言語ランタイムは、モジュールが初期化されると自動的に COM を初期化します (自動的に初期化される場合、COM は MTA として初期化されます)。その結果、明示的に COM を初期化すると、COM が既に初期化されていることを示すリターン コードが生成されます。COM が CLR によって既にいずれかのスレッド モデルに初期化されている場合、別のスレッド モデルを使用して明示的に COM を初期化しようとすると、アプリケーションが失敗するおそれがあります。
共通言語ランタイムの既定では、COM は MTA として起動されます。これを変更するには /CLRTHREADATTRIBUTE (CLR スレッド属性の設定) を使用します。
パフォーマンスの問題
MSIL に対して生成されたネイティブの C++ メソッドが (仮想関数呼び出し、または関数ポインターの使用によって) 間接的に呼び出されると、パフォーマンスが低下することがあります。この問題の詳細については、「ダブル サンキング (C++)」を参照してください。
ネイティブから MSIL に移動すると、ワーキング セットのサイズが増加することがわかります。これは、共通言語ランタイムが、プログラムを正しく実行するための多くの機能を提供するためです。/clr アプリケーションが正しく実行されない場合は、C4793 (既定ではオフ) を有効化できます。詳細については、「コンパイラの警告 (レベル 1 および 3) C4793」を参照してください。
シャットダウン時のプログラムの衝突
場合によっては、マネージ コードが実行を終了する前に CLR がシャットダウンすることがあります。std::set_terminate と SIGTERM を使用すると、この問題が発生します。詳細については、「signal 定数」および「set_terminate (<exception>)」を参照してください。
Visual C++ の新機能の使用
アプリケーションのコンパイル、リンク、および実行が終了すると、/clr でコンパイルされたあらゆるモジュールで .NET 機能を使用できます。詳細については、「ランタイム プラットフォームのコンポーネントの拡張機能」を参照してください。
C++ のマネージ拡張を使用していた場合は、新しい構文を使用してコードを変換できます。構文上の相違の一覧については、「Managed Extensions for C++ Syntax Upgrade Checklist」を参照してください。C++ マネージ拡張の変換の詳細については、「C++/CLI 移行ガイド」を参照してください。
Visual C++ での .NET プログラミングの詳細については、以下の項目を参照してください。