E-rich
E-rich

Reputation: 9521

Simple union conversion example - C to C#

I am trying to use a DLL written in C in a C# application. I've created a simplified example that replicates the issue I am having.

The C code below creates an array of struct data's and assigns the array pointer to the array parameter passed to the get_data() function. The C# code is supposed to be the boilerplate code needed for marshalling the struct to be used in C#, but is presenting issues for me.

C code

#include <stdint.h>
#include <stdlib.h>
#include <string.h>

struct data {
    int32_t value;
    union {
        struct person {
            uint8_t first[10];
            uint8_t last[10];
        } person;
        struct number {
            int32_t imaginary;
            int32_t real;
        } number;
    } type;
};

int get_data(int count, struct data ***array)
{
    int i;

    /* allocate pointers */
    *array = calloc(count, sizeof(struct data*));
    if (*array == NULL)
        return 1;

    for (i = 0; i < count; i++) {
        /* allocate data struct */
        struct data *data = calloc(1, sizeof(struct data));
        if (data == NULL)
            return 2;

        if ((i % 2) == 0) {
            /* if even, its human */
            data->value = i;
            memcpy(data->type.person.first, "john", 4);
            memcpy(data->type.person.last, "doe", 3);
        } else {
            /* if odd its a number */
            data->value = i;
            data->type.number.imaginary = -1;
            data->type.number.real = i + 1;
        }

        (*array)[i] = data;
    }

    return 0;
}

C# code

[DllImport("libdata.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern Int32 get_data(Int32 count, ref IntPtr array);

[StructLayout(LayoutKind.Sequential)]
public struct Person
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
    public String first;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
    public String last;
}

[StructLayout(LayoutKind.Sequential)]
public struct Number
{
    public Int32 imaginary;
    public Int32 real;
}

[StructLayout(LayoutKind.Explicit)]
public struct TypeUnion
{
    [FieldOffset(0)]
    public Person person;
    [FieldOffset(0)]
    public Number number;
}

[StructLayout(LayoutKind.Sequential)]
public struct Data
{
    public Int32 value;
    public TypeUnion type;
}

Right now when I run my test program I get an exception:

System.TypeLoadException was unhandled
  Message=Could not load type 'WpfRibbonApplication1.TypeUnion' from assembly 'WpfRibbonApplication1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' because it contains an object field at offset 0 that is incorrectly aligned or overlapped by a non-object field.

I have tried several different ways of marshalling the Person strings, but get the exception whichever way I try (using this as reference). Am I missing something obvious? Could I get some help to properly read the array created in the C function within my C# application?

Edit (per David Heffernan's comment)

IntPtr arrayPtr = new IntPtr();
int count = 4;
int ret = LibData.get_data(count, ref arrayPtr);
Console.WriteLine("ret=" + ret);

for (int i = 0; i < count; i++)
{
    IntPtr dataPtr = (IntPtr)Marshal.ReadIntPtr(arrayPtr) + (i * Marshal.SizeOf(typeof(IntPtr)));
    Data data = (Data)Marshal.PtrToStructure(dataPtr, typeof(Data));
    Console.WriteLine("value=" + data.value);
    if ((i % 2) == 0)
    {
        // even is human
        Console.WriteLine("first=" + data.type.first);
        Console.WriteLine("last=" + data.type.last);
    }
    else
    {
        // odd is number
        Console.WriteLine("imaginary=" + data.type.imaginary);
        Console.WriteLine("real=" + data.type.real);
    }
    Console.WriteLine("");
}

Upvotes: 1

Views: 917

Answers (1)

David Heffernan
David Heffernan

Reputation: 613352

The error message is telling you that you cannot overlay an object field with a non-object field. You are overlaying a string with an int.

There's no way you are going to get around that using FieldOffset to replicate the native union. As I see it you have two main options:

  1. Stop using a union and include both Person and Number structs in the Data struct.
  2. Continue using a union, but marshal it yourself. Use the Marshal class to read the data in the struct. For example, you'd use Marshal.ReadInt32 to read the integers, Marshal.Copy to read the character arrays and so on.

Upvotes: 1

Related Questions