views:

197

answers:

2

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.

+7  A: 

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;
}
Marc Gravell
I could have sworn I had tried that at some point in the last several days .. but it does seem to work now. However, the point is that the sequential *should* work and the framework is reporting offsets that do not match the ones it is actually using. I really don't want users of this plugin architecture to have to specify an attribute and do the math themselves to get this to work.
Rob Walker
+6  A: 

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.

Hans Passant
Thanks -- I had missed the fact that the Marshall.* methods only applied to the marshaled pointer. For performance reasons I was hope to avoid an extra copy of the data, but that looks pretty unavoidable if I want to support arbitrary structs
Rob Walker