简体   繁体   English

C# 中的通用 PInvoke

[英]Generic PInvoke in C#

I'm interfacing with a C API that has a few functions of the following form:我正在与具有以下形式的一些功能的 C API 接口:

int get_info(/* stuff */, size_t in_size, void* value, size_t *out_size);

This is a well-known C idiom to return a bunch of data of different types from a single function: the in_size parameter contains the size of the buffer passed into value , and the actual number of bytes written out is written through the out_size pointer, the caller can then read back its data in the value buffer.这是一个著名的 C 习惯用法,从单个函数返回一堆不同类型的数据: in_size参数包含传入value的缓冲区大小,实际写出的字节数通过out_size指针写入,然后调用者可以读回它在value缓冲区中的数据。

I'm trying to call these kinds of functions from C# nicely (ie without making an overload for each different type and having to call them individually, which is butt-ugly and introduces a lot of duplicate code).我正在尝试从 C# 中很好地调用这些类型的函数(即,无需为每种不同的类型进行重载,也不必单独调用它们,这很丑陋并引入了大量重复代码)。 So I naturally tried something like this:所以我很自然地尝试了这样的事情:

[DllImport(DllName, EntryPoint = "get_info")]
int GetInfo<T>(/* stuff */, UIntPtr inSize, out T value, out UIntPtr outSize);

To my amazement, this compiled and worked perfectly in Mono.令我惊讶的是,它在 Mono 中编译并完美运行。 Unfortunately, my hopes were quickly slashed by VS not wanting to compile it, and, indeed, generic methods are forbidden as DllImport targets.不幸的是,由于 VS 不想编译它,我的希望很快就破灭了,事实上,泛型方法被禁止作为 DllImport 目标。 But this is basically what I am looking for.但这基本上是我正在寻找的。 I tried a few things:我尝试了几件事:

  • dynamically generating an appropriate PInvoke target based on a generic type using reflection: absolutely atrocious, and very error-prone (also loses all of the benefits offered by automatic marshaling)使用反射基于泛型类型动态生成适当的 PInvoke 目标:绝对糟糕,并且非常容易出错(也失去了自动封送提供的所有好处)

  • having one overload by type ( GetInfoInt , GetInfoStr , ..): quickly gets out of control按类型( GetInfoIntGetInfoStr 、 ..)有一个重载:很快就会失控

  • having a generic method taking a pointer using GCHandle.Alloc and passing that into a basic GetInfo which takes an IntPtr : works great, but needs special handling for enums because they regrettably aren't blittable (yes, I know I could simply GetInfo<[underlying enum type]> and cast to the enum, but that kind of defeats the purpose because you can't invoke a generic method with a runtime-determined type without reflection) and strings also need special code有一个使用GCHandle.Alloc获取指针的通用方法,并将其传递给一个基本的GetInfo ,它接受一个IntPtr :效果很好,但需要对枚举进行特殊处理,因为遗憾的是它们不是 blittable(是的,我知道我可以简单地GetInfo<[underlying enum type]>并强制转换为枚举,但这种方式违背了目的,因为您无法在没有反射的情况下调用具有运行时确定类型的泛型方法)并且字符串也需要特殊代码

So I eventually thought that maybe having exactly three overloads, being GetInfoBlittable<T> , GetInfoEnum<T> , and GetInfoStr would be the best compromise between code duplication and reflection voodoo.所以我最终认为可能正好有三个重载,即GetInfoBlittable<T>GetInfoEnum<T>GetInfoStr将是代码重复和反射伏都GetInfoStr之间的最佳折衷。 But, is there a better way?但是,有没有更好的方法? Is there a nicer way to get as close as possible to the first GetInfo<T> snippet?有没有更好的方法来尽可能接近第一个GetInfo<T>片段? Ideally without needing to switch over types.理想情况下不需要切换类型。 Thanks for your help!谢谢你的帮助!


FWIW, in total I would need to make this work with int , long , uint , ulong , string , string[] , UIntPtr , UIntPtr[] , enum types, blittable structs, and possibly string[][] . FWIW,总的来说,我需要使用intlonguintulongstringstring[]UIntPtrUIntPtr[] 、枚举类型、blittable 结构和可能的string[][]来完成这项工作。

  1. When you use structs such as int , long , ..., you can use Marshal.SizeOf to get the size and new IntPtr(&GCHandle.Alloc(...)) .当您使用intlong 、 ... 等结构时,您可以使用Marshal.SizeOf获取大小和new IntPtr(&GCHandle.Alloc(...))
  2. When you use enums you can use .GetEnumUnderlyingType() to get the original type in it, and to get the value get it's field named value__ by reflection and use GetValue on the enum object and you will receive it.当您使用枚举时,您可以使用.GetEnumUnderlyingType()获取其中的原始类型,并通过反射获取名为value__的字段并在枚举对象上使用GetValue获取值,您将收到它。
  3. When you use string you can make array out of it and give it's pointer.当您使用字符串时,您可以从中创建数组并为其提供指针。

