C++/CLI 文字列キャスト

最近 C++/CLI を弄っているのですが,GUI 以外の部分は通常の C++ で記述しているので,どうしても System::String^ と std::string (, or std::wstring) の相互変換が必要になります.ぐぐって見たところ同様の要望は多いようで,各所で解決方法も記載されていました.それで,せっかくなので System::String^ <-> std::string をキャストに見せかけた関数を作成しておくことにしました.

#include <string>
#include <vcclr.h>

/* ----------------------------------------------------------------- */
//  string_converter
/* ----------------------------------------------------------------- */
template <class CharT>
class string_converter {};

/* ----------------------------------------------------------------- */
//  string_converter<char>
/* ----------------------------------------------------------------- */
template <> class string_converter<char> {
public:
    typedef System::String^ cli_string;
    typedef std::basic_string<char> stl_string;
    typedef char char_type;
    
    static stl_string decode(cli_string src) {
        System::IntPtr mptr = System::Runtime::InteropServices::Marshal::StringToHGlobalAnsi(src);
        stl_string dest(static_cast<char_type*>(mptr.ToPointer()));
        System::Runtime::InteropServices::Marshal::FreeHGlobal(mptr);
        return dest;
    }
    
    static cli_string encode(const stl_string& src) {
        cli_string dest = gcnew System::String(src.c_str());
        return dest;
    }
};

/* ----------------------------------------------------------------- */
//  string_converter<wchar_t>
/* ----------------------------------------------------------------- */
template <> class string_converter<wchar_t> {
public:
    typedef System::String^ cli_string;
    typedef std::basic_string<wchar_t> stl_string;
    typedef wchar_t char_type;
    
    static stl_string decode(cli_string src) {
        System::IntPtr wptr = System::Runtime::InteropServices::Marshal::StringToHGlobalUni(src);
        stl_string dest(static_cast<char_type*>(wptr.ToPointer()));
        System::Runtime::InteropServices::Marshal::FreeHGlobal(wptr);
        return dest;
    }
    
    static cli_string encode(const stl_string& src) {
        cli_string dest = gcnew System::String(src.c_str());
        return dest;
    }
};

/* ----------------------------------------------------------------- */
//  string_cast
/* ----------------------------------------------------------------- */
template <class Type, class Source>
inline Type string_cast(Source src) {
    return string_converter<typename Type::value_type>::decode(src);
}

template <>
inline System::String^ string_cast(std::basic_string<char> src) {
    return string_converter<char>::encode(src);
}

template <>
inline System::String^ string_cast(std::basic_string<wchar_t> src) {
    return string_converter<wchar_t>::encode(src);
}

使用方法は,変換させたい方の型をテンプレート引数に,変換元の文字列を引数に指定します.例えば,System::String^ から std::string に変換する場合は以下のようになります.

System::String^ src = gcnew System::String(L"Hello, world!");
std::string dest = string_cast<std::string>(src);

全てを C++/CLI 内で完結させる場合は System::String^ だけで済ませばいいのですが,メインの処理部分はできるだけ複数のコンパイラコンパイル可能な形にしておきたいので,この類の変換は不可避になりそうです.

追記

Visual Studio 2008 (VC++ 9.0) では marshal_as<> と言う関数(演算子?)が追加されており,上記の変換はそれを用いて行うことができるようです.最新の VC++ 環境で開発を行っている場合は,わざわざ作成する必要はない模様.

marshal_as

このメソッドは、ネイティブ型とマネージ型との間でデータを変換する簡単な方法です。サポートされているデータ型を確認するには、「C++ におけるマーシャリングの概要」を参照してください。一部のデータ変換ではコンテキストが必要になります。このようなデータ型は、marshal_context クラスを使用して変換できます。

http://msdn.microsoft.com/ja-jp/library/bb384859.aspx