Thomas
Thomas

Reputation: 3381

Generic PInvoke in C#

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:

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

Answers (2)

Leandro
Leandro

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

LyingOnTheSky
LyingOnTheSky

Reputation: 2854

  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)

Upvotes: 2

Related Questions