3

I want to fill a structure with data so I can pass it to my DLL functions. The structures are similar in both C# and C implementations. For instance something like (in C):

typedef struct{
    apple[] apples;
    leaf[] leaves;
} tree;

typedef struct{
    int taste;
} apple;

typedef struct{
    int color;
} leaf;

I want to create a tree structure on C#, fill it with apples and leaves and then send it to a function I have on a DLL.

How?

Chris Vilches
  • 986
  • 2
  • 10
  • 25
  • 1
    In order to do stuff like this, you'd need to marshal the unmanaged data types by specifying a sequential layout kind for the structs, and marshaling the array specifically as a C-style array. Check out the MSDN article "Platform Invoke Tutorial", specifically the section labeled ["Specifying Custom Marshaling for User-Defined Structs"](https://msdn.microsoft.com/en-us/library/aa288468(v=vs.71).aspx#pinvoke_custommarshaling). – Shotgun Ninja Sep 14 '15 at 14:05
  • Check out the question http://stackoverflow.com/questions/2240804/marshalling-what-is-it-and-why-do-we-need-it. – Pontus Gagge Sep 14 '15 at 14:06
  • If someone is more familiar with this process and can write up a full answer, you're welcome to the rep. – Shotgun Ninja Sep 14 '15 at 14:07
  • Make it work in C first before you try to pinvoke. You can't help discovering that you got major problems. Like the C code having no idea how many elements are stored in the struct. And tricky memory management problems, who is responsible for releasing the memory for these arrays? These problems do not get better when you pinvoke. – Hans Passant Sep 14 '15 at 14:25
  • If I understand weel, in cases like your, I used to develop a little C++ dll wrapper. This "technic" allow you to use pointers in C++ and you can simply define methods to add apples and so on in the wrapper. – LPs Sep 14 '15 at 14:29

1 Answers1

1

First of all, I think you should change the C struct a little bit:

typedef struct{
    UINT cApples;
    const apple *pApples;
    UINT cLeaves;
    const leaf *pLeaves;
} tree;

On the C# side:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct Tree
{
    internal uint cApples;
    internal IntPtr pApples;
    internal uint cLeaves;
    internal IntPtr pLeaves;
}

You can define Leaf and Apple similarly. Then you can populate them on C# side as follows:

private Tree CreateTree(Leaf[] leaves, Apple[] apples)
{
    var result = new Tree();
    result.cLeaves = (uint)leaves.Length;
    result.cApples = (uint)apples.Length;
    result.pLeaves = AllocateNativeMemory(leaves);
    result.pApples = AllocateNativeMemory(apples);
    return result;
}

private IntPtr AllocateNativeMemory<T>(T[] elements) where T : struct
{
    int elementSize = Marshal.SizeOf(typeof(T));
    IntPtr result = Marshal.AllocHGlobal(elements.Length * elementSize);

    for (int i = 0; i < elements.Length; i++)
    {
        Marshal.StructureToPtr(
            elements[i], new IntPtr((long)result + i * elementSize), false);
    }

    return result;
}

Now you can pass the result of the CreateTree method to the extern method that calls the C side.

Remark: The allocated memory should be freed; otherwise, your app will leak. If you decide that the C# side is responsible to free the allocated memory, you should do it at the end as follows:

private static void FreeTree(Tree tree)
{
    FreeNativeMemory<Leaf>(tree.pLeaves, tree.cLeaves);
    FreeNativeMemory<Apple>(tree.pApples, tree.cApples);
}

private static void FreeNativeMemory<T>(IntPtr arrayPtr, uint arrayLen) where T : struct
{
    int elementSize = Marshal.SizeOf(typeof(T));
    for (uint i = 0; i < arrayLen; i++)
    {
        Marshal.DestroyStructure(new IntPtr((long)arrayPtr + i * elementSize), typeof(T));
    }

    Marshal.FreeHGlobal(arrayPtr);
}
György Kőszeg
  • 17,093
  • 6
  • 37
  • 65