C# C语言中的泛型PInvoke#
我正在与一个C API接口,该API具有以下形式的一些函数: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#很好地调用这类函数(也就是说,不必为每种不同的类型创建重载,也不
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
:效果很好,但需要对枚举进行特殊处理,因为遗憾的是,它们不可Blitttable(是的,我知道我可以简单地IntPtr
并强制转换到enum,但这种方法无法达到目的,因为您无法使用运行时确定的类型调用泛型方法而不进行反射),而且字符串还需要特殊的代码GetInfo
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);
最后,对于OP的情况,代码将简化为: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); }
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)