1

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?

elo
  • 489
  • 3
  • 13
  • Why don't you declare the argument as an array of points to begin with? `([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] Point[] points, int size)`. – GSerg Feb 14 '23 at 13:48
  • Does this answer your question? [Is it possible to marshal native struct array to managed array with out a for loop](https://stackoverflow.com/questions/14062563/is-it-possible-to-marshal-native-struct-array-to-managed-array-with-out-a-for-lo) – TJ Rockefeller Feb 14 '23 at 13:52
  • @TJRockefeller - I have done this marshaling an array of struct between C# and Fortran, but the trick is to pass a reference to the first item in the array. – John Alexiou Feb 14 '23 at 15:18

2 Answers2

3

You can use System.Memory<T> type to interact with a block of memory containing a binary data representation. It allows interpreting the data as some struct.

Example:

struct Example
{
    public int IntValue;
    public float FloatValue;
}

byte[] dataBytes = new byte[] { 0x01, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00 };

Memory<byte> dataMemory = new Memory<byte>(dataBytes);

// Cast the memory to a Memory<Example> object
Memory<MyData> dataMemory = MemoryMarshal.Cast<byte, Example>(dataMemory);

MyData data = dataMemory.Span[0];

int intValue = data.IntValue; // intValue == 1
float floatValue = data.FloatValue; // floatValue == 1.0f
Gor Grigoryan
  • 297
  • 1
  • 7
3

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();