Using p/invoke to call a C function that creates an array of structures in C#

61 Views Asked by At

I have a function in C that will create an array of structures:

struct Point {
    int x, y;
};

void GetPoints(Point points[], int* size) {
    size = 5;
    points = (Points*) malloc(sizeof(Point) * size);
    // ... fill in each structure with data
}

What is the correct way to call this function from C#, considering the size of points is unknown before the function is called?

1

There are 1 best solutions below

2
SamuraiExx On

There are a few mistakes in that code.

#1. If you want to modify the Point array you will need a pointer of a pointer (Or Point** points), otherwise when you assign points = something, you're just changing the copy.

#2. When you multiply by size, you need to dereference the pointer to, right now you're multiplying by the memory address of the array.

void GetPoints(Point** points, int* size) {
  *size = 5;
  *points = (Point*) malloc(sizeof(Point) * (*size));
  ...
}

// Updating thanks to commentEtienne's comment
void FreePoints(Point* points) {
    free(points);
}

In the C# side, something like this should work:

[StructLayout(LayoutKind.Sequential)]
public struct Point {
    public int x, y;
}

public class NativeMethods {
    [DllImport("YourDllName", CallingConvention = CallingConvention.Cdecl)]
    public static extern void GetPoints(out IntPtr points, out int size);

    [DllImport("YourDllName", CallingConvention = CallingConvention.Cdecl)]
    public static extern void FreePoints(IntPtr points);
}

class Program {
    static void Main() {
        IntPtr pointsPtr;
        int size;

        NativeMethods.GetPoints(out pointsPtr, out size);

        Point[] points = new Point[size];
        for (int i = 0; i < size; i++) {
            points[i] = Marshal.PtrToStructure<Point>(IntPtr.Add(pointsPtr, i * Marshal.SizeOf(typeof(Point))));
        }

        for (int i = 0; i < size; i++) {
            Console.WriteLine($"Point {i}: x={points[i].x}, y={points[i].y}");
        }

        // Free the unmanaged memory
        // Updated thanks to Etienne's comment
        NativeMethods.FreePoints(pointsPtr);
    }
}

References: