繁体   English   中英

C# 中的通用 PInvoke

[英]Generic PInvoke in C#

我正在与具有以下形式的一些功能的 C API 接口:

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

这是一个著名的 C 习惯用法,从单个函数返回一堆不同类型的数据: in_size参数包含传入value的缓冲区大小,实际写出的字节数通过out_size指针写入,然后调用者可以读回它在value缓冲区中的数据。

我正在尝试从 C# 中很好地调用这些类型的函数(即,无需为每种不同的类型进行重载,也不必单独调用它们,这很丑陋并引入了大量重复代码)。 所以我很自然地尝试了这样的事情:

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

令我惊讶的是,它在 Mono 中编译并完美运行。 不幸的是,由于 VS 不想编译它,我的希望很快就破灭了,事实上,泛型方法被禁止作为 DllImport 目标。 但这基本上是我正在寻找的。 我尝试了几件事:

  • 使用反射基于泛型类型动态生成适当的 PInvoke 目标:绝对糟糕,并且非常容易出错(也失去了自动封送提供的所有好处)

  • 按类型( GetInfoIntGetInfoStr 、 ..)有一个重载:很快就会失控

  • 有一个使用GCHandle.Alloc获取指针的通用方法,并将其传递给一个基本的GetInfo ,它接受一个IntPtr :效果很好,但需要对枚举进行特殊处理,因为遗憾的是它们不是 blittable(是的,我知道我可以简单地GetInfo<[underlying enum type]>并强制转换为枚举,但这种方式违背了目的,因为您无法在没有反射的情况下调用具有运行时确定类型的泛型方法)并且字符串也需要特殊代码

所以我最终认为可能正好有三个重载,即GetInfoBlittable<T>GetInfoEnum<T>GetInfoStr将是代码重复和反射伏都GetInfoStr之间的最佳折衷。 但是,有没有更好的方法? 有没有更好的方法来尽可能接近第一个GetInfo<T>片段? 理想情况下不需要切换类型。 谢谢你的帮助!


FWIW,总的来说,我需要使用intlonguintulongstringstring[]UIntPtrUIntPtr[] 、枚举类型、blittable 结构和可能的string[][]来完成这项工作。

  1. 当您使用intlong 、 ... 等结构时,您可以使用Marshal.SizeOf获取大小和new IntPtr(&GCHandle.Alloc(...))
  2. 当您使用枚举时,您可以使用.GetEnumUnderlyingType()获取其中的原始类型,并通过反射获取名为value__的字段并在枚举对象上使用GetValue获取值,您将收到它。
  3. 当您使用字符串时,您可以从中创建数组并为其提供指针。

我做了一个测试,所以你可以理解它:

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

注意:您需要在项目的属性中启用不安全代码以对其进行测试。

为了制作数组,我会给你提示:

  1. ValueType做成数组,比如int、long 等。你需要做一些类似于string 的传递方法。
  2. 要使数组脱离string您需要进行多次分配和一些肮脏的工作。 (代码看起来很原生)

由于大多数搜索“Generic P/Invoke”的人可能会来到这里,我想我可以分享一个有趣的解决方案,它适用于 OP 的情况,并且任何情况都需要refout参数。

即使不直接支持通用 P/Invoke,我们仍然可以使用通用委托和Marshal.GetDelegateForFunctionPointer来模拟它。

不幸的是, GetDelegateForFunctionPointer不接受泛型类型,因此我们需要:

  • 使用反射调用GetDelegateForFunctionPointerInternal (这很糟糕,因为它是不受支持的实现细节),或者

  • 从泛型委托类型生成非泛型委托类型,可以使用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); }

请注意,如果至少有一个参数是refoutExpression.GetDelegateType只会返回非泛型类型。 否则,它返回System.FuncSystem.Action的实例。

然后,在获得非泛型委托后,您可以将其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");
    }

最后,对于 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