Search code examples
c#pointersstructpinvokeshared-memory

Accessing nested structs in C# with LayoutKind.Explicit


I have an application in C# that needs to share some binary data with Python, and I'm planning to use shared memory (memory mapped file). Thus, I need to have the same binary structure on both sides.

I create a struct in C# (ST_Layer) that has an array of items of another struct (ST_Point). To access those items I have defined a function getPoint that will return a pointer to the position of the requested ST_Point.

using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;

class TestProgram
{
    [StructLayout(LayoutKind.Explicit, Pack = 8)]
    public struct ST_Point
    {
        [FieldOffset(0)] public double X;
        [FieldOffset(8)] public double Y;
    }

    [StructLayout(LayoutKind.Explicit, Pack = 8)]
    public unsafe struct ST_Layer
    {
        [FieldOffset(0)] public ushort nPoints;
        [FieldOffset(8)] public double fHeight;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
        [FieldOffset(16)] public ST_Point[] Points;

        public unsafe ST_Point* getPoint(int n)
        {
            fixed (ST_Layer* thisPtr = &this)
            {
                int ptr = (int)&(thisPtr->Points) + n * ((16 + 3) / 4) * 4;   // Sizeof ST_Point 16
                return (ST_Point*)ptr;
            }
        }
    }

    static void Main(string[] args)
    {
        unsafe
        {
            ST_Layer layer = new ST_Layer();
            layer.nPoints = 3;
            layer.fHeight = 4;
            layer.getPoint(0)->X = 5;
            layer.getPoint(3)->Y = 6;

            for (int i = 0; i < 10; i++)
                Debug.WriteLine("Data [" + i + "] " + layer.getPoint(i)->X + ", " + layer.getPoint(i)->Y + " - ");
            while (true)
                Thread.Sleep(50);
        }
    }
}

I have 2 questions:

  • The previous code works if I compile the code for x86, but not for x64 or 'Any CPU' ('System.AccessViolationException' exception in 'layer.getPoint(0)->X'). Is it an expected behaviour? What can I do to solve this exception?
  • I'm using the getPoint function to access the ST_Point array because I have not seen a better way of accessing it. Is it possible to access it as a normal array (i.e. layer.Points[0].X)? I can't create the array of ST_Point (layer.Points = new ST_Point[10]), as it'll be created outside the ST_Layer, and data will not be passed to Python.

I've seen this and this, but don't know how to access the individual fields of ST_Point.

Thank you for your help.

[Also, thank you R. Martinho Fernandes for your hint regarding the x86/x64 issue]


Solution

  • If you are using a recent version of .NET you can use System.Runtime.CompilerServices.InlineArray to read and write the struct directly from and to the MMF, without any unsafe code.

    The InlineArray attribute allows you to specify a struct which contains a fixed number of elements of a specific value type, which can be accessed via an indexing operation:

    using System;
    using System.IO;
    using System.IO.MemoryMappedFiles;
    using System.Runtime.InteropServices;
    
    namespace Console1;
    
    public static class Program
    {
        static void Main()
        {
            using MemoryMappedFile mmf = MemoryMappedFile.CreateNew("MappedFile", 1000);
    
            var view = mmf.CreateViewAccessor();
    
            using UnmanagedMemoryAccessor accessor = view;
    
            var s = new ST_Layer();
    
            for (int i = 0; i < 10; ++i)
            {
                s.Points[i] = new ST_Point { X = i, Y = -i };
            }
    
            long offset = 500; // Arbitrary offset with the MMF.
    
            view.Write(offset, ref s);
    
            view.Read(offset, out ST_Layer t);
    
            for (int i = 0; i < 10; ++i)
            {
                Console.WriteLine($"{t.Points[i].X}, {t.Points[i].Y}");
            }
        }
    }
    
    [StructLayout(LayoutKind.Explicit)]
    public struct ST_Point
    {
        [FieldOffset(0)] public double X;
        [FieldOffset(8)] public double Y;
    }
    
    // This is the crucial bit: Define a fixed inline array with 10 elements:
    [System.Runtime.CompilerServices.InlineArray(10)]
    public struct TenPoints
    {
        ST_Point _firstElement;
    }
    
    [StructLayout(LayoutKind.Explicit)]
    {
        [FieldOffset(0)] public ushort nPoints;
        [FieldOffset(8)] public double fHeight;
        [FieldOffset(16)] public TenPoints Points;
    }