C# C语言中的泛型PInvoke#

C# C语言中的泛型PInvoke#,c#,interop,dllimport,C#,Interop,Dllimport,我正在与一个C API接口,该API具有以下形式的一些函数: int get_info(/* stuff */, size_t in_size, void* value, size_t *out_size); 这是一个著名的C语言习惯用法,用于从单个函数返回一组不同类型的数据:in\u size参数包含传递到value的缓冲区大小,实际写入的字节数通过out\u size指针写入,然后,调用者可以读回值缓冲区中的数据 我试图从C#很好地调用这类函数(也就是说,不必为每种不同的类型创建重载,也不

我正在与一个C API接口,该API具有以下形式的一些函数:

int get_info(/* stuff */, size_t in_size, void* value, size_t *out_size);
这是一个著名的C语言习惯用法,用于从单个函数返回一组不同类型的数据:
in\u size
参数包含传递到
value
的缓冲区大小,实际写入的字节数通过
out\u size
指针写入,然后,调用者可以读回
缓冲区中的数据

我试图从C#很好地调用这类函数(也就是说,不必为每种不同的类型创建重载,也不必单独调用它们,这很难看,而且会引入大量重复代码)。所以我自然而然地尝试了这样的方法:

[DllImport(DllName, EntryPoint = "get_info")]
int GetInfo<T>(/* stuff */, UIntPtr inSize, out T value, out UIntPtr outSize);
 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);
 }
[DllImport(DllName,EntryPoint=“get_info”)]
int GetInfo(/*stuff*/,UIntPtr inSize,out T value,out uintpttr outSize);
令我惊讶的是,它在Mono中编译并工作得非常完美。不幸的是,我的希望很快就被不想编译它的VS扼杀了,事实上,泛型方法被禁止作为DllImport目标。但这基本上就是我要找的。我尝试了几件事:

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

  • 按类型有一个重载(
    GetInfoInt
    GetInfoStr
    ,…):很快就会失控

  • 使用
    GCHandle.Alloc
    获取指针并将其传递到一个基本
    GetInfo
    的通用方法,该方法使用
    IntPtr
    :效果很好,但需要对枚举进行特殊处理,因为遗憾的是,它们不可Blitttable(是的,我知道我可以简单地
    GetInfo
    并强制转换到enum,但这种方法无法达到目的,因为您无法使用运行时确定的类型调用泛型方法而不进行反射),而且字符串还需要特殊的代码

因此,我最终认为可能有三个重载,即
GetInfoBlittable
GetInfoEnum
GetInfoStr
将是代码复制和反射伏都教之间的最佳折衷。但是,有没有更好的方法?有没有更好的方法尽可能接近第一个
GetInfo
代码片段?理想情况下无需切换类型。感谢您的帮助


FWIW,总的来说,我需要使用
int
long
uint
ulong
string
string[]
uintpttr
uintpttr[]
,枚举类型,blittable结构,可能还有
string[]

  • 当您使用结构,例如
    int
    long
    ,…,您可以使用
    Marshal.SizeOf
    获取大小和
    新的IntPtr(&GCHandle.Alloc(…)
  • 使用枚举时,可以使用
    .getEnumUnderlineType()
    获取其中的原始类型,并通过反射获取其名为
    value\uuuuu
    的字段的值,然后在枚举对象上使用
    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 );
        }
    }
    
    注意:您需要在项目的属性中启用不安全代码来测试它

    要制作数组,我将给您一些提示:

  • 要使用
    ValueType
    生成数组,如int、long等,您需要执行类似于string的传递方法的操作
  • 要使用
    string
    创建数组,您需要执行多次分配和一些脏活。(代码看起来非常原生)

  • 由于大多数搜索“通用p/Invoke”的人可能会来这里,我想我可以分享一个有趣的解决方案,它适用于OP的案例,任何案例都需要
    ref
    out
    参数

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

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

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

    • 从泛型委托类型生成非泛型委托类型,这可以通过
      Expression.GetDelegateType
      完成,如下所示:

      [DllImport(DllName, EntryPoint = "get_info")]
      int GetInfo<T>(/* stuff */, UIntPtr inSize, out T value, out UIntPtr outSize);
      
       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);
       }
      
      最后,对于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);
          }
      
      IntPtr GetInfoPtr=GetProcAddress(DllName,“获取信息”);
      delegate int GetInfoDelegate(/*stuff*/,UIntPtr inSize,out T value,out uintpttr outSize);
      int GetInfo(/*stuff*/,UIntPtr inSize,out T value,out uintpttr outSize)
      {
      var getInfo=GetGenericDelegateForFunctionPointer(GetInfoPtr);
      返回getInfo(/*stuff*/,inSize,out value,out outSize);
      }
      
      我担心这是一个重复。显然,框架本身不知道如何将一些字节转换为可以通用提供的所有类型。不知道
      t.GetType().GetField(“value_uuu”).GetValue(t)
      ,这似乎很有用,谢谢。我会尝试一下,然后再给你回复!这很好用,我还可以使用委托为所有此类函数编写一次,这很酷。谢谢你的回答!