gpt4 book ai didi

c# - C# 中的通用 PInvoke

转载 作者:行者123 更新时间:2023-11-30 12:27:04 24 4
gpt4 key购买 nike

我正在与具有以下形式的一些函数的 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 目标:绝对残酷,而且非常容易出错(也失去了自动编码(marshal)处理提供的所有好处)

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

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

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


FWIW,总的来说,我需要使用 int 来完成这项工作, long , uint , ulong , string , string[] , UIntPtr , UIntPtr[] 、枚举类型、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 等)制成数组。您需要做一些类似于字符串的传递方法。
  2. 要从string 中创建数组,您需要进行多次分配和一些肮脏的工作。 (代码看起来很原生)

关于c# - C# 中的通用 PInvoke,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26882526/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com