C#事始め
C#初心者が最初に学ぶべきことはマネージコード-アンマネージコード間のデータの受け渡しであることは論をまたない。
これを押さえれば、使い慣れた言語で生成した“Hello World!”文字列をC#に持ってきて表示させれは“Hello World!”のできあがりだからだ(何
これはIntPtrとかいったunsafeな言語機能を一切使わずとも実現できる。
C++からC#に文字列を渡す例
文字列のような多バイトデータのやりとりをする場合、次の点が常に問題になる。
- 誰がメモリ上にバッファを用意するのか
- 誰がそのバッファを解放するのか
これについて2通りのやり方を示す。
C++側で独自にメモリを確保・管理し、C#側で解放にタッチしないタイプ
呼び出し側(C#)。
using System.Runtime.InteropServices; ... // 宣言 [DllImport("HelloWorldDll.dll", CharSet = CharSet.Ansi, EntryPoint = "GetHelloWorld1")] public static extern void GetHelloWorld1( [Out, MarshalAs(UnmanagedType.LPStr)] out string msg); ... // 呼び出し string str; GetHelloWorld1(out str); Console.WriteLine("str1={0}", str);
呼び出される側(C++)
extern "C" __declspec(dllexport) void GetHelloWorld1(LPSTR* msg); // 宣言。Cリンケージにしとくとカソタソ __declspec(dllexport) void GetHelloWorld1(LPSTR* p_msg) { // 引数がポインタのポインタなことに注意! static CHAR msg_buf[1000]; sprintf(msg_buf, "Hello World!"); *p_msg = msg_buf; }
このように、呼び出される側では、DLL生存の間永続するメモリ領域を適当に確保しておき、それへのポインタを*p_msgに代入して戻ればいい。
string<-->LPSTR変換はマーシャラがやってくれる。マルチバイト文字列でなくてユニコードを使いたいときは、CharSet.UnicodeとLPWSTRを併用でもするといい*1。
メモリの確保・解放をC#側にお任せするタイプ
呼び出し側(C#)。
using System.Runtime.InteropServices; ... // 宣言 [DllImport("HelloWorldDll.dll", CharSet = CharSet.Ansi, EntryPoint = "GetHelloWorld2")] public static extern void GetHelloWorld2( [Out, MarshalAs(UnmanagedType.LPStr, SizeParamIndex = 1)] StringBuilder msg, int siz); ... // 呼び出し StringBuilder sb = new StringBuilder(1000); GetHelloWorld2(sb, 1000); Console.WriteLine("str2={0}", sb.ToString());
呼び出される側(C++)
extern "C" __declspec(dllexport) void WINAPI GetHelloWorld2(LPSTR msg, int siz); // 宣言。Cリンケージにしとくとカソタソ extern __declspec(dllexport) void WINAPI GetHelloWorld2(LPSTR msg, int siz) // こっちは普通にLPSTRでいい { sprintf(msg, "This is a pen."); }
このように、呼び出し側(C#)でStringBuilderを使いそいつにメモリの面倒を見させる。
謎の指定子SizeParamIndexは、バッファサイズを伝える引数を指定する。上の例だと2番目の引数を使っているのでSizeParamIndex = 1としている。
実行結果
str1=Hello World!!
str2=This is a pen.
応用編
C#側から渡した文字列をC++側で編集して戻したり、バイナリデータをやりとりしたり、構造体や共用体のやりとり等も適当にゴニョゴニョしればできる(ry
配列については
[In, Out, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr, SizeParamIndex = 1)]
みたくしてマーシャラにLPArray指定で配列である旨を知らしめ、さらにArraySubTypeで要素の型を教えてやる。
構造体や共用体についてはStructLayout属性とかFieldOffset属性でググればよろし。あとはこれらの組み合わせ。
*1:CallerとCalleeで揃える必要があるから、こういった場合LPTSTRはあまり使う機会がない。