マネージドからアンマネージド文字列へ変換
マネージド文字列(System.String)からアンマネージド文字列(unsigned char*)へ変換するのは面倒なものです。ここでは、System.Stringで与えられた文字列を単にC言語の文字列に変換する手法を2つ紹介します。
(「マネージからアンマネージ文字列」と書いているにも関わらず、最後の手法は結局マネージ文字列です。ま、目的は達成できているから、良しとしよう。)
Web上でよく紹介されている方法 ~1~
これ→http://msdn2.microsoft.com/ja-JP/library/d1ae6tz5.aspx
Win32を使うから論外。
Web上でよく紹介されている方法 ~2~
using namespace System::Runtime::InteropServices; void * unmanagedChars = (Marshal::StringToHGlobalAnsi(managedString)).ToPointer(); printf("%s\n", unmanagedChars);
// 解放 Marshal::FreeHGlobal(unmanagedChars);
取得した文字列のためのメモリ領域を解放しないとメモリリークします(たぶん)。
「void *」にしていますが、「char *」にキャストすると良いかと思います。
今回は、printfでの表示でしたのでキャストする手間を考え「void *」としました。
[補足]
マネージだからメモリリークしないと思っている人。
確かに、プログラムが終了するときにはリークしないでしょう。
けれど例えば、ある操作を繰り返すたびにメモリが解放されずに残ってしまう
ということは有り得ます。Javaでも同様です。
たとえば、ArrayListにStringを追加し続けているようなものです。
あまり紹介されていない方法
メモリリークを絶対的に阻止したい→できるだけマネージドから離れたくない人
あるいは、メモリ管理を放棄したい人向け。
unsigned char managedChars __gc[] = System::Text::Encoding::Convert( System::Text::Encoding::Unicode, System::Text::Encoding::GetEncoding("Shift_JIS"), // 環境依存 System::Text::Encoding::Unicode->GetBytes(managedString) );
printf("%s\n", managedChars); // 標準関数ならそのまま使える?!
char str[65536]; // サイズは、目的に合わせてください。 sprintf(str, "%s", managedChars); function(str); // 自前の関数は、sprintfで変換!
環境に合わせて、エンコーディングを変えないとダメだけど、
メモリリークはシステム任せにできる。
エンコーディングの指定は、System::Text::Encoding::GetEncoding(0)でいいのかも。
0を与えると既定のエンコーディングが使われるそうだから。
(→http://msdn2.microsoft.com/ja-jp/library/wzsz3bk3.aspx)
補足
あまり文字列操作には関係のない話題ですが。。
シャローコピーとディープコピー
以下のようにしたとき、aとbは同じ文字列ポインタを指しているハズ。
System::String *a, *b; a = S"Hogehoge"; b = a;
ここで、b=aはシャローコピーになっている。
つまり、aの内容を書き換えるとbの内容も変わるハズ。
なんだけど、実際にはaの内容を書き換える方法がない。
ので、上記のコピーは、一応安全・・・かな?
もっと明示的にディープコピーしたいなら。
b = String::Copy(a);
とする。
検証したコードは、次のような感じ。
System::Diagnostics::Debug::WriteLine(S"------------------------------------ test 1"); { System::String *a, *b; a = S"Hogehoge"; b = a; Console::WriteLine( String::Format(S"String1 : {0}", a) ); Console::WriteLine( String::Format(S"string2 : {0}", b) );
Collections::IEnumerator * enm = a->GetEnumerator(); while (enm->MoveNext() ) { Char * ch = __try_cast<Char*>(enm->Current); *ch = 'a'; } Console::WriteLine( String::Format(S"String1 : {0}", a) ); Console::WriteLine( String::Format(S"string2 : {0}", b) ); }
System::Diagnostics::Debug::WriteLine(S"------------------------------------ test 2"); { System::String *a, *b; char * str; a = S"Hogehoge"; b = a; Console::WriteLine( String::Format(S"String1 : {0}", a) ); Console::WriteLine( String::Format(S"string2 : {0}", b) ); using namespace System::Runtime::InteropServices; str = (char*)(Marshal::StringToHGlobalAnsi(a)).ToPointer(); str[0] = 'p'; Console::WriteLine( String::Format(S"String1 : {0}", a) ); Console::WriteLine( String::Format(S"string2 : {0}", b) ); }
System::Diagnostics::Debug::WriteLine(S"------------------------------------ test 3"); { System::String *a, *b; a = S"Hogehoge"; b = a; Console::WriteLine( String::Format(S"String1 : {0}", a) ); Console::WriteLine( String::Format(S"string2 : {0}", b) );
System::Text::StringBuilder * sb = new System::Text::StringBuilder(a); sb->Replace('H', 'h'); Console::WriteLine( String::Format(S"String1 : {0}", a) ); Console::WriteLine( String::Format(S"string2 : {0}", b) ); }
System::Diagnostics::Debug::WriteLine(S"------------------------------------ test 4"); { System::String *a, *b; a = S"Hogehoge"; b = String::Copy(a); Console::WriteLine( String::Format(S"String1 : {0}", a) ); Console::WriteLine( String::Format(S"string2 : {0}", b) ); }
次のコードは、aからbに文字列をコピーしたつもりになるコード。
System::Void CopyString(System::String * to, System::String * from) { to = from; }
int _tmain() { System::String *a, *b; a = S"Hogehoge"; CopyString(b, a); Console::WriteLine( String::Format(S"String1 : {0}", a) ); Console::WriteLine( String::Format(S"string2 : {0}", b) );
return 0; }
上記のCopyString()中にある「to = from」をしたところで、
CopyString()中のtoが"Hogehoge"になるだけ。
呼び出し元には何ら影響がない。
どうしてもコピーしたいなら、参照かポインタのポインタを使う。
以上は、もちろん文字列Stringに関することだけではないことを最後に書き加えておく。
[参考]