Search code examples
c#c++marshallingwrapperdllimport

Converting mixed type return data from an unmanaged C DLL?


I am writing a wrapper C# DLL to interface with a supplier provided unmanaged DLL written in C. For the most part everything is smooth, however there are some functions that in the provided documentation look like this:

int get_value(int unit, int id, int* value)

My issue is that int* value is an array with a mixture of int's and float's... The documentation states which is which, i.e. that the first value is an int, the second and third are float, etc.. I have some success if i specifically call out the DLL value as int or float. That returns some of the data i'm looking for, but only for the one type. For example, this kinda works:

[DllImport("supplier.dll")]
public static extern int get_value(int unit, int id, float* value)

public static int GetValue(int unit, int id, out float[] value)
{
     float[] value = new float[3];
     int ret = get_value(unit, id, value);
     
     return ret;
}

value is returned with [NaN, value1, value2] because the 0 index is actually an integer and it's not interpreted correctly. I have attempted to use an IntPtr[] as the return value in the unmanaged DLL and then use Marshal.Copy() to get the data from the memory locations, but this hasn't gone well. Is that a promising approach? If so, I'm not sure which overload to use, an example would be very helpful!


Solution

  • int* value is an array with a mixture of int's and float's...

    That's a serious violation of the strict aliasing rule and will invoke undefined behavior. In C you'll have to use a union instead. The documentation should be like this

    union Value
    {
        int i;
        float f;
    };
    
    int get_value(int unit, int id, union Value* value);
    

    Disregarding the original UB in supplier.dll C# you can simulate the similar feature with FieldOffset

    [StructLayout(LayoutKind.Explicit)] 
    public struct Value
    {
        [FieldOffset(0)] public int i;
        [FieldOffset(0)] public float f;
    }
    
    [DllImport("supplier.dll")]
    public static extern int get_value(int unit, int id, out Value[] value);
    
    public static int GetValue(int unit, int id, out Value[] value)
    {
         float[] value = new float[3];
         int ret = get_value(unit, id, value);
         
         return ret;
    }
    

    After obtaining the array, just use the correct field to get the expected value

    var ret = GetValue(unit, id, out Value[] value);
    var f0 = value[0].f;
    var i1 = value[1].i;
    

    If you really want to do that without a union then you must get the float's binary representation like this

    float f0 = value[0];
    int i1 = BitConverter.SingleToInt32Bits(value[1]);
    int i2 = BitConverter.SingleToInt32Bits(value[2]);
    
    value[1] = BitConverter.Int32BitsToSingle(f0 + 0.123f);
    

    But if the array always has 3 items like that then why return an array instead of a struct?

    public struct Values
    {
        public float f0;
        public int i1;
        public int i2;
    }
    
    [DllImport("supplier.dll")]
    public static extern int get_value(int unit, int id, out Values values);
    
    public static int GetValue(int unit, int id, out Values values) {...}