VC.NET ~ プロジェクト管理の落とし穴

もくじ

  • マネージとアンマネージの混在
  • バッチビルドのバグ
  • dbgheapのassertionエラー
  • LNK2022エラー
  • スタートアッププロジェクトの変更

マネージとアンマネージの混在

.NET開発の言語にC++/CLIを選択する理由の1つに、アンマネージなプロジェクトと組み合わせやすい、という点が挙げられると思います。C++ Interopという仕組みのおかげで、明示的にPInvokeを使う必要がありません(PInvokeとC++ Interopについては別記事参照)し、スタティックリンクライブラリをリンクすることもできます。

マネージなプロジェクトからアンマネージなコード(dllやlibの関数)を呼び出す場合の注意点を挙げておきます。

共通言語ランタイムサポート(/clrオプション)

Visual Studioでマネージなプロジェクトを作ると、デフォルトで/clr:pureオプションが設定されます。このプロジェクトから、自作のアンマネージなスタティックリンクライブラリの関数を呼びたい場合は、このオプションを/clrに変える必要があります。pureでもsafeでもなく、ただの/clrに設定します。

  • プロジェクトの[プロパティ]→[構成プロパティ]→[全般]→[共通言語ランタイムサポート]

Win32を呼ぶ

マネージなプロジェクトからWin32の関数を呼びたい場合は、これまで通り、windows.hを#includeして好きな関数を呼ぶだけです。というのはウソで、関数によっては、もうひと手間(ふた手間?)かかります。

まず、関数によってはリンクエラーが出るかもしれません。例えば、GetSysColor()とか。

DWORD col = GetSysColor(COLOR_ACTIVECAPTION);    // error LNK2028

これは、リンカの入力がデフォルトで $(NOINHERIT) に設定されているためです。

  • プロジェクトの[プロパティ]→[構成プロパティ]→[リンカ]→[入力]→[追加の依存ファイル]

ここに、リンクするライブラリ(上記の例ならuser32.lib)を明示してやりましょう。あるいは、「…」をクリックして[追加の依存ファイル]ダイアログを表示し、[親またはプロジェクトの既定値から継承]をチェックしても良いでしょう([継承の値]欄にuser32.libが入っている必要があります)。

また、関数によっては引数やreturn値のマーシャリングが必要です。マーシャリングについては別記事を参照して下さい。

アンマネージ側の文字セット

マネージとアンマネージの間で文字列を共有する場合、マーシャリングが必要です。マーシャリングの詳細は別記事を参照して頂くとして、ここでは、文字セットに関する注意点を書いておきます。

アンマネージ側の文字表現には、マルチバイト文字とワイド文字の2種類があります。つまり、文字をcharで表現するかwchar_tで表現するか、という話しです。どちらを採用しているかによって、マーシャリング方法を変える必要があります。

String^ s = "漢字";
IntPtr ipa = Marshal::StringToHGlobalAnsi(s);  // charの場合
IntPtr ipu = Marshal::StringToHGlobalUni(s);   // wchar_tの場合

アンマネージ側がTCHARやTEXT()、_T()を使っている場合は、コンパイルオプションによって文字表現が決まります。

  • プロジェクトの[プロパティ]→[構成プロパティ]→[全般]→[文字セット]

これが「マルチバイト文字セット」ならchar、「Unicode文字セット」ならwchar_tです。

バッチビルドのバグ

複数のプロジェクトを含んだソリューションをバッチビルドでビルドすると、変更してないプロジェクトまでビルドされてしまう、という現象に出会ったことはないでしょうか。以下のようなケースで発生します。

  • ソリューションには2つのプロジェクトがある
  • 1つはdllを作るプロジェクトA
  • もう1つはexeを作るプロジェクトB
  • プロジェクトBは、プロジェクトAを参照するように設定
  • よって、プロジェクトBは、プロジェクトAに依存

