terrafx / terrafx.interop.windows

Interop bindings for Windows.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

`MEMORY_BASIC_INFORMATION` is not a valid portable definition

nike4613 opened this issue · comments

The MEMORY_BASIC_INFORMATION definition is not portable between 32- and 64-bit. The PartitionId field is only present in Win64, and causes the layout to be incorrect on 32-bit, where that field does not exist, and there is no padding there.

The C definition:

typedef struct _MEMORY_BASIC_INFORMATION {
    PVOID BaseAddress;
    PVOID AllocationBase;
    DWORD AllocationProtect;
#if defined (_WIN64)
    WORD   PartitionId;
#endif
    SIZE_T RegionSize;
    DWORD State;
    DWORD Protect;
    DWORD Type;
} MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION;

The *32 and *64 variants also shed light on this problem:

typedef struct _MEMORY_BASIC_INFORMATION32 {
    DWORD BaseAddress;
    DWORD AllocationBase;
    DWORD AllocationProtect;
    DWORD RegionSize;
    DWORD State;
    DWORD Protect;
    DWORD Type;
} MEMORY_BASIC_INFORMATION32, *PMEMORY_BASIC_INFORMATION32;

typedef struct DECLSPEC_ALIGN(16) _MEMORY_BASIC_INFORMATION64 {
    ULONGLONG BaseAddress;
    ULONGLONG AllocationBase;
    DWORD     AllocationProtect;
    DWORD     __alignment1;
    ULONGLONG RegionSize;
    DWORD     State;
    DWORD     Protect;
    DWORD     Type;
    DWORD     __alignment2;
} MEMORY_BASIC_INFORMATION64, *PMEMORY_BASIC_INFORMATION64;

Notice how neither of these variants have PartitionId, though MEMORY_BASIC_INFORMATION64 has an explicit alignment field in its place. For 32-bit targets, MEMORY_BASIC_INFORMATION should be equivalent to MEMORY_BASIC_INFORMATION32, and on 64-bit targets, it should be equivalent to MEMORY_BASIC_INFORMATION64. The current definition is not.

A correct definition would be this:

public unsafe partial struct MEMORY_BASIC_INFORMATION
{
    [NativeTypeName("PVOID")]
    public void* BaseAddress;

    [NativeTypeName("PVOID")]
    public void* AllocationBase;

    [NativeTypeName("DWORD")]
    public uint AllocationProtect;

    // The automatic padding to align RegionSize fills all the space PartitionId would take up,
    // but ensures that this definition is fully portable.

    [NativeTypeName("SIZE_T")]
    public nuint RegionSize;

    [NativeTypeName("DWORD")]
    public uint State;

    [NativeTypeName("DWORD")]
    public uint Protect;

    [NativeTypeName("DWORD")]
    public uint Type;
}

This is one of the few types where it is impossible to define something valid in C# without building per architecture.

The closest thing I could do here is to provide nested structs users must use per platform instead. I can't provide say MEMORY_BASIC_INFORMATION32 and MEMORY_BASIC_INFORMATION64 because the SDK defines those themselves and they're used for a different purpose

Does TerraFX.Interop.Windows still support 32-bit?

If not then it might be worth adding a static constructor like so,

public unsafe partial struct MEMORY_BASIC_INFORMATION
{
    static MEMORY_BASIC_INFORMATION()
    {
        if (sizeof(IntPtr) == 4) throw new PlatformNotSupportedException("Only 64-bit is supported 🥲");
    }
}

Does TerraFX.Interop.Windows still support 32-bit?

It doesn't "not support" 32-bit.

For the vast majority of types exposed they will seamlessly work regardless of target platform. There are a small subset of types, such as MEMORY_BASIC_INFORMATION, which do differ in definition between 32-bit and 64-bit such that problems may creep in. I've tried to handle most of the cases I was aware of, but apparently missed this one in particular