Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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