Bring back Marshal.Read/Write overloads that takes object (dotnet/coreclr#11251)
authorYi Zhang (CLR) <yizhang82@users.noreply.github.com>
Sat, 29 Apr 2017 16:51:03 +0000 (09:51 -0700)
committerGitHub <noreply@github.com>
Sat, 29 Apr 2017 16:51:03 +0000 (09:51 -0700)
* 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/coreclr/src/mscorlib/src/System/Runtime/InteropServices/Marshal.cs
src/coreclr/src/mscorlib/src/System/StubHelpers.cs
src/coreclr/tests/src/Interop/MarshalAPI/ReadWrite/ReadWriteObject.cs [new file with mode: 0644]
src/coreclr/tests/src/Interop/MarshalAPI/ReadWrite/ReadWriteObject.csproj [new file with mode: 0644]

index 6fb6311..9d9a57b 100644 (file)
@@ -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<T>(object ptr, int ofs, Func<IntPtr, int, T> 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<T>(object ptr, int ofs, T val, Action<IntPtr, int, T> 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
index f584ece..716fa06 100644 (file)
@@ -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 (file)
index 0000000..a11c10b
--- /dev/null
@@ -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<ArgumentNullException>(() => { Marshal.WriteByte(null, 0, 0); });
+        Assert.Throws<ArgumentNullException>(() => { Marshal.WriteInt16(null, 0, 0); });
+        Assert.Throws<ArgumentNullException>(() => { Marshal.WriteInt32(null, 0, 0); });
+        Assert.Throws<ArgumentNullException>(() => { Marshal.WriteInt64(null, 0, 0); });
+        Assert.Throws<ArgumentNullException>(() => { Marshal.WriteIntPtr(null, 0, IntPtr.Zero); });
+        Assert.Throws<ArgumentNullException>(() => { Marshal.ReadByte(null, 0); });
+        Assert.Throws<ArgumentNullException>(() => { Marshal.ReadInt16(null, 0); });
+        Assert.Throws<ArgumentNullException>(() => { Marshal.ReadInt32(null, 0); });
+        Assert.Throws<ArgumentNullException>(() => { 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<BlittableStruct>("b").ToInt32();
+        int offsetOfC = Marshal.OffsetOf<BlittableStruct>("c").ToInt32();
+        int offsetOfD = Marshal.OffsetOf<BlittableStruct>("d").ToInt32();
+        int offsetOfP = Marshal.OffsetOf<BlittableStruct>("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<StructWithReferenceTypes>("str").ToInt32();
+        int offsetOfByValArr = Marshal.OffsetOf<StructWithReferenceTypes>("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 (file)
index 0000000..14fcd2c
--- /dev/null
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <AssemblyName>ReadWriteObject</AssemblyName>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{F1E66554-8C8E-4141-85CF-D0CD6A0CD0B0}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+
+    <DefineConstants>$(DefineConstants);STATIC</DefineConstants>
+  </PropertyGroup>
+  <!-- Default configurations to help VS understand the configurations -->
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+  </PropertyGroup>
+  <ItemGroup>
+    <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+      <Visible>False</Visible>
+    </CodeAnalysisDependentAssemblyPaths>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="ReadWriteObject.cs" />
+    <Compile Include="..\..\common\Assertion.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\..\..\Common\CoreCLRTestLibrary\CoreCLRTestLibrary.csproj">
+      <Project>{c8c0dc74-fac4-45b1-81fe-70c4808366e0}</Project>
+      <Name>CoreCLRTestLibrary</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+</Project>