views:

1650

answers:

3

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);
        }
    }
}
+2  A: 

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.

Mehrdad Afshari
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.
Taylor Leese
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).
Mehrdad Afshari
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.
Mehrdad Afshari
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.
Taylor Leese
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.
Pavel Minaev
Sorry, the above is missing one important word: two fields of reference types (even completely unrelated) can _fully_ overlap (i.e. same offset).
Pavel Minaev
A: 

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);
        }
    }
}
Taylor Leese
A: 

Taylor L I have confirmed your answer, and it is perfect.

But I have a question: How to simulate the pointer and struct array in the union of C++. For example (C++)

union

{

CSTASnapshotDeviceResponseInfo_t *info;

ForwardingInfo_t param[7];

} u

Could you please give the good advice? Thanks a lot.

Exactly the same way he did. C array -> `fixed` C# array. C pointer - C# pointer (remember that C# has pointers with syntax almost same as in C!). C union -> C# struct with `StructLayout.Explicit`.
Pavel Minaev
hi Pavel Minaev, C# fixed array does not support struct array. " fixed ForwardingInfo_t param[7]; " It is wrong.