Skip to content

Commit

Permalink
Add tests for generic types/values with the Marshal APIs (#55533)
Browse files Browse the repository at this point in the history
  • Loading branch information
jkoritzinsky authored Jul 14, 2021
1 parent 8de6d05 commit 3ec6907
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ namespace System.Runtime.InteropServices.Tests.Common
[ComVisible(true)]
public class GenericClass<T> { }

[StructLayout(LayoutKind.Sequential)]
public class SequentialGenericClass<T>
{
public T field;
}

[ComVisible(true)]
public class NonGenericClass { }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,34 @@ public void OffsetOf_NoLayoutPoint_ThrowsArgumentException()
AssertExtensions.Throws<ArgumentException>(null, () => Marshal.OffsetOf<NoLayoutPoint>(nameof(NoLayoutPoint.x)));
}

[Fact]
public void OffsetOf_Field_ValueType_Generic()
{
Assert.Equal((IntPtr)4, Marshal.OffsetOf<Point2<int>>(nameof(Point2<int>.y)));
Assert.Equal((IntPtr)8, Marshal.OffsetOf<Point2<ulong>>(nameof(Point2<int>.y)));
Assert.Equal((IntPtr)4, Marshal.OffsetOf<Point2<float>>(nameof(Point2<int>.y)));
Assert.Equal((IntPtr)8, Marshal.OffsetOf<Point2<double>>(nameof(Point2<int>.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<Point2<char>>(nameof(Point2<int>.y)));
Assert.Equal((IntPtr)1, Marshal.OffsetOf<Point2<byte>>(nameof(Point2<int>.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<Point2Class<int>>(nameof(Point2Class<int>.y)));
Assert.Equal((IntPtr)8, Marshal.OffsetOf<Point2Class<ulong>>(nameof(Point2Class<int>.y)));
Assert.Equal((IntPtr)4, Marshal.OffsetOf<Point2Class<float>>(nameof(Point2Class<int>.y)));
Assert.Equal((IntPtr)8, Marshal.OffsetOf<Point2Class<double>>(nameof(Point2Class<int>.y)));

Assert.Equal((IntPtr)1, Marshal.OffsetOf<Point2Class<char>>(nameof(Point2Class<int>.y)));
Assert.Equal((IntPtr)1, Marshal.OffsetOf<Point2Class<byte>>(nameof(Point2Class<int>.y)));
}

public class NonRuntimeType : Type
{
public override FieldInfo GetField(string name, BindingFlags bindingAttr)
Expand Down Expand Up @@ -495,6 +523,19 @@ struct FieldAlignmentTest_Variant

public short s; // 2 bytes
// 6 bytes of padding
};
}

struct Point2<T>
{
public T x;
public T y;
}

[StructLayout(LayoutKind.Sequential)]
class Point2Class<T>
{
public T x;
public T y;
}
#pragma warning restore 169, 649, 618
}
Original file line number Diff line number Diff line change
Expand Up @@ -138,18 +138,31 @@ public void PtrToStructure_NullStructure_ThrowsArgumentNullException()
AssertExtensions.Throws<ArgumentNullException>("structure", () => Marshal.PtrToStructure<object>((IntPtr)1, null));
}

public static IEnumerable<object[]> PtrToStructure_GenericClass_TestData()
[Fact]
public void PtrToStructure_AutoLayoutClass_ThrowsArgumentException()
{
yield return new object[] { new GenericClass<string>() };
yield return new object[] { new GenericStruct<string>() };
AssertExtensions.Throws<ArgumentException>("structure", () => Marshal.PtrToStructure((IntPtr)1, (object)new NonGenericClass()));
AssertExtensions.Throws<ArgumentException>("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<int> obj = new SequentialGenericClass<int>();
Marshal.PtrToStructure(ptr, obj);
Assert.Equal(i, obj.field);
}

[Fact]
public unsafe void PtrToStructure_GenericLayoutClass()
{
AssertExtensions.Throws<ArgumentException>("structure", () => Marshal.PtrToStructure((IntPtr)1, o));
AssertExtensions.Throws<ArgumentException>("structure", () => Marshal.PtrToStructure<object>((IntPtr)1, o));
int i = 42;
IntPtr ptr = (IntPtr)(&i);
SequentialGenericClass<int> obj = new SequentialGenericClass<int>();
Marshal.PtrToStructure(ptr, (object)obj);
Assert.Equal(i, obj.field);
}

public static IEnumerable<object[]> PtrToStructure_ObjectNotValueClass_TestData()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,34 @@ public void SizeOf_InvalidType_ThrowsArgumentException(Type type, string paramNa
AssertExtensions.Throws<ArgumentException>(paramName, () => Marshal.SizeOf(type));
}

[Fact]
public void SizeOf_GenericStruct_Value_NonGeneric()
{
GenericStruct<int> value = default;
Assert.Equal(8, Marshal.SizeOf((object)value));
}

[Fact]
public void SizeOf_GenericStruct_Value_Generic()
{
GenericStruct<int> value = default;
Assert.Equal(8, Marshal.SizeOf(value));
}

[Fact]
public void SizeOf_GenericClass_Value_NonGeneric()
{
SequentialGenericClass<int> value = new();
Assert.Equal(4, Marshal.SizeOf((object)value));
}

[Fact]
public void SizeOf_GenericClass_Value_Generic()
{
SequentialGenericClass<int> value = new();
Assert.Equal(4, Marshal.SizeOf(value));
}

public struct TestStructWithEnumArray
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
Expand Down

0 comments on commit 3ec6907

Please sign in to comment.