CsWin32: Could not load type "that is incorrectly aligned or overlapped by a non-object field"

Actual behavior

When I import the PROPVARIANT type, the type is not declared property and throws a TypeLoadException when referenced. The error message is “Could not load type __Anonymous_e__Union from assembly ‘…’ because it contains an object field at offset 0 that is incorrectly aligned or overlapped by a non-object field.”

Expected behavior

Type should work.

Repro steps

  1. NativeMethods.txt content:
PROPVARIANT
IPropertyStore
SHGetPropertyStoreForWindow
  1. NativeMethods.json content:
{
    "$schema": "https://aka.ms/CsWin32.schema.json",
    "className": "NativeMethods",
    "namespace": "Windows.Win32",
    "wideCharOnly": true
}
  1. Any of your own code that should be shared?

This is the class that references the broken type:

FormShellProperties.cs

Context

  • CsWin32 version: 0.1.422-beta
  • Win32Metadata version: not explicitly set
  • Target Framework: net5.0-windows
  • LangVersion: 9

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 1
  • Comments: 18 (16 by maintainers)

Commits related to this issue

Most upvoted comments

We should probably keep this active to track the bug when allowMarshaling: true, yes?

That sounds like support for my prior comment, that I emit interop types that don’t activate the .NET marshaler

This would just be defining 100% blittable data types (which is what TerraFX.Interop.Windows does). Anything else is a “convenience wrapper” on top of this that deals with things like pinning, marshalling, etc (which is what DllImport Source Generator is responsible for generating from a given non-blittable interop signature).

But maybe there’s an opportunity at least for me to generate some methods on the ITypeComp struct to make it easier to safely obtain an ITypeComp interface instance based on it, complete with a ReleaseRef call on the original pointer.

The way I handle this in TerraFX.Interop.Windows: https://source.terrafx.dev/#TerraFX.Interop.Windows/Windows/um/oaidl/ITypeComp.cs,5a439f56db1bf1c9

There is the base struct:

public unsafe partial struct ITypeComp : ITypeComp.Interface
{
    public void** lpVtbl;

    // ...

Various helper methods exposing the VTBL members and account for ABI differences (returning HRESULT isn’t ABI compatible, so you’ll note that the fnptr returns int and there is an implicit conversion to the user friendly type):

    // ...

    public HRESULT QueryInterface(Guid* riid, void** ppvObject)
    {
        return ((delegate* unmanaged<ITypeComp*, Guid*, void**, int>)(lpVtbl[0]))((ITypeComp*)Unsafe.AsPointer(ref this), riid, ppvObject);
    }

    // ...

An interface defining the required exposed members and the inheritance heirarchy:

    // ...

    public interface Interface : IUnknown.Interface
    {
        HRESULT Bind(ushort* szName, uint lHashVal, ushort wFlags, ITypeInfo** ppTInfo, DESCKIND* pDescKind, BINDPTR* pBindPtr);
        HRESULT BindType(ushort* szName, uint lHashVal, ITypeInfo** ppTInfo, ITypeComp** ppTComp);
    }

   // ...

and finally a VTBL declaration for convenience:

    // ...

    public partial struct Vtbl<TSelf>
        where TSelf : unmanaged, Interface
    {
        public delegate* unmanaged<TSelf*, Guid*, void**, int> QueryInterface;
        public delegate* unmanaged<TSelf*, uint> AddRef;
        public delegate* unmanaged<TSelf*, uint> Release;
        public delegate* unmanaged<TSelf*, ushort*, uint, ushort, ITypeInfo**, DESCKIND*, BINDPTR*, int> Bind;
        public delegate* unmanaged<TSelf*, ushort*, uint, ITypeInfo**, ITypeComp**, int> BindType;
    }
}

All of these combined allow robust and convenient access/usability of the types and data, even while being unsafe. There are various attributs like VtblIndex and NativeTypeName which track “lost metadata” represented in C/C++ and which are stripped in Release builds.

I can’t change the memory layout of the struct so they don’t overlap either as that would break interop with the native code. So I’m not sure what you’re proposing here.

The data is already not blittable and so is already incompatible with native code. The built-in marshalling system isn’t really recommended for “new code”, the DllImport Source Generator is going to be the recommended thing moving forward.

But in either case, the built-in marshalling system can’t support such a definition anyways because the type system fundamentally doesn’t support it. You will have to define and implement manual marshalling here if your type is both a union and that union contains any reference types.

You cannot union a value and reference type. You really shouldn’t union incompatible reference types (its akin to Unsafe.As<T>(object obj) and is undefined behavior when obj isn’t actually a T).

At a minimum you’re going to need to break it apart so that the value and reference types aren’t overlapping, and you’ll need to handle that as part of the marshalling logic.

Note how one field is exactly 4 bytes in length while the other is pointer-sized. Is that really a stable layout, considering various CPU architectures?

@AArnott Looking around on the interweb, VC compiler & GCC appear to pick the size of the largest union field, ie the size of that union will always be pointer size (before factoring in padding).

That did it! Thanks!