C++/CLIでstd::stringとSystem::string^を相互に変換する

タイトルの通りで、C++/CLIでC++の文字列とC#の文字列を相互に変換する方法です。

環境は、VS2017 15.5.4のCLR コンソールアプリケーションのプロジェクトで動作確認しています。

先ず、以下のヘッダーをincludeします。

#include <msclr/marshal_cppstd.h>

C# → C++への文字列変換

includeしたヘッダーにある msclr::interop::marshal_as 関数を使って変換します。

System::String^ cs_string = gcnew System::String("ああああ");
std::string cs_string_2_cpp_string = msclr::interop::marshal_as<std::string>(cs_string);

std::cout << cs_string_2_cpp_string << std::endl;

文字列のリテラルはプレフィックスに "L" や "u8" を付けても正常に処理してくれます。 マルチバイト文字をstringで受けたためバッファの中身は以下のように読めない状態になりますがこれで想定通りです。

char buff[] = { 0xe3, 0x81, 0x82, 0xe3, 0x81, 0x82, 0xe3, 0x81, 0x82, 0xe3, 0x81, 0x82 } // utf8::e38182 = 'あ'

また、テンプレートの引数を std::wstring に変更しても問題なく処理が完了します。

// wstringに変換
std::wstring cs_string_2_cpp_string_2 = msclr::interop::marshal_as<std::wstring>(cs_string);

余談ですが、この時std::wstringのバッファの内容は以下のようになります。

wchar_t buff[] = { 0x3044, 0x3044, 0x3044, 0x3044 }

これ、処理上どっち正しいとか存在しないのでないので、その時の仕様に従ってください。

C++ → C#への変換

こちらも上述の関数を呼び出せばいいだけなのですが、パターンがいくつかあります。

いちばん簡単なのが以下パターンです。std::stringにconst char*(のように)設定されているパターン

std::string cpp_string = "ああああ";
System::String^ cpp_string_2_cs_string = msclr::interop::marshal_as<System::String^>(cpp_string);

次にプレフィックスにu8が付いている変換方法ですが、以下のようにそのまま設定すると文字化けになります。

std::string cpp_string = u8"ああああ";
System::String^ cpp_string_2_cs_string = msclr::interop::marshal_as<System::String^>(cpp_string);
// 文字化け発生 = "縺ゅ≠縺ゅ≠"

ネットにはMarshalAsすればいいなどありますが、そういった操作はあまりしたくないので代替案を考案しました。

  • utf-8のバイト配列を取得する
  • 取得したバイト配列をC#のEncoding.UTF8のGetStringで処理する

この時、GetStringはarrayでstd::stringをc_str()したり[i]したしするとchar*が取れるので型が違うのを変換します。.NETのbyte[]はCLIの世界だとArrayです。

// UTF-8の文字列
std::string cpp_string = u8"ああああ";

// 型の変換
array<unsigned char>^ c_array = gcnew array<unsigned char>(cpp_string.length());
for (int i = 0; i < cpp_string.length(); i++)
{
    c_array[i] = cpp_string[i];
}

// .NETのライブラリでバイト配列(
System::Text::Encoding^ u8enc = System::Text::Encoding::UTF8;
System::String^ u8_array = u8enc->GetString(c_array);

これでu8_arrayに想定通り文字列が入ります。

最後に

というか文字列に関係ある型がいろいろあって中に設定されている文字のエンコードと両方を考慮しないといけないのは結構面倒ですね、、、バリエーションがたくさんあるので、普通の開発であれば開発規約で文字列の取り扱いをある程度決定できますが、どの局面でも利用できるような汎用的な操作を一括で提供はなかなか難しいかと思います。