Reputation: 3381
I'm interfacing with a C API that has a few functions of the following form:
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.
I'm trying to call these kinds of functions from C# nicely (i.e. 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). 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. Unfortunately, my hopes were quickly slashed by VS not wanting to compile it, and, indeed, generic methods are forbidden as DllImport targets. 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)
having one overload by type (GetInfoInt
, GetInfoStr
, ..): quickly gets out of control
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
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. But, is there a better way? Is there a nicer way to get as close as possible to the first GetInfo<T>
snippet? 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[][]
.
Upvotes: 4
Views: 1208
Reputation: 96
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.
Even though Generic P/Invoke is not supported directly, we can still use generic delegates and Marshal.GetDelegateForFunctionPointer
to emulate that.
Unfortunately, GetDelegateForFunctionPointer
doesn't accept generic types, so we need to either:
Invoke GetDelegateForFunctionPointerInternal
using reflection (which is bad, since it's an unsupported implementation detail), OR
Generate a non-generic delegate type from a generic one, which can be done with Expression.GetDelegateType
, like this:
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
. Otherwise, it returns an instance of System.Func
or System.Action
.
Then, after getting the non-generic delegate, you can bind its Invoke
method to an instance of the generic one:
//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:
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);
}
Upvotes: 0
Reputation: 2854
int
, long
, ..., you can use Marshal.SizeOf
to get the size and new IntPtr(&GCHandle.Alloc(...))
..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.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:
ValueType
such as int, long and etc. You need to do something similar to string's delivery method.string
you will need to do multi-allocations and a bit of dirty work. (The code will look quite native)Upvotes: 2