Question

I have the following .NET value types:

[StructLayout(LayoutKind.Sequential)]
public struct Date
{
    public UInt16 V;
}

[StructLayout(LayoutKind.Sequential)]
public struct StringPair
{
    public String A;
    public String B;
    public String C;
    public Date D;
    public double V;
}

I have code that is passing a pointer to a value type to unmanaged code, along with offsets discovered by calling System.Runtime.InteropServices.Marshal.OffsetOf. The unmanaged code is populating the Date and double values.

The offsets that are reported for the StringPair struct are exactly what I would expect: 0, 8, 16, 24, 32

I have the following code in a test function:

FieldInfo[] fields = typeof(StringPair).GetFields(BindingFlags.Instance|BindingFlags.Public);

for ( int i = 0; i < fields.Length; i++ )
{
    int offset = System.Runtime.InteropServices.Marshal.OffsetOf(typeof(StringPair), fields[i].Name).ToInt32();

    Console.WriteLine(String.Format(" >> field {0} @ offset {1}", fields[i].Name, offset));
}

Which prints out exactly these offsets.

 >> field A @ offset 0
 >> field B @ offset 8
 >> field C @ offset 16
 >> field D @ offset 24
 >> field V @ offset 32

I then have some test code: foreach (StringPair pair in pairs) { Date d = pair.D; double v = pair.V; ...

Which has the following assembler associated with it in the debugger:

               Date d = pair.D;
0000035d  lea         rax,[rbp+20h] 
00000361  add         rax,20h 
00000367  mov         ax,word ptr [rax] 
0000036a  mov         word ptr [rbp+000000A8h],ax 
00000371  movzx       eax,word ptr [rbp+000000A8h] 
00000378  mov         word ptr [rbp+48h],ax 

                double v = pair.V;
0000037c  movsd       xmm0,mmword ptr [rbp+38h] 
00000381  movsd       mmword ptr [rbp+50h],xmm0 

It is loading the D field at offset 32 (0x20) and the V field at offset 24 (0x38-0x20). The JIT has changed the order around. The Visual Studio debugger shows this inverted order too.

Why!? I've been pulling my hair out try to see where my logic is going wrong. If I swap the order of D and V in the struct then everything works, but this code needs to be able to deal with a plugin architecture where other developers have defined the struct, and they can't be expected to remember arcane layout rules.

Was it helpful?

Solution

The info you get from the Marshal class is only relevant if the type actually gets marshaled. The internal memory layout of a managed structure is not discoverable through any documented means, other than peeking at the assembly code perhaps.

Which means the CLR is free to reorganize the layout of the structure and optimize the packing. Swapping the D and V fields makes your structure smaller due the alignment requirements of a double. It saves 6 bytes on your 64-bit machine.

Not sure why this would be an issue for you, it shouldn't be. Consider Marshal.StructureToPtr() to get the structure laid-out the way you want it.

OTHER TIPS

If you need explicit layout... use explicit layout...

[StructLayout(LayoutKind.Explicit)]
public struct StringPair
{
    [FieldOffset(0)] public String A;
    [FieldOffset(8)] public String B;
    [FieldOffset(16)] public String C;
    [FieldOffset(24)] public Date D;
    [FieldOffset(32)] public double V;
}

Two things:

  • StructLayout(Sequential) does not guarantee packing. You might want to use Pack=1, otherwise 32 and 64bit platforms might be different.

  • and string is a reference, not a pointer. If the string length is always fixed, you might want to use fixed char arrays:

    public struct MyArray // This code must appear in an unsafe block
    {
        public fixed char pathName[128];
    }
    
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top