C# StructLayout.Explicit Question
Asked Answered
E

2

11

I'm trying to understand why the second example below works with no issues, but the first example gives me the exception below. It seems to me that both examples should give an exception based on the description. Can anyone enlighten me?

Unhandled Exception: System.TypeLoadException: Could not load type 'StructTest.OuterType' from assembly 'StructTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' because it contains an object field at offset 0 that is incorrectly aligned or overlapped by a non-object field.
at StructTest.Program.Main(String[] args) Press any key to continue . . .

Example 1

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace StructTest
{
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    struct InnerType
    {
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)]
        char[] buffer;
    }

    [StructLayout(LayoutKind.Explicit)]
    struct OuterType
    {
        [FieldOffset(0)]
        int someValue;

        [FieldOffset(0)]
        InnerType someOtherValue;
    }

    class Program
    {
        static void Main(string[] args)
        {
            OuterType t = new OuterType();
            System.Console.WriteLine(t);
        }
    }
}

Example 2

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace StructTest
{
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    struct InnerType
    {
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)]
        char[] buffer;
    }

    [StructLayout(LayoutKind.Explicit)]
    struct OuterType
    {
        [FieldOffset(4)]
        private int someValue;

        [FieldOffset(0)]
        InnerType someOtherValue;

    }

    class Program
    {
        static void Main(string[] args)
        {
            OuterType t = new OuterType();
            System.Console.WriteLine(t);
        }
    }
}
Endurance answered 25/7, 2009 at 19:5 Comment(0)
S
12

The common language runtime contains a verifier that makes sure the running code (verifiable IL) cannot possibly corrupt memory in the managed environment. This prevents you to declare such a structure in which fields overlap. Basically, your struct contains two data members. One integer (which is 4 bytes) and a native integer (pointer size). On a 32 bit CLR, in which you are probably running your code, the char[] will take 4 bytes so if you put the integer less than four bytes away from the beginning of the struct, you'll have overlapping fields. It's interesting to note that both of your code snippets with fail on a 64 bit runtime, as the pointer size is 8 bytes.

Sauterne answered 25/7, 2009 at 19:24 Comment(7)
I see, so if I wanted to do this correctly so it would work correct on 32 or 64 bit machines I would need to use an offset of 8 and 0 respectively, correct? The problem is that what I really wanted to create was a union and it looks like this will end up not being one if I can't use the same offset.Endurance
Yes. It'll work on x64 CLR with offsets 8 and 0. Note that while the code snippet works, if you are doing this in a real world app, you are probably interfacing with some unmanaged stuff, which would expect the exact offsets. If you set it to 8, it might fail on 32 bit machines (not this snippet itself, but the unmanaged code you're interfacing with).Sauterne
By the way, you can have overlapping fields if they are of unmanaged types, but a .NET array is a reference type (which cannot be considered an unmanaged type per C# spec). So you can't have a reference type as a member of a union in C#. If you really need this, you should consider using things like pointer types as struct members instead of an array.Sauterne
Thanks. I am looking into using an unsafe struct with a fixed char of size 100 in OuterType directly rather than using an InnerType. It sounds like this may be a better approach.Endurance
The rules are even more obscure. Two fields of reference types (even completely unrelated) can overlap, in which case the assembly is valid, but non-verifiable (so it'll run in FullTrust). So you can still mess things up pretty bad by overlapping references to incompatible types. I think the reason for "partial overlap" restriction is because it is absolutely guaranteed to blow GC out of the water. Whereas with full reference overlap GC will do just fine, and it can actually be useful (to save on cast runtime typechecks) when you have a discriminator field to keep track of the type.Peh
Sorry, the above is missing one important word: two fields of reference types (even completely unrelated) can fully overlap (i.e. same offset).Peh
@MehrdadAfshari can you answer my question to clarify here :#43050029Bindle
E
1

I figured I'd respond with the solution I used to create the union -- which was my original intention. I used an unsafe struct and a fixed array and then used a property to interact with the fixed array. I believe this should do what I want.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace StructTest
{

    [StructLayout(LayoutKind.Explicit)]
    unsafe struct OuterType
    {
        private const int BUFFER_SIZE = 100;

        [FieldOffset(0)]
        private int transactionType;

        [FieldOffset(0)]
        private fixed byte writeBuffer[BUFFER_SIZE];

        public int TransactionType
        {
            get { return transactionType; }
            set { transactionType = value; }
        }

        public char[] WriteBuffer
        {
            set
            {
                char[] newBuffer = value;

                fixed (byte* b = writeBuffer)
                {
                    byte* bptr = b;
                    for (int i = 0; i < newBuffer.Length; i++)
                    {
                         *bptr++ = (byte) newBuffer[i];
                    }
                }
            }

            get
            {
                char[] newBuffer = new char[BUFFER_SIZE];

                fixed (byte* b = writeBuffer)
                {
                    byte* bptr = b;
                    for (int i = 0; i < newBuffer.Length; i++)
                    {
                        newBuffer[i] = (char) *bptr++;
                    }
                }

                return newBuffer;
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            OuterType t = new OuterType();
            System.Console.WriteLine(t);
        }
    }
}
Endurance answered 26/7, 2009 at 18:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.