Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Return list of points (x,y,z) from C to C# using PInvoke

Tags:

c

c#

pinvoke

I need to return a list of points i have from a C dll to a C# application using PInvoke. These are points in 3 dimensions [x,y,z]. The number of points varies by what kind of model it is. In C i handle this a linked list of structs. But I don't see how i can pass this on to C#.

The way I see it, I have to return a flexible two dimensional array, probably in a struct.

Any suggestions to how this can be done? Both ideas on how to return it in C and how to access it in C# are highly appreciated.

like image 479
user978281 Avatar asked Dec 25 '11 00:12

user978281


1 Answers

A linked list of structs could be passed back, but it would be quite a hassle to deal with, as you would have to write code to loop through the pointers, reading and copying the data from native memory into managed memory space. I would recommend a simple array of structs instead.

If you have a C struct like the following (assuming 32-bit ints)...

struct Point
{
    int x;
    int y;
    int z;
}

... then you'd represent it nearly the same way in C#:

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

Now to pass an array back, it would be easiest to have your native code allocate the array and pass it back as a pointer, along with another pointer specifying the size in elements.

Your C prototype might look like this:

// Return value would represent an error code
// (in case something goes wrong or the caller
// passes some invalid pointer, e.g. a NULL).
// Caller must pass in a valid pointer-to-pointer to
// capture the array and a pointer to capture the size
// in elements.
int GetPoints(Point ** array, int * arraySizeInElements);

The P/Invoke declaration would then be this:

[DllImport("YourLib.dll")]
static extern int GetPoints(
    [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] out Point[] array,
    out int arraySizeInElements);

The MarshalAs attribute specifies that the array should be marshaled using the size specified in the second parameter (you can read more about this at MSDN, "Default Marshaling for Arrays").

If you use this approach, note that you must use CoTaskMemAlloc to allocate the native buffer as this is what the .NET marshaler expects. Otherwise, you will get memory leaks and/or other errors in your application.

Here is a snippet from the simple example I compiled while verifying my answer:

struct Point
{
    int x;
    int y;
    int z;
};

extern "C"
int GetPoints(Point ** array, int * arraySizeInElements)
{
    // Always return 3 items for this simple example.
    *arraySizeInElements = 3;

    // MUST use CoTaskMemAlloc to allocate (from ole32.dll)
    int bytesToAlloc = sizeof(Point) * (*arraySizeInElements);
    Point * a = static_cast<Point *>(CoTaskMemAlloc(bytesToAlloc));
    *array = a;

    Point p1 = { 1, 2, 3 };
    a[0] = p1;

    Point p2 = { 4, 5, 6 };
    a[1] = p2;

    Point p3 = { 7, 8, 9 };
    a[2] = p3;

    return 0;
}

The managed caller can then deal with the data very simply (in this example, I put all the interop code inside a static class called NativeMethods):

NativeMethods.Point[] points;
int size;
int result = NativeMethods.GetPoints(out points, out size);
if (result == 0)
{
    Console.WriteLine("{0} points returned.", size);
    foreach (NativeMethods.Point point in points)
    {
        Console.WriteLine("({0}, {1}, {2})", point.x, point.y, point.z);
    }
}
like image 91
bobbymcr Avatar answered Nov 09 '22 07:11

bobbymcr