この状態で、バッチビルドで、プロジェクトAとプロジェクトBそれぞれのDebug版とRelease版をビルドすると、バッチビルドするたびに、プロジェクトAのDebug版とRelease版がリビルドされます(コンパイルエラーは出ないにもかかわらず)。

回避策(というか妥協策)は以下の2点です。

  • バッチビルドの対象を、Debug版かRelease版のどちらか一方にする
  • アクティブな構成を、バッチビルドの対象とした方に設定する

dbgheapのassertionエラー

以下の条件が重なると、assertionエラーが起きます。

  • アンマネージなスタティックリンクライブラリと、マネージなexeからなるソリューション
  • ライブラリ側ではSTLを使用しており、コンテナ(std::listとか)のstaticなインスタンスを定義している
  • exeはフォームアプリ(コンソールアプリではない)
  • デバッグ実行(F5キー)する

※自作のシングルトンなクラスがあって、そのメンバーとしてSTLインスタンスを持っている場合でも、上記の「STLのstaticなインスタンス」に該当します。

エラーの内容はこんな感じです。

Debug Assertion Failed!
File: f:\dd\vctools\crt_bld\self_x86\crt\src\dbgheap.c
Line: 1511

Expression: _CrtIsValidHeapPointer(pUserData)

dbgheapの中にある、ヒープの整合性をチェックするassertに引っかかっているようです。これは、exeのエントリポイントを変更することにより回避できます。

  • プロジェクトの[プロパティ]→[構成プロパティ]→[リンカ]→[詳細]→[エントリポイント]

これを、mainから、?mainCRTStartupStrArray@@$$FYMHP$01AP$AAVString@System@@@Z に変更して下さい。長い名前ですが、要するにmainCRTStartupStrArray()のことです。こうすると、main()の前にmainCRTStartupStrArray()が呼ばれ、ナゼだか分かりませんが、assertエラーがおさまります。

ちなみに、コンソールアプリのmain()の先頭でブレークしてみると、main()がmainCRTStartupStrArray()から呼ばれているのが分かります。フォームアプリの場合は、(デフォルト設定のままでは)これがありません。

LNK2022エラー

前述の「バッチビルドのバグ」の項で挙げたようなソリューションをビルドしていると、突然(何も悪いことはしてないのに)、LNK2022エラーが出て、それ以降は何度ビルドしてもLNK2022が出続けることがあります。リビルドすれば一時的には解消するのですが、そのうち再発します。

これは、マネージインクリメンタルビルドを無効にすると回避できます。

  • プロジェクトの[プロパティ]→[構成プロパティ]→[全般]→[マネージインクリメンタルビルドを有効にする]

ただし、マネージインクリメンタルビルドを無効にすると、ビルドが遅くなるケースがあります。例えば、前述の「バッチビルドのバグ」の項で挙げたようなソリューションの場合、プロジェクトA(dll)を変更してビルドすると、プロジェクトA(exe)がリビルドされてしまいます。

スタートアッププロジェクトの変更

スタートアッププロジェクトとは、デバッグ実行(F5キー)時に起動されるプロジェクトのことです。VC6の頃はアクティブプロジェクトと呼ばれていたと思います。

1つのソリューションで複数のプロジェクトを管理する場合、特にexeを作るプロジェクトが複数ある場合は、スタートアッププロジェクトを意識する必要があります。しかしVC.NETでは、スタートアッププロジェクトを切り替えるのが面倒です。VC6の頃は、切り替え用のコンボボックスをツールバーに追加することができたのですが…。

妥協策として、ソリューションエクスプローラー上で選択されたプロジェクトをスタートアッププロジェクトと見なすことができます。

  • ソリューションの[プロパティ]→[共通プロパティ]→[スタートアッププロジェクト]

ここで[現在の選択]を選択します。

  • スタートアップルーチンのバグ?→staticインスタンスとジェネリクスとデバッグ実行で落ちる
  • LNK2022エラー?→マネージインクリメンタルビルド
Last modified:2012/03/07 15:53:44
Keyword(s):
References:[.NETアプリ開発]
This page is frozen.