Better handle marshaling of packed types (#3508)
authorJoshua Peterson <petersonjm1@gmail.com>
Mon, 5 Sep 2016 21:43:55 +0000 (17:43 -0400)
committerZoltan Varga <vargaz@gmail.com>
Mon, 5 Sep 2016 21:43:55 +0000 (23:43 +0200)
* This improves the marshaling for packed classes and class involved in an inheritance hierarchy.
* Similar changes were made in the Unity fork of mono across a number of commits.
* The tests here have been taken from the repo and converted to work as NUnit tests in Mono.

mcs/class/corlib/Test/System.Runtime.InteropServices/MarshalTest.cs
mono/metadata/marshal.c

index d6254137ee0122b4b5008f679181c54239ccc207..d4ca72dc0a94857730af44a9635cffa80624a605 100644 (file)
@@ -822,6 +822,151 @@ namespace MonoTests.System.Runtime.InteropServices
                        int nSize
                );
 #endif
+
+               [StructLayout( LayoutKind.Sequential, Pack = 1 )]
+               public class FourByteStruct
+               {
+                       public UInt16 value1;
+                       public UInt16 value2;
+               }
+
+               [StructLayout( LayoutKind.Sequential, Pack = 1 )]
+               public class ByteArrayFourByteStruct : FourByteStruct
+               {
+                       [MarshalAs( UnmanagedType.ByValArray, SizeConst = 5 )]
+                       public byte[] array;
+               }
+
+               [StructLayout( LayoutKind.Sequential, Pack = 1 )]
+               public class SingleByteStruct
+               {
+                       public byte value1;
+               }
+
+               [StructLayout( LayoutKind.Sequential, Pack = 1 )]
+               public class ByteArraySingleByteStruct : SingleByteStruct
+               {
+                       [MarshalAs( UnmanagedType.ByValArray, SizeConst = 5 )]
+                       public byte[] array1;
+                       public byte value2;
+               }
+
+               [StructLayout( LayoutKind.Sequential, Pack = 1 )]
+               public class ByteArraySingleByteChildStruct : ByteArraySingleByteStruct
+               {
+                       [MarshalAs( UnmanagedType.ByValArray, SizeConst = 5 )]
+                       public byte[] array2;
+               }
+
+               [Test]
+               public void CheckByteArrayFourByteStruct()
+               {
+                       ByteArrayFourByteStruct myStruct = new ByteArrayFourByteStruct
+                       { value1 = 42, value2 = 53, array = Encoding.UTF8.GetBytes( "Hello" ) };
+
+                       byte[] buffer = Serialize (myStruct);
+
+                       UInt16 value1 = BitConverter.ToUInt16 (buffer, 0);
+                       UInt16 value2 = BitConverter.ToUInt16 (buffer, 2);
+                       string array = Encoding.UTF8.GetString (buffer, 4, 5);
+
+                       Assert.AreEqual((UInt16)42, value1);
+                       Assert.AreEqual((UInt16)53, value2);
+                       Assert.AreEqual ("Hello", array);
+               }
+
+               [Test]
+               public void CheckByteArraySingleByteChildStruct()
+               {
+                       ByteArraySingleByteChildStruct myStruct = new ByteArraySingleByteChildStruct
+                       { value1 = 42, array1 = Encoding.UTF8.GetBytes( "Hello" ), value2 = 53,  array2 = Encoding.UTF8.GetBytes( "World" ) };
+
+                       byte[] array = Serialize (myStruct);
+
+                       byte value1 = array [0];
+                       string array1 = Encoding.UTF8.GetString (array, 1, 5);
+                       byte value2 = array [6];
+                       string array2 = Encoding.UTF8.GetString (array, 7, 5);
+
+                       Assert.AreEqual((byte)42, value1);
+                       Assert.AreEqual ("Hello", array1);
+                       Assert.AreEqual((byte)53, value2);
+                       Assert.AreEqual ("World", array2);
+               }
+
+               [StructLayout(LayoutKind.Sequential, Pack = 1)]
+               public struct FiveByteStruct
+               {
+                       public uint uIntField;
+                       public byte byteField;
+               };
+
+               [StructLayout(LayoutKind.Sequential, Pack = 1)]
+               public class Base
+               {
+                       public ushort firstUShortField;
+                       public ushort secondUShortField;
+               }
+
+               [StructLayout(LayoutKind.Sequential, Pack = 1)]
+               public class Derived : Base
+               {
+                       [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
+                       public FiveByteStruct[] arrayField;
+               }
+
+               [Test]
+               public void CheckPtrToStructureWithFixedArrayAndBaseClassFields()
+               {
+                       const int arraySize = 6;
+                       var derived = new Derived
+                       {
+                               arrayField = new FiveByteStruct[arraySize],
+                               firstUShortField = 42,
+                               secondUShortField = 43
+                       };
+
+                       for (var i = 0; i < arraySize; ++i)
+                       {
+                               derived.arrayField[i].byteField = (byte)i;
+                               derived.arrayField[i].uIntField = (uint)i * 10;
+                       }
+
+                       var array = Serialize(derived);
+                       var deserializedDerived = Deserialize<Derived>(array);
+
+                       Assert.AreEqual(derived.firstUShortField, deserializedDerived.firstUShortField, "The firstUShortField differs, which is not expected.");
+                       Assert.AreEqual(derived.secondUShortField, deserializedDerived.secondUShortField, "The secondUShortField differs, which is not expected.");
+
+                       for (var i = 0; i < arraySize; ++i)
+                       {
+                               Assert.AreEqual(derived.arrayField[i].byteField, deserializedDerived.arrayField[i].byteField, string.Format("The byteField at index {0} differs, which is not expected.", i));
+                               Assert.AreEqual(derived.arrayField[i].uIntField, deserializedDerived.arrayField[i].uIntField, string.Format("The uIntField at index {0} differs, which is not expected.", i));
+                       }
+               }
+
+               public static byte[] Serialize( object obj )
+               {
+                       int nTypeSize = Marshal.SizeOf( obj );
+                       byte[] arrBuffer = new byte[nTypeSize];
+
+                       GCHandle hGCHandle = GCHandle.Alloc( arrBuffer, GCHandleType.Pinned );
+                       IntPtr pBuffer = hGCHandle.AddrOfPinnedObject();
+                       Marshal.StructureToPtr( obj, pBuffer, false );
+                       hGCHandle.Free();
+
+                       return arrBuffer;
+               }
+
+               public static T Deserialize<T>(byte[] buffer)
+               {
+                       var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
+                       var pBuffer = handle.AddrOfPinnedObject();
+                       var objResult = (T)Marshal.PtrToStructure(pBuffer, typeof(T));
+                       handle.Free();
+
+                       return objResult;
+               }
        }
 #if !MOBILE
        [ComImport()]
index 599a7c82c13caf7f9a332be3ef11c6e858b558f3..5b290e74947ff5143a963c5a2e891c4d7d71fc3f 100644 (file)
@@ -95,7 +95,7 @@ static void
 emit_struct_conv (MonoMethodBuilder *mb, MonoClass *klass, gboolean to_object);
 
 static void
-emit_struct_conv_full (MonoMethodBuilder *mb, MonoClass *klass, gboolean to_object, MonoMarshalNative string_encoding);
+emit_struct_conv_full (MonoMethodBuilder *mb, MonoClass *klass, gboolean to_object, int offset_of_first_child_field, MonoMarshalNative string_encoding);
 
 static void 
 mono_struct_delete_old (MonoClass *klass, char *ptr);
@@ -1937,15 +1937,26 @@ emit_object_to_ptr_conv (MonoMethodBuilder *mb, MonoType *type, MonoMarshalConv
        }
 }
 
+static int offset_of_first_nonstatic_field(MonoClass *klass)
+{
+       int i;
+       for (i = 0; i < klass->field.count; i++) {
+               if (!(klass->fields[i].type->attrs & FIELD_ATTRIBUTE_STATIC) && !mono_field_is_deleted (&klass->fields[i]))
+                       return klass->fields[i].offset - sizeof (MonoObject);
+       }
+
+       return 0;
+}
+
 static void
 emit_struct_conv_full (MonoMethodBuilder *mb, MonoClass *klass, gboolean to_object,
-                                          MonoMarshalNative string_encoding)
+                                               int offset_of_first_child_field, MonoMarshalNative string_encoding)
 {
        MonoMarshalType *info;
        int i;
 
        if (klass->parent)
-               emit_struct_conv(mb, klass->parent, to_object);
+               emit_struct_conv_full (mb, klass->parent, to_object, offset_of_first_nonstatic_field (klass), string_encoding);
 
        info = mono_marshal_load_type_info (klass);
 
@@ -1953,16 +1964,21 @@ emit_struct_conv_full (MonoMethodBuilder *mb, MonoClass *klass, gboolean to_obje
                return;
 
        if (klass->blittable) {
-               int msize = mono_class_value_size (klass, NULL);
-               g_assert (msize == info->native_size);
+               int usize = mono_class_value_size (klass, NULL);
+               g_assert (usize == info->native_size);
                mono_mb_emit_ldloc (mb, 1);
                mono_mb_emit_ldloc (mb, 0);
-               mono_mb_emit_icon (mb, msize);
+               mono_mb_emit_icon (mb, usize);
                mono_mb_emit_byte (mb, CEE_PREFIX1);
                mono_mb_emit_byte (mb, CEE_CPBLK);
 
-               mono_mb_emit_add_to_local (mb, 0, msize);
-               mono_mb_emit_add_to_local (mb, 1, msize);
+               if (to_object) {
+                       mono_mb_emit_add_to_local (mb, 0, usize);
+                       mono_mb_emit_add_to_local (mb, 1, offset_of_first_child_field);
+               } else {
+                       mono_mb_emit_add_to_local (mb, 0, offset_of_first_child_field);
+                       mono_mb_emit_add_to_local (mb, 1, usize);
+               }
                return;
        }
 
@@ -2166,7 +2182,7 @@ emit_struct_conv_full (MonoMethodBuilder *mb, MonoClass *klass, gboolean to_obje
 static void
 emit_struct_conv (MonoMethodBuilder *mb, MonoClass *klass, gboolean to_object)
 {
-       emit_struct_conv_full (mb, klass, to_object, (MonoMarshalNative)-1);
+       emit_struct_conv_full (mb, klass, to_object, 0, (MonoMarshalNative)-1);
 }
 
 static void
@@ -6342,7 +6358,7 @@ emit_marshal_array (EmitMarshalContext *m, int argnum, MonoType *t,
                                mono_mb_emit_stloc (mb, 1);
 
                                /* emit valuetype conversion code */
-                               emit_struct_conv_full (mb, eklass, FALSE, eklass == mono_defaults.char_class ? encoding : (MonoMarshalNative)-1);
+                               emit_struct_conv_full (mb, eklass, FALSE, 0, eklass == mono_defaults.char_class ? encoding : (MonoMarshalNative)-1);
                        }
 
                        mono_mb_emit_add_to_local (mb, index_var, 1);
@@ -6487,7 +6503,7 @@ emit_marshal_array (EmitMarshalContext *m, int argnum, MonoType *t,
                                        mono_mb_emit_stloc (mb, 1);
 
                                        /* emit valuetype conversion code */
-                                       emit_struct_conv_full (mb, eklass, TRUE, eklass == mono_defaults.char_class ? encoding : (MonoMarshalNative)-1);
+                                       emit_struct_conv_full (mb, eklass, TRUE, 0, eklass == mono_defaults.char_class ? encoding : (MonoMarshalNative)-1);
                                }
 
                                if (need_free) {