I made a test, so you could understand it:我做了一个测试,所以你可以理解它:

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

Which outputs:哪些输出:

Should print: 1234
1234
Should print: 1234
1234
Should print: 1
1

NOTE: You will need to enable unsafe code at your project's properties to test it.注意:您需要在项目的属性中启用不安全代码以对其进行测试。

To make arrays I will give you hints:为了制作数组,我会给你提示:

  1. To make array out of ValueType such as int, long and etc. You need to do something similar to string's delivery method.ValueType做成数组,比如int、long 等。你需要做一些类似于string 的传递方法。
  2. To make array out of string you will need to do multi-allocations and a bit of dirty work.要使数组脱离string您需要进行多次分配和一些肮脏的工作。 (The code will look quite native) (代码看起来很原生)

As most people that search for "Generic P/Invoke" will probably come here, I thought I could share an interesting solution, that would work for the OP's case, and any case takes ref or out parameters.由于大多数搜索“Generic P/Invoke”的人可能会来到这里,我想我可以分享一个有趣的解决方案,它适用于 OP 的情况,并且任何情况都需要refout参数。

Even though Generic P/Invoke is not supported directly, we can still use generic delegates and Marshal.GetDelegateForFunctionPointer to emulate that.即使不直接支持通用 P/Invoke,我们仍然可以使用通用委托和Marshal.GetDelegateForFunctionPointer来模拟它。

Unfortunately, GetDelegateForFunctionPointer doesn't accept generic types, so we need to either:不幸的是, GetDelegateForFunctionPointer不接受泛型类型,因此我们需要:

  • Invoke GetDelegateForFunctionPointerInternal using reflection (which is bad, since it's an unsupported implementation detail), OR使用反射调用GetDelegateForFunctionPointerInternal (这很糟糕,因为它是不受支持的实现细节),或者

  • Generate a non-generic delegate type from a generic one, which can be done with Expression.GetDelegateType , like this:从泛型委托类型生成非泛型委托类型,可以使用Expression.GetDelegateType完成,如下所示:

     public static Type GetNonGenericDelegateType<T>() where T: Delegate { var method = typeof(T).GetMethod("Invoke"); var types = method.GetParameters() .Select(p => p.ParameterType) .Concat(new[] { method.ReturnType }) .ToArray(); return Expression.GetDelegateType(types); }

Please note that Expression.GetDelegateType will only return a non-generic type if at least one parameter is ref or out .请注意,如果至少有一个参数是refoutExpression.GetDelegateType只会返回非泛型类型。 Otherwise, it returns an instance of System.Func or System.Action .否则,它返回System.FuncSystem.Action的实例。

Then, after getting the non-generic delegate, you can bind its Invoke method to an instance of the generic one:然后,在获得非泛型委托后,您可以将其Invoke方法绑定到泛型委托的实例:

    //Converts an unmanaged function pointer to a generic delegate of the specified type.
    public static T GetGenericDelegateForFunctionPointer<T>(IntPtr ptr) where T: Delegate
    {
        var delegateType = GetNonGenericDelegateType<T>();
        var delegateInstance = Marshal.GetDelegateForFunctionPointer(ptr, delegateType);
        return (T)Delegate.CreateDelegate(typeof(T), delegateInstance, "Invoke");
    }

Finally, for the OP's case, the code would be reduced to this:最后,对于 OP 的情况,代码将简化为:

    IntPtr GetInfoPtr = GetProcAddress(DllName, "get_info");
    delegate int GetInfoDelegate<T>(/* stuff */, UIntPtr inSize, out T value, out UIntPtr outSize);

    int GetInfo<T>(/* stuff */, UIntPtr inSize, out T value, out UIntPtr outSize)
    {
        var getInfo = GetGenericDelegateForFunctionPointer<GetInfoDelegate<T>>(GetInfoPtr);
        return getInfo(/* stuff */, inSize, out value, out outSize);
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM