C#中的通用PInvoke

我正在与具有以下形式的一些函数的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,它接受一个I​​ntPtr:工作得很好,但是需要对枚举进行特殊处理,因为很遗憾它们不是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的传递方法类似的操作.
>要从字符串中生成数组,您需要进行多次分配和一些肮脏的工作. (代码看起来很原生)

点赞