C++で書かれたDLLにポインタを渡したいことがあります。例えば次のような関数がエクスポートされていたとします。
void WINAPI ConvertToShort(char* pstr, short* pret);
これをC#側から使用したい。char* は文字列なので C# 側からは string を渡してやるだけでよいですが、short* については何を渡していいものやら困ります。
C#には IntPtr という型があります。これは汎用的なポインタを表す型で、ほぼ void* と同義です。
ただしC#は超厳しい型付け言語なので、void* みたいな万能選手は万能ゆえの曖昧さを解決するために、回りくどい変換メソッドを経由しないと使えません。
具体的には、IntPtrの変数に Marshal.AllocHGlobalで必要なサイズのメモリを確保し、それをC++のDLLに渡します。
さらにMarshal.ReadInt16(必要な型によって異なる)などで変換後、確保したメモリをMarshal.FreeHGlobalで解放する、と3段階の面倒なプロセスを経なければいけません。
C#はその厳しさゆえテキトーなC++とは違いメモリ破壊など厄介な問題が発生しませんが、こういうときに手続きが面倒です。リスクと面倒さはトレードオフですね。
using System.Runtime.InteropServices;
[DllImport("DrsUtil.dll", EntryPoint = "ConvertToShort")]
extern static void ConvertToShort(string pstr, IntPtr pret);
IntPtr buffer = new IntPtr();
buffer = Marshal.AllocHGlobal(2);
ConvertToShort("XXXXXX", buffer);
short sval = Marshal.ReadInt16(buffer);
Marshal.FreeHGlobal(buffer);
実用的には、次のようにConvertToShortをラップして使いやすい形に整えるのが賢明でしょう。
[DllImport("DrsUtil.dll", EntryPoint = "ConvertToShort")]
extern static void _ConvertToShort(string pstr, IntPtr pret);
short ConvertToShort(string str)
{
IntPtr buffer = new IntPtr();
buffer = Marshal.AllocHGlobal(2);
_ConvertToShort(str, buffer);
short sval = Marshal.ReadInt16(buffer);
Marshal.FreeHGlobal(buffer);
return sval;
}
IntPtrには何でも入りますから、例えば構造体をゲットすることも可能です。ただしC#側で構造体をC#流に定義してあげなければなりません。若干面倒です。
参考
IntPtr からの色々な型への変換 – Kenrowの覚書と日々
@IT:.NET TIPS Win32 APIやDLL関数に構造体を渡すには? – C#