Search code examples
c#pointerslayoutstructfixed

Setting the FieldOffset of a pointer to a structure to the same value as a fixed byte array


i am new to stackoverflow and i would have a question about C# structs and their layout.

Let's assume following structs:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe struct Link
{
    // some primitive data (2 integers for example)
}

[StructLayout(LayoutKind.Explicit, Pack = 1)]
public unsafe struct Node
{
    [FieldOffset(0)]
    public int LinkCount;
    [FieldOffset(4)]
    public Link* Links;
    [FieldOffset(4)]
    private fixed byte _linksData[10 * sizeof(Link)];
}

The reason for that is that i need a blittable type for IO-Performance. I have to deal with very large (and sparse a maximum of 10 links per node) graphs which have several GB in size. The graph is represented as an array of Node-structs. With the setup like above i was hoping of being able to read for example a hundred MB from the graph file into a byte-pointer (which of course points to a byte-buffer) and cast it to a pointer of type Node* which yields very good performance. At first my Node-struct just had 10 separate variables of type Link (Link0, ..., Link10) which worked just fine. But it would be nice to make this configurable at compile-time, which lead to the above Node-struct.

I was hoping, that Links would just point to the same memory location as _linksData since it has the same FieldOffset. But actually the Links pointer always is a null pointer.

So my question is: Is there a way that Links points to the same memory location as _linksData or is there another way to have a fixed sized array of structs embedded into another struct.

Thanks for every answer in advance - Markus

After reading Ben Voigt's post I tryed something similar without the need of changing the struct to a class. The following is how it works for me:

[StructLayout(LayoutKind.Explicit, Pack = 1)]
public unsafe struct Node
{
    [FieldOffset(0)]
    public int LinkCount;
    [FieldOffset(4)]
    private fixed byte _linksData[10 * sizeof(Link)];

    public Link* GetLinks()
    {
       fixed(byte* pLinksData = _linksData)
       {
          return (Link*)pLinksData;
       }
    }
}

Solution

  • I guess that you're not actually trying to store a pointer, just have a correctly-typed way to access the 10 elements. How about:

    [StructLayout(LayoutKind.Explicit, Pack = 1)]
    public unsafe struct Node
    {
        [FieldOffset(0)]
        public int LinkCount;
        [FieldOffset(4)]
        private fixed byte _linksData[10 * sizeof(Link)];
    
        public Link* Links { get { return _linksData; } };
    }
    

    No, wait, .NET supports interior pointers but C# doesn't, so that won't work. You can only have a pointer into a .NET object if you've pinned it or placed it on the stack, and we don't know if that's the case here.

    :(

    Full-on wrapper time:

    public class LinkCollection
    {
        Node peer;
        public LinkCollection(Node node) { peer = node; }
        void CheckIndex(int index) { if (index < 0 || index >= 10) throw new ArgumentOutOfRangeException(); }
        public Link default[int index] {
            get { CheckIndex(index); return peer.GetLink(index); }
            set { CheckIndex(index); peer.SetLink(index, value); }
        }
    }
    
    [StructLayout(LayoutKind.Explicit, Pack = 1)]
    public unsafe class Node
    {
        [FieldOffset(0)]
        public int LinkCount;
        [FieldOffset(4)]
        private fixed byte _linksData[10 * sizeof(Link)];
    
        unsafe Link GetLink(int index) { fixed( Link* plink = (Link*)&_linksData[0] ) return plink[index]; }
        unsafe void SetLink(int index, Link newvalue) { fixed( Link* plink = (Link*)&linksData[0] ) plink[index] = newvalue; }
        public LinkCollection Links { get { return new LinkCollection(this); } };
    }
    

    Note that I had to change Node to a class... p/invoke should still act pretty much the same though.

    If you don't want to do that, extension methods might be an answer.