Generic PInvoke in C#

1.1k Views Asked by At

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[][].

2

There are 2 best solutions below

2
On BEST ANSWER
  1. When you use structs such as int, long, ..., you can use Marshal.SizeOf to get the size and new IntPtr(&GCHandle.Alloc(...)).
  2. When you use enums you can use .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.
  3. When you use string you can make array out of it and give it's pointer.

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:

  1. To make array out of ValueType such as int, long and etc. You need to do something similar to string's delivery method.
  2. To make array out of string you will need to do multi-allocations and a bit of dirty work. (The code will look quite native)
0
On

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);
    }