microsoft / win32metadata

Tooling to generate metadata for Win32 APIs in the Windows SDK.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Make more structs AnyCPU-compatible by ignoring non-default StructLayout Packing when it makes no layout difference

AffluentOwl opened this issue · comments

Consider APPBARDATA which has the following two definitions. However, regardless of which of the two Pack values are chosen, the layout is the same on x86. Therefore, we can drop the Pack = 1 annotation, and fall back to the default packing annotation, and increase the AnyCPU API coverage in CsWin32.

Basically, what is the point of assigning a specific Pack value, if it makes no difference to the layout? Is it there "just in case" a new field gets added? Why not change the packing at that point in time if it does not continue to hold true in the future?

This change can extend the existing code which tries to unify the x86 and x64 definitions.

  [SupportedArchitecture(Architecture.X86)]
  [StructLayout(LayoutKind.Sequential, Pack = 1)]
  public struct APPBARDATA2
  {
    public uint cbSize;
    public HWND hWnd;
    public uint uCallbackMessage;
    public uint uEdge;
    public RECT rc;
    public LPARAM lParam;
  }
  
  [SupportedArchitecture(Architecture.X64 | Architecture.Arm64)]
  public struct APPBARDATA3
  {
    public uint cbSize;
    public HWND hWnd;
    public uint uCallbackMessage;
    public uint uEdge;
    public RECT rc;
    public LPARAM lParam;
  }

  {
    APPBARDATA2 a = new();
    byte* addr = (byte*)&a;
    Console.WriteLine($"{(byte*)&a.cbSize - addr} {(byte*)&a.hWnd - addr} {(byte*)&a.uCallbackMessage - addr} {(byte*)&a.uEdge - addr} {(byte*)&a.rc - addr} {(byte*)&a.lParam - addr} {sizeof(APPBARDATA2)}");
  }
  {
    APPBARDATA3 a = new();
    byte* addr = (byte*)&a;
    Console.WriteLine($"{(byte*)&a.cbSize - addr} {(byte*)&a.hWnd - addr} {(byte*)&a.uCallbackMessage - addr} {(byte*)&a.uEdge - addr} {(byte*)&a.rc - addr} {(byte*)&a.lParam - addr} {sizeof(APPBARDATA3)}");
  }
x86 Output
0 4 8 12 16 32 36
0 4 8 12 16 32 36

x64 Output
0 4 12 16 20 36 44
0 8 16 20 24 40 48

Turns out that Packing can leak out beyond just the struct that the attribute is applied to. Therefore, the simplification I proposed will not allow for nested packed structs.

[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct MINIDUMP_INCLUDE_MODULE_CALLBACK1
{
  public ulong BaseOfImage;
}

public struct MINIDUMP_INCLUDE_MODULE_CALLBACK2
{
  public ulong BaseOfImage;
}

public struct Test1
{
  public byte x;
  public MINIDUMP_INCLUDE_MODULE_CALLBACK1 y;
}

public struct Test2
{
  public byte x;
  public MINIDUMP_INCLUDE_MODULE_CALLBACK2 y;
}
  
{
  Test1 a = new();
  byte* addr = (byte*)&a;
  Console.WriteLine($"{(byte*)&a.x - addr} {(byte*)&a.y - addr} {sizeof(Test1)}");
}

{
  Test2 a = new();
  byte* addr = (byte*)&a;
  Console.WriteLine($"{(byte*)&a.x - addr} {(byte*)&a.y - addr} {sizeof(Test2)}");
}
0 4 12
0 8 16