はじめに
C#とC++を足して2で割り損ねた言語「C++/CLI」は.NETと従来のnativeとの仲介役として機能してくれます。今までに書かれた、あるいはこれからも書き続けるであろうnativeコードをC++/CLIで作った小さなラッパー(wrapper)でくるむことでC#やVB.NETから利用でき、僕はとても重宝しています。
ただ、managedとnativeの混在を許すため、コード自体はかなりややこしいことになります。ややこしさの最たるものが「文字列」です。Cで使われる char*、wchar_t* とC++標準のstd::string、std::wstringだけでも十分にややこしいのに、さらに加えてmanaged文字列「System::String」まで扱うことになり、必要に応じてこれら相互の変換を避けては通れません。
このアーティクルではこれら様々な文字列、そして数々の文字コードの変換の方法を紹介します。
.NET文字列とC++文字列
C#やVB.NETで使われている文字列「System::String^」とC++文字列「std::string」「std::wstring」との相互変換にはマーシャル・ライブラリが最もお手軽です。コードのアタマで
#include <marshal_cppstd.h> using namespace msclr::interop;
するだけで準備完了。
使い方もとっても簡単、関数template: marshal_as<OutType>のテンプレート引数(OutType)に変換先の型を与えて呼び出すだけ。たとえば:
std::string s0 = "変換元の文字列"; System::String^ s1 = marshal_as<System::String^>(s0); std::wstring s2 = marshal_as<std::wstring>(s0);
ちょっとばかり気になるのは変換に要する時間ですが、よほど頻繁に変換を繰り返すのでない限り、実際のアプリケーションでは気にするほどではなさそうです。ただし、System::String^―std::string間の変換/逆変換にはUnicodeとOEM文字コード(すなわちshift_jis)間の文字コード変換を伴うのでString^―std::wstring間の変換/逆変換に比べて3倍ほど遅いという実験結果を得ています。スピードを気にするならばC++/CLIのコード内ではできるだけ変換を要しないよう、System::String^あるいはstd::wstringに揃えておいたほうがよさそうです。
.NET文字列とC文字列
既存のライブラリであれば、関数の引数あるいは戻り値の型がchar*やwchar_t*であることも少なくないでしょう。上記の変換を用い:
System::String^ s0 = L"変換元の文字列"; std::string tmp = marshal_as<std::string>(s0); const char* s1 = tmp.c_str();
としても構わないのですが、マーシャル・ライブラリはSystem::String^とconst char*/const wchar_t*間の直接相互変換もサポートしています。その際は#include <marshal.h>しておいてください。
const char*/const wchar_t*からSystem::String^への変換は上記と同じく:
const char* s0 = "変換元の文字列"; System::String^ s1 = marshal_as<System::String^>(s0); const wchar_t* s0 = L"変換元の文字列"; System::String^ s1 = marshal_as<System::String^>(s0);
なのですが、System::String^からconst char*/const wchar_t*への変換の際は変換された文字列のためにメモリを確保しなければなりませんし、使った後は適切なタイミングで開放しなければなりません。このようなメモリ管理を行うのがmarshal_contextです。marshal_contextは変換に必要な領域を確保し、marshal_contextがスコープから外れる時に(デストラクタによって)確保した領域を解放します。
System::String^ s0 = L"変換元の文字列"; const char* s1; const wchar_t* s2; { marshal_context ctx; s1 = ctx.marshal_as<const char*>(s0); s2 = ctx.marshal_as<const wchar_t*>(s0); // s1, s2 を使う... } // ctxのスコープ外では s1, s2は使用不可(解放済)
……なので得られた変換文字列は必要に応じて別のメモリにコピーしておかなければなりません。