C++/CLIでネイティブな文字列とmanagedな文字列を相互変換する「試行」その1ではマーシャリング関係の機能を使わないでサンプルソースを書いてみました。
今回は、マーシャリング関係の機能を活用してこれをよりコンパクトに書き直してみます。
サンプルソース §
Visual C++ Express 2005 Beta2で、managedコンソールアプリケーションのテンプレートでプロジェクト作成後に、以下のソースを入力します。
#include "stdafx.h"
// あえてprintfを使うのincludeしておく
#include <stdio.h>
using namespace System;
// ここからはネイティブコードにしてちょ
#pragma unmanaged
// printfでcharのポインタで受けた文字列を出力するネイティブコード関数
void unmanagedOutput(const char * str)
{
printf("native string: %s\n",str);
}
// いかにも古き良きCらしいコーディングで文字列を生成するネイティブコード関数
// むろん、以下の関数はバッファオーバーランの可能性がある悪い関数である
void unmanagedCreateString(char * str)
{
// 要するにアルファベット大文字A~Zの生成
for( int i=0; i<26; i++ )
{
*str++ = 'A' + i;
}
*str = '\0';
}
// ここからはmanagedコードに戻してちょ
#pragma managed
// managedコードで文字列を加工する関数。小文字にしてみる
String ^ managedModefyString( String ^ str )
{
return str->ToLower();
}
int main(array<System::String ^> ^args)
{
// 入れ物になる配列。マジックナンバー27はお行儀が悪いぞ!
char test[27];
// ネイティブコード関数で配列に文字列をセットさせる
unmanagedCreateString(test);
// managedコードで文字列を加工する
// char配列からSystem::Stringに直すのは、
// PtrToStringAnsiメソッドに任せることができる
String ^ result = managedModefyString(
System::Runtime::InteropServices::Marshal::PtrToStringAnsi((IntPtr)test) );
// System::StringからANSI文字に直すのは
// StringToHGlobalAnsiメソッドに任せることができる
IntPtr ptr = System::Runtime::InteropServices::Marshal::StringToHGlobalAnsi( result );
try
{
// ネイティブコード関数から結果を出力
// IntPtr型の値からToPointerメソッドを呼んでvoidのポインターにした上でキャスト
// (もっとスマートな書き方はあり得るか?)
unmanagedOutput( (const char*)ptr.ToPointer() );
}
finally
{
// おっと、後始末を忘れちゃいけません!
System::Runtime::InteropServices::Marshal::FreeHGlobal(ptr);
}
// おしまい
return 0;
}
実行結果 §
native string: abcdefghijklmnopqrstuvwxyz
解説 §
ANSI文字列ポインターからSystem::String型に変換するには以下のメソッド。
System::Runtime::InteropServices::Marshal::PtrToStringAnsi
System::String型からANSI文字列ポインターに変換するには以下のメソッド。
System::Runtime::InteropServices::Marshal::StringToHGlobalAnsi
同様にUnicode文字列とのポインターとの相互変換はAnsiのかわりにUniという名前を持ったメソッド。ANSI/Unicodeの自動判定はAnsiのかわりにAuto。
StringToHGlobalAnsi等は、文字列をWindowsのグローバルヒープ(GlobalAllocする領域)に(たぶん)確保します。従って、この領域は必ず開放しなければなりません。解放するには、以下のメソッドを使います。
System::Runtime::InteropServices::Marshal::FreeHGlobal(ptr);
これらのメソッドは、ポインターではなくIntPtr型の値を扱うので、それとポインター型の間を適切に相互変換する必要があります。必要に応じてキャストやToPointerメソッドなどを使います。
感想 §
結局、C++/CLIの問題ではなく、System::Runtime::InteropServices::Marshalクラスの問題になってしまいました。あれまあ。
つまりは、昔からそこにあった機能の問題ということで、単に使う機会が無く知らなかっただけ、ということですね。