I am trying to fill structure containing array from native library to C#. My structure contains two int variables and array of elements which are structures of two integers.
Issue is that variables inside array are received correctly but variables in the outer structure are received as 0. If I initalize them with different values on the C# side they come unchanged.
C interface and implementation:
extern "C" {
struct Element
{
int id;
int uid;
};
struct ElementsGrid
{
int width;
int height;
Element* elements;
};
__declspec(dllexport) int gridSize()
{
return 20;
}
__declspec(dllexport) void getGrid(ElementsGrid* grid)
{
grid->width = 5;
grid->height = 4;
for (int w = 0; w < grid->width; ++w) {
for (int h = 0; h < grid->height; ++h) {
int index = w * grid->height + h;
grid->elements[index].uid = index;
grid->elements[index].id = 42;
}
}
}
} // extern "C"
C# part
[StructLayout(LayoutKind.Sequential)]
public struct Element
{
public int id;
public int uid;
}
[StructLayout(LayoutKind.Sequential)]
public struct ElementsGrid
{
public int width;
public int height;
public Element[] elements;
}
//...
[DllImport("my_plugin")]
static extern void getGrid([In,Out] ElementsGrid ptr);
//...
void getGrid()
{
int field_size = gridSize();
ElementsGrid grid = new ElementsGrid
{
elements = new Element[field_size]
};
getGrid(grid);
print(grid.width); // 0 here. Unchanged
print(grid.height); // 0 here. Unchanged
for (int row = 0; row < 4; row++) {
for (int column = 0; column < 5; column++) {
int index = row * 5 + column;
Element element = grid.elements[index];
print(element.id); // 42. Correct value
print(element.uid); // ==index. Correct value
}
}
}
This is totally possible, but your code has some flaws. The getGrid method in C++ is expecting a pointer to an instance of ElementsGrid, whereas you're trying to pass the instance, not a pointer to it. The other issue is that the definition of ElementsGrid in C++ differs from the equivalent in C#; C++ wants a pointer to Element, whereas C# has an array of element.
I didn't change your C++ code at all, but managed to get this working as follows.
The structures:
[StructLayout(LayoutKind.Sequential)]
public struct Element
{
public int id;
public int uid;
}
[StructLayout(LayoutKind.Sequential)]
public struct ElementsGrid
{
public int width;
public int height;
public IntPtr elements; // Note that this is now a pointer, not an array of Element
}
The getGrid method works when I try it like this:
public static void getGrid()
{
int field_size = gridSize();
ElementsGrid grid = new ElementsGrid()
{
width = field_size,
height = field_size,
elements = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Element)) * field_size * field_size) // Allocate enough memory to the pointer for the C++ method to populate it
};
IntPtr gridPtr = Marshal.AllocHGlobal(Marshal.SizeOf(grid)); // Allocate enough memory to a pointer to hold the contents of the ElementsGrid instance
try
{
Marshal.StructureToPtr<ElementsGrid>(grid, gridPtr, false); // Fill the pointer
getGrid(gridPtr); // Pass the pointer to C++
grid = Marshal.PtrToStructure<ElementsGrid>(gridPtr); // Re-assign grid to the contents of the pointer
Element[] elements = GetElements(grid.elements, grid.width * grid.height); // Read the array from the grid.elements pointer using the new method below
Console.WriteLine("Grid Width: " + grid.width.ToString()); // Works
Console.WriteLine("Grid Height: " + grid.height.ToString()); // Works
for (int row = 0; row < grid.height; row++)
{
for (int column = 0; column < grid.width; column++)
{
int index = row * 5 + column;
Element element = elements[index];
Console.WriteLine("Element id: " + element.id.ToString()); // Works
Console.WriteLine("Element uid: " + element.uid.ToString()); // Works
}
}
}
finally
{
// Always clear up the pointers in a finally block
Marshal.FreeHGlobal(grid.elements);
Marshal.FreeHGlobal(gridPtr);
}
}
The new method to read the grid.elements pointer into an array of Element. This is necessary as you can't marshall a pointer to an array of structure without reading the array directly.
private static Element[] GetElements (IntPtr elementsPtr, int length)
{
int sizeInBytes = Marshal.SizeOf(typeof(Element)); // Get the size of one Element
Element[] output = new Element[length]; // Create an array of Element with the right size
for (int elCount = 0; elCount < length; elCount++)
{
IntPtr elPtr = new IntPtr((elementsPtr.ToInt64() + (elCount * sizeInBytes))); // Read the pointer + offset from the element count
output[elCount] = (Element)Marshal.PtrToStructure(elPtr, typeof(Element)); Assign the element to the contents of the pointer
}
return output;
}
Also, with a blank canvas, you could achieve this in a totally different way by changing your code in C++ to accept the row and column count and return a pointer to the ElementsGrid struture. The code in C# could pass in the relevent row and column count and read the data from the pointer. That would save creating ElementsGrid in C#, getting a pointer to it then passing this to your C++ code.
Hope this helps.