From c3fabe1d650df1cf98f68cf76e15b12937c6dd84 Mon Sep 17 00:00:00 2001 From: "Yi Zhang (CLR)" Date: Sat, 29 Apr 2017 09:51:03 -0700 Subject: [PATCH] Bring back Marshal.Read/Write overloads that takes object (dotnet/coreclr#11251) * Bring back desktop version of Marshal.Read/Write APIs that takes object and add a test Commit migrated from https://github.com/dotnet/coreclr/commit/88779dc83763dc199ad1666282316d393473609d --- .../src/System/Runtime/InteropServices/Marshal.cs | 105 ++++++++++++++++--- src/coreclr/src/mscorlib/src/System/StubHelpers.cs | 31 +++++- .../MarshalAPI/ReadWrite/ReadWriteObject.cs | 116 +++++++++++++++++++++ .../MarshalAPI/ReadWrite/ReadWriteObject.csproj | 40 +++++++ 4 files changed, 270 insertions(+), 22 deletions(-) create mode 100644 src/coreclr/tests/src/Interop/MarshalAPI/ReadWrite/ReadWriteObject.cs create mode 100644 src/coreclr/tests/src/Interop/MarshalAPI/ReadWrite/ReadWriteObject.csproj diff --git a/src/coreclr/src/mscorlib/src/System/Runtime/InteropServices/Marshal.cs b/src/coreclr/src/mscorlib/src/System/Runtime/InteropServices/Marshal.cs index 6fb6311..9d9a57b 100644 --- a/src/coreclr/src/mscorlib/src/System/Runtime/InteropServices/Marshal.cs +++ b/src/coreclr/src/mscorlib/src/System/Runtime/InteropServices/Marshal.cs @@ -31,6 +31,7 @@ namespace System.Runtime.InteropServices using System.Diagnostics; using System.Diagnostics.Contracts; using System.Runtime.InteropServices.ComTypes; + using System.StubHelpers; [Serializable] public enum CustomQueryInterfaceMode @@ -406,9 +407,9 @@ namespace System.Runtime.InteropServices //==================================================================== // Read from memory //==================================================================== - public static byte ReadByte([MarshalAs(UnmanagedType.AsAny), In] Object ptr, int ofs) + public static byte ReadByte(Object ptr, int ofs) { - throw new PlatformNotSupportedException(); // https://github.com/dotnet/coreclr/issues/10442 + return ReadValueSlow(ptr, ofs, (IntPtr nativeHome, int offset) => Marshal.ReadByte(nativeHome, offset)); } public static unsafe byte ReadByte(IntPtr ptr, int ofs) @@ -430,9 +431,9 @@ namespace System.Runtime.InteropServices return ReadByte(ptr, 0); } - public static short ReadInt16([MarshalAs(UnmanagedType.AsAny), In] Object ptr, int ofs) + public static short ReadInt16(Object ptr, int ofs) { - throw new PlatformNotSupportedException(); // https://github.com/dotnet/coreclr/issues/10442 + return ReadValueSlow(ptr, ofs, (IntPtr nativeHome, int offset) => Marshal.ReadInt16(nativeHome, offset)); } public static unsafe short ReadInt16(IntPtr ptr, int ofs) @@ -467,9 +468,9 @@ namespace System.Runtime.InteropServices return ReadInt16(ptr, 0); } - public static int ReadInt32([MarshalAs(UnmanagedType.AsAny), In] Object ptr, int ofs) + public static int ReadInt32(object ptr, int ofs) { - throw new PlatformNotSupportedException(); // https://github.com/dotnet/coreclr/issues/10442 + return ReadValueSlow(ptr, ofs, (IntPtr nativeHome, int offset) => Marshal.ReadInt32(nativeHome, offset)); } public static unsafe int ReadInt32(IntPtr ptr, int ofs) @@ -506,7 +507,7 @@ namespace System.Runtime.InteropServices return ReadInt32(ptr, 0); } - public static IntPtr ReadIntPtr([MarshalAs(UnmanagedType.AsAny), In] Object ptr, int ofs) + public static IntPtr ReadIntPtr(Object ptr, int ofs) { #if BIT64 return (IntPtr)ReadInt64(ptr, ofs); @@ -535,7 +536,7 @@ namespace System.Runtime.InteropServices public static long ReadInt64([MarshalAs(UnmanagedType.AsAny), In] Object ptr, int ofs) { - throw new PlatformNotSupportedException(); // https://github.com/dotnet/coreclr/issues/10442 + return ReadValueSlow(ptr, ofs, (IntPtr nativeHome, int offset) => Marshal.ReadInt64(nativeHome, offset)); } public static unsafe long ReadInt64(IntPtr ptr, int ofs) @@ -576,6 +577,41 @@ namespace System.Runtime.InteropServices return ReadInt64(ptr, 0); } + //==================================================================== + // Read value from marshaled object (marshaled using AsAny) + // It's quite slow and can return back dangling pointers + // It's only there for backcompact + // I don't think we should spend time optimizing it + // People should really call the IntPtr overload instead + //==================================================================== + private static unsafe T ReadValueSlow(object ptr, int ofs, Func readValueHelper) + { + // We AV on desktop if passing NULL. So this is technically a breaking change but is an improvement + if (ptr == null) + throw new ArgumentNullException(nameof(ptr)); + + int dwFlags = + (int)AsAnyMarshaler.AsAnyFlags.In | + (int)AsAnyMarshaler.AsAnyFlags.IsAnsi | + (int)AsAnyMarshaler.AsAnyFlags.IsBestFit; + + MngdNativeArrayMarshaler.MarshalerState nativeArrayMarshalerState = new MngdNativeArrayMarshaler.MarshalerState(); + AsAnyMarshaler marshaler = new AsAnyMarshaler(new IntPtr(&nativeArrayMarshalerState)); + + IntPtr pNativeHome = IntPtr.Zero; + + try + { + pNativeHome = marshaler.ConvertToNative(ptr, dwFlags); + return readValueHelper(pNativeHome, ofs); + } + finally + { + marshaler.ClearNative(pNativeHome); + } + } + + //==================================================================== // Write to memory @@ -594,9 +630,9 @@ namespace System.Runtime.InteropServices } } - public static void WriteByte([MarshalAs(UnmanagedType.AsAny), In, Out] Object ptr, int ofs, byte val) + public static void WriteByte(Object ptr, int ofs, byte val) { - throw new PlatformNotSupportedException(); // https://github.com/dotnet/coreclr/issues/10442 + WriteValueSlow(ptr, ofs, val, (IntPtr nativeHome, int offset, byte value) => Marshal.WriteByte(nativeHome, offset, value)); } public static void WriteByte(IntPtr ptr, byte val) @@ -629,9 +665,9 @@ namespace System.Runtime.InteropServices } } - public static void WriteInt16([MarshalAs(UnmanagedType.AsAny), In, Out] Object ptr, int ofs, short val) + public static void WriteInt16(Object ptr, int ofs, short val) { - throw new PlatformNotSupportedException(); // https://github.com/dotnet/coreclr/issues/10442 + WriteValueSlow(ptr, ofs, val, (IntPtr nativeHome, int offset, short value) => Marshal.WriteInt16(nativeHome, offset, value)); } public static void WriteInt16(IntPtr ptr, short val) @@ -681,9 +717,9 @@ namespace System.Runtime.InteropServices } } - public static void WriteInt32([MarshalAs(UnmanagedType.AsAny), In, Out] Object ptr, int ofs, int val) + public static void WriteInt32(Object ptr, int ofs, int val) { - throw new PlatformNotSupportedException(); // https://github.com/dotnet/coreclr/issues/10442 + WriteValueSlow(ptr, ofs, val, (IntPtr nativeHome, int offset, int value) => Marshal.WriteInt32(nativeHome, offset, value)); } public static void WriteInt32(IntPtr ptr, int val) @@ -700,7 +736,7 @@ namespace System.Runtime.InteropServices #endif } - public static void WriteIntPtr([MarshalAs(UnmanagedType.AsAny), In, Out] Object ptr, int ofs, IntPtr val) + public static void WriteIntPtr(Object ptr, int ofs, IntPtr val) { #if BIT64 WriteInt64(ptr, ofs, (long)val); @@ -749,9 +785,9 @@ namespace System.Runtime.InteropServices } } - public static void WriteInt64([MarshalAs(UnmanagedType.AsAny), In, Out] Object ptr, int ofs, long val) + public static void WriteInt64(Object ptr, int ofs, long val) { - throw new PlatformNotSupportedException(); // https://github.com/dotnet/coreclr/issues/10442 + WriteValueSlow(ptr, ofs, val, (IntPtr nativeHome, int offset, long value) => Marshal.WriteInt64(nativeHome, offset, value)); } public static void WriteInt64(IntPtr ptr, long val) @@ -759,6 +795,41 @@ namespace System.Runtime.InteropServices WriteInt64(ptr, 0, val); } + //==================================================================== + // Write value into marshaled object (marshaled using AsAny) and + // propagate the value back + // It's quite slow and is only there for backcompact + // I don't think we should spend time optimizing it + // People should really call the IntPtr overload instead + //==================================================================== + private static unsafe void WriteValueSlow(object ptr, int ofs, T val, Action writeValueHelper) + { + // We AV on desktop if passing NULL. So this is technically a breaking change but is an improvement + if (ptr == null) + throw new ArgumentNullException(nameof(ptr)); + + int dwFlags = + (int)AsAnyMarshaler.AsAnyFlags.In | + (int)AsAnyMarshaler.AsAnyFlags.Out | + (int)AsAnyMarshaler.AsAnyFlags.IsAnsi | + (int)AsAnyMarshaler.AsAnyFlags.IsBestFit; + + MngdNativeArrayMarshaler.MarshalerState nativeArrayMarshalerState = new MngdNativeArrayMarshaler.MarshalerState(); + AsAnyMarshaler marshaler = new AsAnyMarshaler(new IntPtr(&nativeArrayMarshalerState)); + + IntPtr pNativeHome = IntPtr.Zero; + + try + { + pNativeHome = marshaler.ConvertToNative(ptr, dwFlags); + writeValueHelper(pNativeHome, ofs, val); + marshaler.ConvertToManaged(ptr, pNativeHome); + } + finally + { + marshaler.ClearNative(pNativeHome); + } + } //==================================================================== // GetLastWin32Error diff --git a/src/coreclr/src/mscorlib/src/System/StubHelpers.cs b/src/coreclr/src/mscorlib/src/System/StubHelpers.cs index f584ece..716fa06 100644 --- a/src/coreclr/src/mscorlib/src/System/StubHelpers.cs +++ b/src/coreclr/src/mscorlib/src/System/StubHelpers.cs @@ -700,6 +700,17 @@ namespace System.StubHelpers internal static class MngdNativeArrayMarshaler { + // Needs to match exactly with MngdNativeArrayMarshaler in ilmarshalers.h + internal struct MarshalerState + { + IntPtr m_pElementMT; + IntPtr m_Array; + int m_NativeDataValid; + int m_BestFitMap; + int m_ThrowOnUnmappableChar; + short m_vt; + } + [MethodImplAttribute(MethodImplOptions.InternalCall)] static internal extern void CreateMarshaler(IntPtr pMarshalState, IntPtr pMT, int dwFlags); @@ -949,11 +960,21 @@ namespace System.StubHelpers // Cleanup list to be destroyed when clearing the native view (for layouts with SafeHandles). private CleanupWorkList cleanupWorkList; - private static bool IsIn(int dwFlags) { return ((dwFlags & 0x10000000) != 0); } - private static bool IsOut(int dwFlags) { return ((dwFlags & 0x20000000) != 0); } - private static bool IsAnsi(int dwFlags) { return ((dwFlags & 0x00FF0000) != 0); } - private static bool IsThrowOn(int dwFlags) { return ((dwFlags & 0x0000FF00) != 0); } - private static bool IsBestFit(int dwFlags) { return ((dwFlags & 0x000000FF) != 0); } + [Flags] + internal enum AsAnyFlags + { + In = 0x10000000, + Out = 0x20000000, + IsAnsi = 0x00FF0000, + IsThrowOn = 0x0000FF00, + IsBestFit = 0x000000FF + } + + private static bool IsIn(int dwFlags) { return ((dwFlags & (int)AsAnyFlags.In) != 0); } + private static bool IsOut(int dwFlags) { return ((dwFlags & (int)AsAnyFlags.Out) != 0); } + private static bool IsAnsi(int dwFlags) { return ((dwFlags & (int)AsAnyFlags.IsAnsi) != 0); } + private static bool IsThrowOn(int dwFlags) { return ((dwFlags & (int)AsAnyFlags.IsThrowOn) != 0); } + private static bool IsBestFit(int dwFlags) { return ((dwFlags & (int)AsAnyFlags.IsBestFit) != 0); } internal AsAnyMarshaler(IntPtr pvArrayMarshaler) { diff --git a/src/coreclr/tests/src/Interop/MarshalAPI/ReadWrite/ReadWriteObject.cs b/src/coreclr/tests/src/Interop/MarshalAPI/ReadWrite/ReadWriteObject.cs new file mode 100644 index 0000000..a11c10b --- /dev/null +++ b/src/coreclr/tests/src/Interop/MarshalAPI/ReadWrite/ReadWriteObject.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +using CoreFXTestLibrary; + +internal struct BlittableStruct +{ + internal int a; + internal int b; + internal byte c; + internal short d; + internal IntPtr p; +} + +internal struct StructWithReferenceTypes +{ + internal IntPtr ptr; + internal string str; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)] + internal int[] byValArr; +} + +class Test +{ + static int Main(string[] args) + { + TestNegativeCases(); + TestBlittableStruct(); + TestStructWithReferenceType(); + + return 100; + } + + static void TestNegativeCases() + { + Assert.Throws(() => { Marshal.WriteByte(null, 0, 0); }); + Assert.Throws(() => { Marshal.WriteInt16(null, 0, 0); }); + Assert.Throws(() => { Marshal.WriteInt32(null, 0, 0); }); + Assert.Throws(() => { Marshal.WriteInt64(null, 0, 0); }); + Assert.Throws(() => { Marshal.WriteIntPtr(null, 0, IntPtr.Zero); }); + Assert.Throws(() => { Marshal.ReadByte(null, 0); }); + Assert.Throws(() => { Marshal.ReadInt16(null, 0); }); + Assert.Throws(() => { Marshal.ReadInt32(null, 0); }); + Assert.Throws(() => { Marshal.ReadIntPtr(null, 0); }); + } + + static void TestBlittableStruct() + { + Console.WriteLine("TestBlittableStruct"); + + BlittableStruct blittableStruct = new BlittableStruct(); + blittableStruct.a = 200; + blittableStruct.b = 300; + blittableStruct.c = 10; + blittableStruct.d = 123; + blittableStruct.p = new IntPtr(100); + + object boxedBlittableStruct = (object)blittableStruct; + + int offsetOfB = Marshal.OffsetOf("b").ToInt32(); + int offsetOfC = Marshal.OffsetOf("c").ToInt32(); + int offsetOfD = Marshal.OffsetOf("d").ToInt32(); + int offsetOfP = Marshal.OffsetOf("p").ToInt32(); + + Assert.AreEqual(Marshal.ReadInt32(boxedBlittableStruct, 0), 200); + Assert.AreEqual(Marshal.ReadInt32(boxedBlittableStruct, offsetOfB), 300); + Assert.AreEqual(Marshal.ReadByte(boxedBlittableStruct, offsetOfC), 10); + Assert.AreEqual(Marshal.ReadInt16(boxedBlittableStruct, offsetOfD), 123); + Assert.AreEqual(Marshal.ReadIntPtr(boxedBlittableStruct, offsetOfP), new IntPtr(100)); + + Marshal.WriteInt32(boxedBlittableStruct, 0, 300); + Marshal.WriteInt32(boxedBlittableStruct, offsetOfB, 400); + Marshal.WriteByte(boxedBlittableStruct, offsetOfC, 20); + Marshal.WriteInt16(boxedBlittableStruct, offsetOfD, 144); + + Marshal.WriteIntPtr(boxedBlittableStruct, offsetOfP, new IntPtr(500)); + + Assert.AreEqual(((BlittableStruct)boxedBlittableStruct).a, 300); + Assert.AreEqual(((BlittableStruct)boxedBlittableStruct).b, 400); + Assert.AreEqual(((BlittableStruct)boxedBlittableStruct).c, 20); + Assert.AreEqual(((BlittableStruct)boxedBlittableStruct).d, 144); + Assert.AreEqual(((BlittableStruct)boxedBlittableStruct).p, new IntPtr(500)); + } + + static void TestStructWithReferenceType() + { + Console.WriteLine("TestStructWithReferenceType"); + + StructWithReferenceTypes structWithReferenceTypes = new StructWithReferenceTypes(); + structWithReferenceTypes.ptr = new IntPtr(100); + structWithReferenceTypes.str = "ABC"; + structWithReferenceTypes.byValArr = new int[10] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + + object boxedStruct = (object)structWithReferenceTypes; + + int offsetOfStr = Marshal.OffsetOf("str").ToInt32(); + int offsetOfByValArr = Marshal.OffsetOf("byValArr").ToInt32(); + + Assert.AreEqual(Marshal.ReadInt32(boxedStruct, 0), 100); + Assert.AreNotEqual(Marshal.ReadIntPtr(boxedStruct, offsetOfStr), IntPtr.Zero); + Assert.AreEqual(Marshal.ReadInt32(boxedStruct, offsetOfByValArr + sizeof(int) * 2), 3); + + Marshal.WriteInt32(boxedStruct, 0, 200); + Marshal.WriteInt32(boxedStruct, offsetOfByValArr + sizeof(int) * 9, 100); + + Assert.AreEqual(((StructWithReferenceTypes)boxedStruct).ptr, new IntPtr(200)); + Assert.AreEqual(((StructWithReferenceTypes)boxedStruct).byValArr[9], 100); + Assert.AreEqual(((StructWithReferenceTypes)boxedStruct).byValArr[8], 9); + Assert.AreEqual(((StructWithReferenceTypes)boxedStruct).str, "ABC"); + } +} + diff --git a/src/coreclr/tests/src/Interop/MarshalAPI/ReadWrite/ReadWriteObject.csproj b/src/coreclr/tests/src/Interop/MarshalAPI/ReadWrite/ReadWriteObject.csproj new file mode 100644 index 0000000..14fcd2c --- /dev/null +++ b/src/coreclr/tests/src/Interop/MarshalAPI/ReadWrite/ReadWriteObject.csproj @@ -0,0 +1,40 @@ + + + + + Debug + AnyCPU + ReadWriteObject + 2.0 + {F1E66554-8C8E-4141-85CF-D0CD6A0CD0B0} + Exe + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + ..\..\ + + $(DefineConstants);STATIC + + + + + + + + + False + + + + + + + + + + + + {c8c0dc74-fac4-45b1-81fe-70c4808366e0} + CoreCLRTestLibrary + + + + -- 2.7.4