diff --git a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/Common/CommonTypes.cs b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/Common/CommonTypes.cs index 4c7a993138a8a4..6a6cab70b7356b 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/Common/CommonTypes.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/Common/CommonTypes.cs @@ -6,6 +6,12 @@ namespace System.Runtime.InteropServices.Tests.Common [ComVisible(true)] public class GenericClass { } + [StructLayout(LayoutKind.Sequential)] + public class SequentialGenericClass + { + public T field; + } + [ComVisible(true)] public class NonGenericClass { } diff --git a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/OffsetOfTests.cs b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/OffsetOfTests.cs index dd3ccea3e2dc49..d4257da655aa1c 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/OffsetOfTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/OffsetOfTests.cs @@ -224,6 +224,34 @@ public void OffsetOf_NoLayoutPoint_ThrowsArgumentException() AssertExtensions.Throws(null, () => Marshal.OffsetOf(nameof(NoLayoutPoint.x))); } + [Fact] + public void OffsetOf_Field_ValueType_Generic() + { + Assert.Equal((IntPtr)4, Marshal.OffsetOf>(nameof(Point2.y))); + Assert.Equal((IntPtr)8, Marshal.OffsetOf>(nameof(Point2.y))); + Assert.Equal((IntPtr)4, Marshal.OffsetOf>(nameof(Point2.y))); + Assert.Equal((IntPtr)8, Marshal.OffsetOf>(nameof(Point2.y))); + + // [COMPAT] Non-blittable generic types with value-type generic arguments are supported in OffsetOf since they've always been allowed + // and it likely doesn't meet the bar to break back-compat. + Assert.Equal((IntPtr)1, Marshal.OffsetOf>(nameof(Point2.y))); + Assert.Equal((IntPtr)1, Marshal.OffsetOf>(nameof(Point2.y))); + } + + [Fact] + public void OffsetOf_Field_ReferenceType_Generic() + { + // [COMPAT] Generic types with value-type generic arguments are supported in OffsetOf since they've always been allowed + // and it likely doesn't meet the bar to break back-compat. + Assert.Equal((IntPtr)4, Marshal.OffsetOf>(nameof(Point2Class.y))); + Assert.Equal((IntPtr)8, Marshal.OffsetOf>(nameof(Point2Class.y))); + Assert.Equal((IntPtr)4, Marshal.OffsetOf>(nameof(Point2Class.y))); + Assert.Equal((IntPtr)8, Marshal.OffsetOf>(nameof(Point2Class.y))); + + Assert.Equal((IntPtr)1, Marshal.OffsetOf>(nameof(Point2Class.y))); + Assert.Equal((IntPtr)1, Marshal.OffsetOf>(nameof(Point2Class.y))); + } + public class NonRuntimeType : Type { public override FieldInfo GetField(string name, BindingFlags bindingAttr) @@ -495,6 +523,19 @@ struct FieldAlignmentTest_Variant public short s; // 2 bytes // 6 bytes of padding - }; + } + + struct Point2 + { + public T x; + public T y; + } + + [StructLayout(LayoutKind.Sequential)] + class Point2Class + { + public T x; + public T y; + } #pragma warning restore 169, 649, 618 } diff --git a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/PtrToStructureTests.cs b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/PtrToStructureTests.cs index f2a6c2b5d80084..dbd31cfa4adf35 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/PtrToStructureTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/PtrToStructureTests.cs @@ -138,18 +138,31 @@ public void PtrToStructure_NullStructure_ThrowsArgumentNullException() AssertExtensions.Throws("structure", () => Marshal.PtrToStructure((IntPtr)1, null)); } - public static IEnumerable PtrToStructure_GenericClass_TestData() + [Fact] + public void PtrToStructure_AutoLayoutClass_ThrowsArgumentException() { - yield return new object[] { new GenericClass() }; - yield return new object[] { new GenericStruct() }; + AssertExtensions.Throws("structure", () => Marshal.PtrToStructure((IntPtr)1, (object)new NonGenericClass())); + AssertExtensions.Throws("structure", () => Marshal.PtrToStructure((IntPtr)1, new NonGenericClass())); } - [Theory] - [MemberData(nameof(PtrToStructure_GenericClass_TestData))] - public void PtrToStructure_GenericObject_ThrowsArgumentException(object o) + [Fact] + public unsafe void PtrToStructure_GenericLayoutClass_Generic() + { + int i = 42; + IntPtr ptr = (IntPtr)(&i); + SequentialGenericClass obj = new SequentialGenericClass(); + Marshal.PtrToStructure(ptr, obj); + Assert.Equal(i, obj.field); + } + + [Fact] + public unsafe void PtrToStructure_GenericLayoutClass() { - AssertExtensions.Throws("structure", () => Marshal.PtrToStructure((IntPtr)1, o)); - AssertExtensions.Throws("structure", () => Marshal.PtrToStructure((IntPtr)1, o)); + int i = 42; + IntPtr ptr = (IntPtr)(&i); + SequentialGenericClass obj = new SequentialGenericClass(); + Marshal.PtrToStructure(ptr, (object)obj); + Assert.Equal(i, obj.field); } public static IEnumerable PtrToStructure_ObjectNotValueClass_TestData() diff --git a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/SizeOfTests.cs b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/SizeOfTests.cs index 6a009a06437f9a..a91bc349f4cec4 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/SizeOfTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/SizeOfTests.cs @@ -95,6 +95,34 @@ public void SizeOf_InvalidType_ThrowsArgumentException(Type type, string paramNa AssertExtensions.Throws(paramName, () => Marshal.SizeOf(type)); } + [Fact] + public void SizeOf_GenericStruct_Value_NonGeneric() + { + GenericStruct value = default; + Assert.Equal(8, Marshal.SizeOf((object)value)); + } + + [Fact] + public void SizeOf_GenericStruct_Value_Generic() + { + GenericStruct value = default; + Assert.Equal(8, Marshal.SizeOf(value)); + } + + [Fact] + public void SizeOf_GenericClass_Value_NonGeneric() + { + SequentialGenericClass value = new(); + Assert.Equal(4, Marshal.SizeOf((object)value)); + } + + [Fact] + public void SizeOf_GenericClass_Value_Generic() + { + SequentialGenericClass value = new(); + Assert.Equal(4, Marshal.SizeOf(value)); + } + public struct TestStructWithEnumArray { [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]