Skip to content

Incorrect PInvoke when parameter has base class with no members #49857

Closed
@dgomsvek

Description

@dgomsvek

Description

PInvoke marshalling is incorrect when there is a parameter whose type has a base class with no members.

I hit this when removing our manual marshaling workaround for #46643, and it feels like the same bug in a different place.

#46643 describes a problem where given:

[StructLayout(LayoutKind.Sequential)]
class MyClassBase { }

[StructLayout(LayoutKind.Sequential)]
class MyClass1: MyClassBase { public int Value; }

The result of Marshal.StructureToPtr<MyClass1>(...) was incorrect, containing a non-collapsed zero-field for the base type.

The fix for that is in net5.0.3, and in testing against 5.0.4, while I do see the fix for Marshal.StructureToPtr<>(), I'm still seeing the same symptoms on a PInvoke parameter:

[DllImport(...)]
static extern void TakeThat(
    MyClassBase pObject,
    UIntPtr sizeOfObject);

Configuration

runtime version: 5.0.4
OS: Windows 20H2 19042.867 64-bit
Arch: x64 PC, 32-bit and 64-bit runtimes
Specific to configuration: unknown

Regression?

net5.0 regression from netfx and netcore 3.1.

The fix for the #46643 is in 5.0.3 PR 46697, but this related issue remains.

Other information

With this native export:

extern "C" N5MNATIVE_API void TakeThat(
	void* pObject,
	size_t sizeOfObject);

And these managed imports:

static class NativeMethods
{
    [DllImport("n5mnative.dll", EntryPoint = "TakeThat", CallingConvention = CallingConvention.Cdecl)]
    public static extern void TakeThatBuffer(
        [In][MarshalAs(UnmanagedType.LPArray)] byte[] pObject,
        [In][MarshalAs(UnmanagedType.SysUInt)] UIntPtr sizeOfObject);

    [DllImport("n5mnative.dll", EntryPoint = "TakeThat", CallingConvention = CallingConvention.Cdecl)]
    public static extern void TakeThatObject(
        [In][MarshalAs(UnmanagedType.LPStruct)] MyClassBase pObject,
        [In][MarshalAs(UnmanagedType.SysUInt)] UIntPtr sizeOfObject);
}

[StructLayout(LayoutKind.Sequential)]
abstract class MyClassBase
{
}

[StructLayout(LayoutKind.Sequential)]
class MyClass1 : MyClassBase
{
    public uint Int2;
}

And this instance:

var myClass1 = new MyClass1
{
    Int2 = 0xA2A2A2A2,
};

Here's the output of some test code on netcoreapp 3.1.13 and net 5.0.4. (net472 is the same as netcoreapp so I won't re-paste.)

Environment.Version: 3.1.13
Environment.Is64BitProcess: False

Bytes from managed Marshal.StructureToPtr<MyClassBase>(myClass1):
        A2 A2 A2 A2

PInvoke NativeMethods.TakeThatBuffer():
TakeThat(): pObject is not nullptr and declared sizeOfObject is 0x00000004.
        A2 A2 A2 A2

PInvoke NativeMethods.TakeThatObject():
TakeThat(): pObject is not nullptr and declared sizeOfObject is 0x00000004.
        A2 A2 A2 A2

PInvoke NativeMethods.TakeThatObject() with size overrun:
TakeThat(): pObject is not nullptr and declared sizeOfObject is 0x00000008.
        A2 A2 A2 A2 00 00 00 00
nvironment.Version: 5.0.4
Environment.Is64BitProcess: False

Bytes from managed Marshal.StructureToPtr<MyClassBase>(myClass1):
        A2 A2 A2 A2

PInvoke NativeMethods.TakeThatBuffer():
TakeThat(): pObject is not nullptr and declared sizeOfObject is 0x00000004.
        A2 A2 A2 A2

PInvoke NativeMethods.TakeThatObject():
TakeThat(): pObject is not nullptr and declared sizeOfObject is 0x00000004.
        00 00 00 00

PInvoke NativeMethods.TakeThatObject() with size overrun:
TakeThat(): pObject is not nullptr and declared sizeOfObject is 0x00000008.
        00 00 00 00 A2 A2 A2 A2

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions