2008年11月29日土曜日

PtrToStructureArray(): 配列のマーシャリング

Marshal.PtrToStructure()を使うと、メモリの内容を元に構造体などを復元することができます。これでC言語などとデータをやりとりできるようになります。
構造体の配列に関してもMarshalAs属性を使用すれば扱えますが、この場合、コンパイル時にサイズを決定した固定長配列に限られます。
そこで、実行時にサイズを決定できるPtrToStructureArray()を作ってみました。どれほど使い道があるやら。

public class Marshal{
static ModuleBuilder moduleBuilder;
static ModuleBuilder ModuleBuilder {
get {
if( moduleBuilder == null ) {
var name = new AssemblyName( "Sayuri.Dynamic" );
var ab = AppDomain.CurrentDomain.DefineDynamicAssembly( name, AssemblyBuilderAccess.Run );
moduleBuilder = ab.DefineDynamicModule( name.Name );
}
return moduleBuilder;
}
}
public static T[] PtrToStructureArray<T>( IntPtr ptr, int size ) {
var type = typeof(T);
var typeName = string.Format( "{0}Array{1}", type.Name, size );
var arrayType = ModuleBuilder.GetType( typeName );
if( arrayType == null ){
var tb = ModuleBuilder.DefineType( typeName, TypeAttributes.SequentialLayout );
var fb = tb.DefineField( "array", type.MakeArrayType(), FieldAttributes.Public|FieldAttributes.HasFieldMarshal );
var ctype = typeof( MarshalAsAttribute );
var cb = new CustomAttributeBuilder( ctype.GetConstructor( new[] { typeof( UnmanagedType ) } ),
new object[] { UnmanagedType.ByValArray }, new[] { ctype.GetField( "SizeConst" ) }, new object[] { size } );
fb.SetCustomAttribute( cb );
arrayType = tb.CreateType();
}
var obj = SystemMarshal.PtrToStructure( ptr, arrayType );
var fi = arrayType.GetField( "array" );
return (T[])fi.GetValue( obj );
}
}
やってることはT型とサイズ20が与えられた場合に
[StructLayout(LayoutKind.Sequential)]
class TArray20{
[MarshalAs(UnmanagedType.ByValArray, SizeConst=20)]
public T[] array;
}
というクラスをその場で生成して使っています。

0 件のコメント: