管理:ヽ(゚∀。)ノうぇね

Programing Enlightened

ナビゲーション リンクのスキップ

始めに

 C++/CLI はおそらく旧来のライブラリを .net のアセンブリに変更されるのに一番利用されると考えています。
 そのとき、一番面倒なのは、旧来の MBCS や wchar_t 型と System::String をどうやりとりすればいいのかという点です。

 ここでは、MultiByte ないし、 WideChar の System::String 型とのやりとりについて記述しておきます。

  1. MultiByte から System::String に
  2. WideChar から System::String に
  3. System::String から MultiByte に
  4. System::String から WideChar に
  5. Visual Studio 2008 での追加機能

MultiByte から System::String に

 これは特に問題も疑問の余地もないですよね。
std::string text = "サンプル・テキスト";  // めんどくさいので char * はこれで代用
String^ textString = gcnew String(text.c_str());

String^ textString = Marshal::PtrToStringAnsi(text.c_str());  // Marshal を使うとこれ

WideChar から System::String に

 これも特に疑念の余地はありませんよね。
std::wstring wide_text = L"ワイド文字列";  // めんどくさいので wchar_t * はこれで代用
String^ textString = gcnew String(wide_text.c_str());

String^ textString = Marshal::PtrToStringUni(wide_text.c_str());  // Marshal を使うとこれ

 Marshal クラスにはそのほかに BSTR 用の PtrToStringBSTR や 自動識別の PtrToStringAuto も用意されています。TCHAR を使っているときは、PtrToStringAuto を使った方が便利でしょう。

System::String から MultiByte に

 ここから段々と面倒な手続きが必要になってきます。
 キーワードは、ピンニング、マーシャル・アズです。
 追加で、System.Runtime.InteropServices の Marshal クラスを見ておくといろいろと便利です。
std::string text;  // 文字列を受け止めるバッファ
String^ textString = gcnew String("適当なサンプル文字列");  // 変換する文字列

// System::String をChar 型 = wchar_t 型の配列にする
array<Char>^ warr = textString->ToCharArray();

// 配列がガベージ・コレクトによって移動しないようピンニング
pin_ptr<Char> wptr = &warr[0];

// 変換後文字サイズを取得
char *buffer = 0;
int len = ::WideCharToMultiByte(CP_UTF8, 0, wptr, num, buffer, 0, NULL, NULL);
if ( len > 0 ) {

    // 文字バッファを取得して
    buffer = new char[len + 1];
    memset(buffer, 0, len+1);

    // UNICODE を MultiByte に変換
    ::WideCharToMultiByte(CP_UTF8, 0, wptr, num, buffer, len, NULL, NULL);

    // バッファをMultiByteの文字型に代入
    text = buffer;
    if ( buffer ) delete [] buffer;
}

 もっといい方法が .NET Framework で提供されていました。
std::string text;  // 文字列を受け止めるバッファ
String^ textString = gcnew String("適当なサンプル文字列");  // 変換する文字列

IntPtr mptr = Marshal::StringToHGlobalAnsi(textString);

text = static_cast<const char*>(mptr.ToPointer());
Marshal::FreeHGlobal(mptr);

System::String から WideChar に

 これはちょっと微妙。どうやってコピーするかによると思う。
 基本的にはピンニングして、コピーすればいい。
String^ textString = gcnew String("適当なサンプル文字列");  // 変換する文字列

// System::String をChar 型 = wchar_t 型の配列にする
array<Char>^ warr = textString->ToCharArray();

// 配列がガベージ・コレクトによって移動しないようピンニング
pin_ptr<Char> wptr = &warr[0];

// 文字列を受け止めるバッファ
std::wstring wide_text(wptr);

 egtra さまからのご指摘で、追加しました。
 2006/05/20

 managed C++ 時代の vcclr.h を利用したやり方も使えます。
#include <vcclr.h>  // これ重要

String^ textString = gcnew String("適当なサンプル文字列");  // 変換する文字列

pin_ptr<wchar_t> wptr = PtrToStringChars(textString);

// 文字列を受け止めるバッファ
std::wstring wide_text(wptr);

String^ textString = gcnew String("適当なサンプル文字列");  // 変換する文字列

IntPtr wptr = Marshal::StringToHGlobalUni(textString);

// 文字列を受け止めるバッファ
std::wstring wide_text(static_cast<const wchar_t*>(wptr.ToPointer()));

Marshal::FreeHGlobal(wptr);

System::String から TCHAR に

 MFC ないし、ATL(WTL) の CString を使ってまとめて書くと次のようになります。
String^ textString = gcnew String("適当なサンプル文字列");  // 変換する文字列

IntPtr tptr;

#ifdef _UNICODE
tptr = Marshal::StringToHGlobalUni(textString);
#else
tptr = Marshal::StringToHGlobalAnsi(textString);
#endif

CString result_text = (LPCTSTR)tptr.ToPointer();

