我正在与具有以下形式的一些函数的C API接口:
int get_info(/* stuff */, size_t in_size, void* value, size_t *out_size);
这是一个众所周知的C语言,它从单个函数返回一堆不同类型的数据:in_size参数包含传递给value的缓冲区的大小,写出的实际字节数是通过out_size指针写入的,然后,调用者可以在值缓冲区中读回其数据.
我试图很好地从C#中调用这些类型的函数(即,不为每个不同的类型进行重载,并且必须单独调用它们,这很丑陋并且引入了大量重复的代码).所以我自然尝试过这样的事情:
[DllImport(DllName, EntryPoint = "get_info")]
int GetInfo<T>(/* stuff */, UIntPtr inSize, out T value, out UIntPtr outSize);
令我惊讶的是,这在Mono中完美地编译和工作.不幸的是,我的希望很快就被VS不想编译而大幅削减,事实上,泛型方法被禁止作为DllImport目标.但这基本上就是我要找的东西.我试了几件事:
>使用反射基于泛型类型动态生成适当的PInvoke目标:绝对恶劣,并且非常容易出错(也会失去自动封送提供的所有好处)
>按类型重载一个(GetInfoInt,GetInfoStr,..):很快失控
>有一个泛型方法使用GCHandle.Alloc获取指针并将其传递给一个基本的GetInfo,它接受一个IntPtr:工作得很好,但是需要对枚举进行特殊处理,因为很遗憾它们不是blittable(是的,我知道我可以简单地GetInfo< [底层枚举类型]>并强制转换为枚举,但这种方法失败了,因为你无法使用运行时确定的类型调用泛型方法而没有反射)并且字符串也需要特殊的代码
所以我最终认为可能只有三个重载,GetInfoBlittable< T>,GetInfoEnum< T>和GetInfoStr将是代码复制和反射巫术之间的最佳折衷.但是,有更好的方法吗?有没有更好的方法来尽可能接近第一个GetInfo< T>片段?理想情况下无需切换类型.谢谢你的帮助!
FWIW,总的来说我需要使用int,long,uint,ulong,string,string [],UIntPtr,UIntPtr [],枚举类型,blittable结构以及可能的string [] []来完成这项工作.
最佳答案 >当你使用诸如int,long,…之类的结构时,你可以使用Marshal.SizeOf来获得大小和新的IntPtr(& GCHandle.Alloc(…)).
>使用枚举时,您可以使用.GetEnumUnderlyingType()来获取其中的原始类型,并通过反射获取值得到它的名为value__的字段,并在枚举对象上使用GetValue,您将收到它.
>当你使用字符串时,你可以从中取出数组并给它指针.
我做了一个测试,所以你可以理解它:
internal class Program {
public unsafe static int GetInfo(IntPtr t,UIntPtr size) {
if(size.ToUInt32( ) == 4)
Console.WriteLine( *( int* )t.ToPointer( ) );
else //Is it our string?
Console.WriteLine( new string( ( char* )t.ToPointer( ) ) );
return 1;
}
public static unsafe int ManagedGetInfo<T>(T t) {
if (t.GetType().IsEnum) {
var handle = GCHandle.Alloc( t.GetType( ).GetField( "value__" ).GetValue( t ), GCHandleType.Pinned );
var result = GetInfo( handle.AddrOfPinnedObject( ), new UIntPtr( (uint)Marshal.SizeOf( t.GetType().GetEnumUnderlyingType() ) ) );
handle.Free( );
return result;
}
else if (t.GetType().IsValueType) {
var handle = GCHandle.Alloc( t, GCHandleType.Pinned );
var result = GetInfo( handle.AddrOfPinnedObject( ), new UIntPtr( ( uint )Marshal.SizeOf( t ) ) );
handle.Free( );
return result;
}
else if (t is string) {
var str = t as string;
var arr = ( str + "\0" ).ToArray( );
fixed (char *ptr = &arr[0])
{
return GetInfo( new IntPtr( ptr ), new UIntPtr( ( uint )( arr.Length * Marshal.SizeOf( typeof(char) ) ) ) );
}
}
return -1;
}
enum A {
x,y,z
}
private static void Main( ) {
string str = "1234";
int i = 1234;
A a = A.y;
Console.WriteLine( "Should print: " + str );
ManagedGetInfo( str );
Console.WriteLine( "Should print: " + i );
ManagedGetInfo( i );
Console.WriteLine( "Should print: " + ( int )a );
ManagedGetInfo( a );
}
}
哪个输出:
Should print: 1234
1234
Should print: 1234
1234
Should print: 1
1
注意:您需要在项目的属性中启用不安全的代码来测试它.
要制作数组,我会给你提示:
>使用ValueType创建数组,如int,long等.您需要执行与string的传递方法类似的操作.
>要从字符串中生成数组,您需要进行多次分配和一些肮脏的工作. (代码看起来很原生)