[C++/CLI] 混在モードのGUIアプリケーション作成時の注意点
CLIは混在モードで使用すると、.NETの機能とネイティブなC++を同時に使用することができる非常に便利なものですが、いくつか注意しなければならい点があります。
今回は、そのCLI混在モードでのアプリケーション開発時の注意点の1つについてです。
今回は、そのCLI混在モードでのアプリケーション開発時の注意点の1つについてです。
・サンプルの準備
まずはじめに、VisualStudioのウィザードで、C++/CLIのWindowsFormアプリケーションのプロジェクトを作成します。
そして、作成したフォームクラスにボタンコントロールを追加して、こんな感じのフォームを作ります。

そして、作成したボタンコントロールのイベントを作ります。
不要な部分はすべて省略しますが、ここまでの手順でフォームクラスのソースコードは以下のようになっていると思います。
・ネイティブコードを追加する
ネイティブコードを使用できるようにするために、プロジェクトの設定を変更します。
プロジェクトのプロパティを開き、[構成プロパティ] -> [全般] -> [共通言語ランタイムサポート]
の値を "共通言語ランタイムサポート(/clr)" に変更し、混在モードに変更します。
そして、先ほどのフォームクラスのコードが書かれているファイルの内容を以下のように変更します。
これをコンパイルして実行し、ボタンをクリックすると以下のようなメッセージボックスが表示され、正常に動作することが確認できます。

・エラーを発生させる
先ほどのサンプルでは正常に動作するのですが、NativeClass内で定義されている関数の内容を以下のように変更するだけでエラーが発生してしまいます。
ostringstreamをstaticオブジェクトに変更するだけです。
このように変更するだけで、プログラム実行時に以下のようなエラーメッセージが表示され、正常に動作しなくなってしまいます。

・対策
実はこのような問題が発生する理由が良くわかっていないのですが、とりあえずの対策方法を見つけたので説明します。
結論を先に言うと、エントリポイントをネイティブアプリケーションと同様の形式に修正するとこの問題が発生しなくなります。
まず、VisualStudioが自動生成するエントリポイントは次のようになっています。
このコードの、
の部分を、次のように変更します。
そして、プロジェクトのプロパティエントリポイントを変更します。
プロパティの、[構成プロパティ] -> [リンカ] -> [詳細] -> [エントリポイント]
に、標準では "main" という値が設定されていますが、これを "mainCRTStartup" 変更します。
これでエラーが発生しなくなります。
・最後に
原因は不明なのですが、CLIの混在モードでネイティブクラスのスタティックなインスタンスが存在する場合に、エラーが発生することがあるようです。
今回紹介した対策方法は、試行錯誤の末、偶然発見した対策方法なのですが、とりあえずこれで正常動作するようになるみたいです。
まずはじめに、VisualStudioのウィザードで、C++/CLIのWindowsFormアプリケーションのプロジェクトを作成します。
そして、作成したフォームクラスにボタンコントロールを追加して、こんな感じのフォームを作ります。

そして、作成したボタンコントロールのイベントを作ります。
不要な部分はすべて省略しますが、ここまでの手順でフォームクラスのソースコードは以下のようになっていると思います。
#pragma once
namespace CLIForm {
// 略
public ref class Form1 : public System::Windows::Forms::Form
{
public:
// 略
private:
// ボタンのイベントハンドラ
System::Void button1_Click(System::Object^ sender, System::EventArgs^ e)
{
}
};
}
・ネイティブコードを追加する
ネイティブコードを使用できるようにするために、プロジェクトの設定を変更します。
プロジェクトのプロパティを開き、[構成プロパティ] -> [全般] -> [共通言語ランタイムサポート]
の値を "共通言語ランタイムサポート(/clr)" に変更し、混在モードに変更します。
そして、先ほどのフォームクラスのコードが書かれているファイルの内容を以下のように変更します。
#pragma once
#pragma unmanaged
#include <string>
#include <sstream>
class NativeClass
{
public:
std::string func()
{
std::ostringstream oss;
oss << 1234;
return oss.str();
}
};
#pragma managed
namespace CLIForm {
// 略
public ref class Form1 : public System::Windows::Forms::Form
{
public:
// 略
private:
System::Void button1_Click(System::Object^ sender, System::EventArgs^ e)
{
NativeClass obj;
std::string s = obj.func();
MessageBox::Show( gcnew System::String( s.c_str() ) );
}
};
}
これをコンパイルして実行し、ボタンをクリックすると以下のようなメッセージボックスが表示され、正常に動作することが確認できます。

・エラーを発生させる
先ほどのサンプルでは正常に動作するのですが、NativeClass内で定義されている関数の内容を以下のように変更するだけでエラーが発生してしまいます。
class NativeClass
{
public:
std::string func()
{
static std::ostringstream oss;
oss << 1234;
return oss.str();
}
};
ostringstreamをstaticオブジェクトに変更するだけです。
このように変更するだけで、プログラム実行時に以下のようなエラーメッセージが表示され、正常に動作しなくなってしまいます。

・対策
実はこのような問題が発生する理由が良くわかっていないのですが、とりあえずの対策方法を見つけたので説明します。
結論を先に言うと、エントリポイントをネイティブアプリケーションと同様の形式に修正するとこの問題が発生しなくなります。
まず、VisualStudioが自動生成するエントリポイントは次のようになっています。
[STAThreadAttribute]
int main(array<System::String ^> ^args)
{
// コントロールが作成される前に、Windows XP ビジュアル効果を有効にします
Application::EnableVisualStyles();
Application::SetCompatibleTextRenderingDefault(false);
// メイン ウィンドウを作成して、実行します
Application::Run(gcnew Form1());
return 0;
}
このコードの、
int main(array<System::String ^> ^args)
の部分を、次のように変更します。
int main(int argc, char* argv[])
そして、プロジェクトのプロパティエントリポイントを変更します。
プロパティの、[構成プロパティ] -> [リンカ] -> [詳細] -> [エントリポイント]
に、標準では "main" という値が設定されていますが、これを "mainCRTStartup" 変更します。
これでエラーが発生しなくなります。
・最後に
原因は不明なのですが、CLIの混在モードでネイティブクラスのスタティックなインスタンスが存在する場合に、エラーが発生することがあるようです。
今回紹介した対策方法は、試行錯誤の末、偶然発見した対策方法なのですが、とりあえずこれで正常動作するようになるみたいです。
スポンサーサイト