Generic PInvoke in C #

I interact with the C API, which has several 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 that returns a bunch of data of various types from one function: the parameter in_sizecontains the size of the buffer passed to value, and the actual number of bytes written is written using a pointer out_size, the caller can then read his data in the buffer value.

I try to call these functions functions from C # beautifully (i.e. without overloading for each other type and not calling them individually, which is inconvenient 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 surprise, this compiled and worked great in Mono. Unfortunately, my hopes were quickly shrunk by VS, not wanting to compile it, and, indeed, generic methods are forbidden as targets of DllImport. But this is basically what I'm looking for. I have tried several things:

  • dynamically generates a suitable target PInvoke based on a generic type using reflection: absolutely brutal and very error prone (also loses all the benefits offered by automatic marshaling)

  • one overloaded by type ( GetInfoInt, GetInfoStr, ..) quickly goes out of control

  • , GCHandle.Alloc GetInfo, IntPtr: , , , , blittable ( , , GetInfo<[underlying enum type]> , , ),

, , , , , GetInfoBlittable<T>, GetInfoEnum<T> GetInfoStr voodoo. ? GetInfo<T>? . !


FWIW, , int, long, uint, ulong, string, string[], UIntPtr, UIntPtr[], , blittable structs , , string[][].

+4
1
  • , int, long,..., Marshal.SizeOf new IntPtr(&GCHandle.Alloc(...)).
  • , .GetEnumUnderlyingType(), , , value__ GetValue enum, .
  • , .

, :

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

. , .

, :

  • ValueType , int, long .. - .
  • string, . ( )
+2

All Articles