MS.NetのPlatform Invoke でしょんぼり

GetDelegateForFunctionPoiter と GetFunctionPointerForDelegate でマーシャリングの挙動が違うんだな。

NativeからManagedの関数をGetDelegateForFunctionPoiter 経由でコールする時に、Delegate型を

delegate void dg_ByteArray( [MarshalAs( UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = 1 )]Byte[] data, Int32 size );

みたいに書いてGetDelegateForFunctionPoiter でNative側から呼ばせると、呼び出されたManaged関数はちゃんとサイズが設定されたByte[]を受け取るんだけども変更しても呼び出し側には反映されない。
IntPtrで渡してMarshal.Copyかunsafeコードなら問題ないが、キレイではないな−。

おまけ:C#(.Net managed)とNative(unmanaged) DLL間のマーシャリングについて

extern構文を使ってDLL関数をインポートする場合は[DllImport]属性をメソッドに設定できる。LoadLibrary, GetProcAddress, GetDelegateForFunctionPointer 等で遅延バインディングを行う場合は[UnmanagedFunctionPointer]属性をdelegate型に設定することができる。

引数や戻り値のマーシャリングの設定には[MarshalAs]属性を使える。
デフォルトでどのような変換が行われるかは Platform Invoke Data Typesを参照。


MarshalAs の使用例:

delegate void dg_Int16(Int16 v);
delegate void dg_Int32(Int32 v);
delegate void dg_Int64(Int64 v);
delegate void dg_UInt16(UInt16 v);
delegate void dg_UInt32(UInt32 v);
delegate void dg_UInt64(UInt64 v);
delegate void dg_Float(Single v);
delegate void dg_Double(Double v);
delegate void dg_Bool(bool v);
delegate void dg_IntPtr( IntPtr v );
delegate void dg_Enum( ArgType v );
delegate void dg_String( [MarshalAs( UnmanagedType.LPStr )]String v );
delegate void dg_WString( [MarshalAs( UnmanagedType.LPWStr )]String v );
delegate void dg_StringBuilder( [MarshalAs( UnmanagedType.LPStr, SizeParamIndex = 1 )]StringBuilder v, int size );
delegate void dg_WStringBuilder( [MarshalAs( UnmanagedType.LPWStr, SizeParamIndex = 1 )]StringBuilder v, int size );
delegate void dg_Byte( Byte v );
delegate void dg_ByteArray( [MarshalAs( UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1,SizeParamIndex=1 )]Byte[] data, Int32
size );
delegate void dg_Char( [MarshalAs( UnmanagedType.U2 )]Char v );
delegate void dg_CharArray( [MarshalAs( UnmanagedType.LPArray, ArraySubType = UnmanagedType.U2, SizeParamIndex = 1 )]Char[] data,
Int32 size );
  • UnmanagedType.LPStr と UnmanagedType.LPWStr を指定することで、文字列のエンコーディングANSIコードページ/Unicodeに変更することができる。
  • Charは1文字だけでもANSIコードページに変換されてしまうので、それを避けるにはUnmanagedType.U2 を指定するか、メソッド単位でCharSetを指定する。
  • Native関数によって変更されるバッファをC#の呼び出し側から指定するには、StringBuilderもしくはByte,Charを使える。
  • IntPtrをString.Formatで表示するとおかしな数値を出すことがある。これはIntPtr.ToInt64()を使うと回避できる。

GetFunctionPointerForDelegate を使ってC#のデリゲートへのポインタをNative側に渡す場合、上記のマーシャリングではNative側の指定したバッファをC#から更新することできない。マーシャリングによってコピーされた一時データを変更するだけで、
Nativeな呼び出し側の指定したバッファは変更されない。この場合はIntPtr とunsafeコードを使うとうまくいく。Marshal.Copyでも可。

// c#
delegate void dg_CharArrayUnsafe( IntPtr data, Int32 size );
void onCharArrayUnsafe( IntPtr addr, int size ) {
	say( "C#cb CharArrayUnsafe[{1}]={0}", addr, size );
	unsafe {
		Char* p = ( Char* ) addr;
		p[2] = 'A';
	}
}

なお、GetFunctionPointerForDelegateを使う場合は対象のdelegateをGCHandleでGCから保護する必要がある。こんな感じ。

public class DelegateConverter<T> : IDisposable {
	private GCHandle mHandle;
	public void Dispose() { clear(); }
	public DelegateConverter() {}
	public DelegateConverter( T dg ) { set( dg ); }
	public void clear() {
		if( mHandle.IsAllocated ) mHandle.Free();
	}
	public void set( T dg ) {
		clear();
		mHandle = GCHandle.Alloc( dg );
	}
	public IntPtr getFunctionPointer() {
		return Marshal.GetFunctionPointerForDelegate( (System.Delegate) mHandle.Target );
	}
}