Marshal::FreeHGlobal(tptr);
 すごく簡単ですね。

 以下、egtra さまからのご指摘で、追加しました。

 でも、#ifdef が嫌いなんじゃー、という方は、あまり難しく考えずに、
String^ textString = gcnew String("適当なサンプル文字列");  // 変換する文字列

// CStringT 型はコンストラクタで変換してくれるぜ
CString buff(textString);
でいいのかもしれません。
 え、最初からこれを紹介しろって(w

 これで基本的な置き換えはおっけーです。

VisualStudio 2008 での追加機能

 VisualStudio 2008 になって、C++/CLI では marshal_as<> が利用できるようになりました。
 これは組み込み関数である marshal_as 演算子とマーシャリング・オブジェクト marshal_context を作成し、 ネイティブ・データの生存時間を管理する marshal オブジェクトによって構成されています。

 まぁ、もともとの C++/CLI の設計仕様に組み込まれていた機能が、ようやく取り込まれたんですけどね。

// マーシャリング用ヘッダ
#include "msclr/marshal.h"
#include "msclr/marshal_windows.h"
#include "msclr/marshal_cppstd.h"
#include "msclr/marshal_atl.h"

// LPCTSTR to System::String
LPCTSTR lptMessage = _T("マーシャリングのテストです。");
String^ message = marshal_as<String^>(lptMessage);

// std::string to System::String
std::string cstrMessage = "Marshalling テスト";
String^ message = marshal_as<String^>(cstrMessage);

// std::wstring to System::String
std::wstring cwstrMessage = L"Marshalling テスト";
String^ message = marshal_as<String^>(cwstrMessage);

// System::String to LPCTSTR
String^ strMessage = "マーシャリングをします。";
marshal_context^ context = gcnew marshal_context;
LPCTSTR message = context->marshal_as<LPCTSTR>(strMessage);
delete context;

// System::String to std::string
String^ strMessage = "マーシャリングをします。";
std::string message = marshal_as<std::string>(strMessage);

// System::String to std::wstring
String^ strMessage = "マーシャリングをします。";
std::string message = marshal_as<std::wstring>(strMessage);
 marshal_as 演算子で直接処理できるのか、marshal_context を間に噛む必要があるのか、は、もとの型の生存の仕方に依存します。
 BSTR や const char* と言った領域確保が必要な文字列に関しては marshal_context を使い、確保した領域の開放をしてくれる std::string, std::wstring, _bstr_t などは、marshal_as 演算子で変換が可能となります。
 marshal_as はただ文字列の互換を行うだけの演算子ではありません。
 marshal_context を自前で拡張することで、こんなことも可能です。
// 定義 どっかのヘッダ
typedef struct _native_struct
{
    double x;
    double y;
    double z;
    
    std::string name;
} NativeStruct;

ref class ConvertedClazz
{
private:
    double _x, _y, _z;
    
    String^ _name;
public:
    ConvertedClazz()
    {
    }
};

// インクルード marshal_俺専用.h
namespace msclr
{
   namespace interop
   {
      template<>
      inline ConvertedClazz^ marshal_as<ConvertedClazz^, struct NativeStruct> (const struct NativeStruct& from)
      {
          ConvertedClazz^ clazz = gcnew ConvertedClazz;
          
          // from の中身を clazz に移動
          // めんといので手抜き
          return clazz;
      }
      
      template<>
      ref class context_node<struct NativeStruct*, ConvertedClazz^> : public context_node_base
      {
      private:
         struct NativeStruct* toPtr;
      public:
         context_node(struct NativeStruct*& toObject, ConvertedClazz^ fromObject)
         {
            // 初期化
            toPtr = new NativeStruct();
            
            // toPtr のオブジェクトに ConvertedClazz の値のコピー処理をする。
            toPtr->x = fromObject->X;
            ...
         }

         ~context_node()
         {
            this->!context_node();
         }

      protected:
         !context_node()
         {
             // 明示的な削除がされなかったときのファイナライザ処理
             // toPtr の中身を削除する。
             delete toPtr;
         }
      };
   }
}

// コード
struct NativeStruct a;
ConvertedClazz^ clazz = marshal_as<ConvertedClazz>(a);

struct NativeStruct *b = NULL;
marshal_context context;
b = context.marshal_as<struct NativeStruct*>(clazz);
などと、ネイティブとマネージド型間のデータ互換用に用意することができます。
 どちらかというと、文字列変換はサンプルの一つで頻繁に使用するため実装してあると言うことですね。

参照:MSDN へのリンクです。
MSDN2 : StringToGlobalAnsi
MSDN2 : StringToGlobalAuto
MSDN2 : StringToGlobalUni
MSDN2 : FreeHGlobal
Microsoft サポート: String* to char*
MSDN2 : marshal_as<>
C++/CLI におけるマーシャリングの概要



Topに戻る