六帖のかたすみ

DVを受けていた男性。家を脱出して二周目の人生を生きています。自閉症スペクトラム(受動型)です。http://rokujo.org/ に引っ越しました。

C++で書かれたDLLにC#からポインタを渡す

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ではこれが必要
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); //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#