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 ); } }