Search code examples
c#marshalling

Effectively convert bytes to array of structures in C#


My C# application interacts with the C application via the "callbacks" mechanism.

On the C side I have Point struct:

struct Point {
  float x, y, z;
};

To transfer points from C to C# I pass my C# delegate callback function pointsCallback to C function getPoints.

typedef void(*PointsCallback)(const Point* points, int size);

void getPoints(PointsCallback callback);
delegate void PointsCallback(IntPtr points, int size);

[DllImport(...)]
static extern void getPoints(PointsCallback callback);

My PointsCallback C# implementation looks like this:

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

// called from c code
void PointsCallbackImpl(IntPtr points, int size) {
  var pointSize = Marshal.SizeOf<Point>();
  var myPoints = new List<Point>();
  for (int i = 0; i < size; ++i) {
    myPoints.Add(Marshal.PtrToStructure<Point>(points + i * pointSize));
  }
  // do something with myPoints
}

The problem is this code is quite slow compared to python's np.array which allows me to save all points as a binary array and interpret them via np.dtype.

Is there any C# analog to np.array behavior, which allows to store everything in binary form and only interpret data as some structs?


Solution

  • I am not sure what 'slow' means in this context, but the example below takes 0.3285 milliseconds, or 328500 nanoseconds to convert 1000 points.

    [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
    static unsafe extern void memcpy(void* dst, void* src, int count);
    
    [StructLayout(LayoutKind.Sequential)]
    struct Point
    {
        public float x, y, z;
    }
    
    static unsafe void PointCallback(IntPtr ptPoints, int size)
    {   
        Point[] points = new Point[size];
        fixed (void* pDest = &points[0])             {
            memcpy(pDest, (void*)ptPoints, Marshal.SizeOf<Point>() * size);
        }
    }
    

    And here is my test:

    Point[] points = new Point[1000];
    for (int i=0; i<1000; i++) {
        points[i] = new Point() { x= i, y = i,z = i };
    }
    
    var ptPoints = GCHandle.Alloc(points, GCHandleType.Pinned);
    PointCallback(ptPoints.AddrOfPinnedObject(), 1000);
    ptPoints.Free();