IGNITE-6951 .NET: Support pointer serialization
authorPavel Tupitsyn <ptupitsyn@apache.org>
Thu, 21 Dec 2017 11:11:47 +0000 (14:11 +0300)
committerPavel Tupitsyn <ptupitsyn@apache.org>
Thu, 21 Dec 2017 11:11:47 +0000 (14:11 +0300)
This closes #3267

modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinarySelfTest.cs
modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/Serializable/PrimitivesTest.cs
modules/platforms/dotnet/Apache.Ignite.Core.Tests/TestUtils.Common.cs
modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReflectiveActions.cs
modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriter.cs
modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/SerializableSerializer.cs
modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/TypeCaster.cs
modules/platforms/dotnet/Apache.Ignite.sln.DotSettings

index 6ffce38..c8d1b95 100644 (file)
@@ -1584,6 +1584,49 @@ namespace Apache.Ignite.Core.Tests.Binary
         }
 
         /// <summary>
+        /// Tests pointer types.
+        /// </summary>
+        [Test]
+        public unsafe void TestPointers([Values(false, true)] bool raw)
+        {
+            // Values.
+            var vals = new[] {IntPtr.Zero, new IntPtr(long.MinValue), new IntPtr(long.MaxValue)};
+            foreach (var intPtr in vals)
+            {
+                Assert.AreEqual(intPtr, TestUtils.SerializeDeserialize(intPtr));
+            }
+
+            var uvals = new[] {UIntPtr.Zero, new UIntPtr(long.MaxValue), new UIntPtr(ulong.MaxValue)};
+            foreach (var uintPtr in uvals)
+            {
+                Assert.AreEqual(uintPtr, TestUtils.SerializeDeserialize(uintPtr));
+            }
+
+            // Type fields.
+            var ptrs = new Pointers
+            {
+                ByteP = (byte*) 123,
+                IntP = (int*) 456,
+                VoidP = (void*) 789,
+                IntPtr = new IntPtr(long.MaxValue),
+                UIntPtr = new UIntPtr(ulong.MaxValue),
+                IntPtrs = new[] {new IntPtr(long.MinValue)},
+                UIntPtrs = new[] {new UIntPtr(long.MaxValue), new UIntPtr(ulong.MaxValue)}
+            };
+
+            var res = TestUtils.SerializeDeserialize(ptrs, raw);
+            
+            Assert.IsTrue(ptrs.ByteP == res.ByteP);
+            Assert.IsTrue(ptrs.IntP == res.IntP);
+            Assert.IsTrue(ptrs.VoidP == res.VoidP);
+
+            Assert.AreEqual(ptrs.IntPtr, res.IntPtr);
+            Assert.AreEqual(ptrs.IntPtrs, res.IntPtrs);
+            Assert.AreEqual(ptrs.UIntPtr, res.UIntPtr);
+            Assert.AreEqual(ptrs.UIntPtrs, res.UIntPtrs);
+        }
+
+        /// <summary>
         /// Tests the compact footer setting.
         /// </summary>
         [Test]
@@ -2692,5 +2735,16 @@ namespace Apache.Ignite.Core.Tests.Binary
                 MultidimUInt = reader.ReadObject<uint[,,]>("MultidimUInt");
             }
         }
+
+        private unsafe class Pointers
+        {
+            public IntPtr IntPtr { get; set; }
+            public IntPtr[] IntPtrs { get; set; }
+            public UIntPtr UIntPtr { get; set; }
+            public UIntPtr[] UIntPtrs { get; set; }
+            public byte* ByteP { get; set; }
+            public int* IntP { get; set; }
+            public void* VoidP { get; set; }
+        }
     }
 }
index bbbee60..0aa23cf 100644 (file)
@@ -126,7 +126,11 @@ namespace Apache.Ignite.Core.Tests.Binary.Serializable
                 Guid = Guid.NewGuid(),
                 Guids = new[] {Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid()},
                 String = "hello world",
-                Strings = new[] {"hello", "world"}
+                Strings = new[] {"hello", "world"},
+                IntPtr = new IntPtr(12345),
+                IntPtrs = new[] {IntPtr.Zero, new IntPtr(1), new IntPtr(-1), new IntPtr(long.MaxValue)},
+                UIntPtr = new UIntPtr(1234567),
+                UIntPtrs = new[] {UIntPtr.Zero, new UIntPtr(1), new UIntPtr(long.MaxValue), new UIntPtr(ulong.MaxValue)}
             };
 
             var vals = new[] {new Primitives(), val1};
@@ -246,6 +250,18 @@ namespace Apache.Ignite.Core.Tests.Binary.Serializable
                 Assert.AreEqual(val.Strings, res.Strings);
                 Assert.AreEqual(val.Strings, bin.GetField<string[]>("strings"));
 
+                Assert.AreEqual(val.IntPtr, res.IntPtr);
+                Assert.AreEqual(val.IntPtr, bin.GetField<IntPtr>("intptr"));
+
+                Assert.AreEqual(val.IntPtrs, res.IntPtrs);
+                Assert.AreEqual(val.IntPtrs, bin.GetField<IntPtr[]>("intptrs"));
+
+                Assert.AreEqual(val.UIntPtr, res.UIntPtr);
+                Assert.AreEqual(val.UIntPtr, bin.GetField<UIntPtr>("uintptr"));
+
+                Assert.AreEqual(val.UIntPtrs, res.UIntPtrs);
+                Assert.AreEqual(val.UIntPtrs, bin.GetField<UIntPtr[]>("uintptrs"));
+
                 VerifyFieldTypes(bin);
             }
         }
@@ -306,7 +322,7 @@ namespace Apache.Ignite.Core.Tests.Binary.Serializable
                     DateTime.Now, DateTime.MinValue, DateTime.MaxValue, DateTime.UtcNow, null
                 },
                 Guid = Guid.NewGuid(),
-                Guids = new Guid?[] {Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), null},
+                Guids = new Guid?[] {Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), null}
             };
 
             var vals = new[] {new PrimitivesNullable(), val1};
@@ -476,6 +492,12 @@ namespace Apache.Ignite.Core.Tests.Binary.Serializable
 
             Assert.AreEqual("Object", binType.GetFieldTypeName("datetime"));
             Assert.AreEqual("Object", binType.GetFieldTypeName("datetimes"));
+
+            Assert.AreEqual("Object", binType.GetFieldTypeName("intptr"));
+            Assert.AreEqual("Object", binType.GetFieldTypeName("intptrs"));
+
+            Assert.AreEqual("Object", binType.GetFieldTypeName("uintptr"));
+            Assert.AreEqual("Object", binType.GetFieldTypeName("uintptrs"));
         }
 
         /// <summary>
@@ -524,6 +546,10 @@ namespace Apache.Ignite.Core.Tests.Binary.Serializable
             public DateTime[] DateTimes { get; set; }
             public string String { get; set; }
             public string[] Strings { get; set; }
+            public IntPtr IntPtr { get; set; }
+            public IntPtr[] IntPtrs { get; set; }
+            public UIntPtr UIntPtr { get; set; }
+            public UIntPtr[] UIntPtrs { get; set; }
 
             public Primitives()
             {
@@ -581,6 +607,12 @@ namespace Apache.Ignite.Core.Tests.Binary.Serializable
 
                 String = info.GetString("string");
                 Strings = (string[]) info.GetValue("strings", typeof(string[]));
+
+                IntPtr = (IntPtr) info.GetInt64("intptr");
+                IntPtrs = (IntPtr[]) info.GetValue("intptrs", typeof(IntPtr[]));
+
+                UIntPtr = (UIntPtr) info.GetInt64("uintptr");
+                UIntPtrs = (UIntPtr[]) info.GetValue("uintptrs", typeof(UIntPtr[]));
             }
 
             public void GetObjectData(SerializationInfo info, StreamingContext context)
@@ -619,6 +651,10 @@ namespace Apache.Ignite.Core.Tests.Binary.Serializable
                 info.AddValue("datetimes", DateTimes, typeof(DateTime[]));
                 info.AddValue("string", String, typeof(string));
                 info.AddValue("strings", Strings, typeof(string[]));
+                info.AddValue("intptr", IntPtr);
+                info.AddValue("intptrs", IntPtrs, typeof(IntPtr[]));
+                info.AddValue("uintptr", UIntPtr);
+                info.AddValue("uintptrs", UIntPtrs, typeof(UIntPtr[]));
             }
         }
 
index 2430300..2d735e9 100644 (file)
@@ -22,6 +22,7 @@ namespace Apache.Ignite.Core.Tests
     using System.Collections.Generic;
     using System.Linq;
     using System.Threading;
+    using Apache.Ignite.Core.Binary;
     using Apache.Ignite.Core.Cluster;
     using Apache.Ignite.Core.Discovery.Tcp;
     using Apache.Ignite.Core.Discovery.Tcp.Static;
@@ -341,11 +342,16 @@ namespace Apache.Ignite.Core.Tests
         /// <summary>
         /// Serializes and deserializes back an object.
         /// </summary>
-        public static T SerializeDeserialize<T>(T obj)
+        public static T SerializeDeserialize<T>(T obj, bool raw = false)
         {
+            var cfg = new BinaryConfiguration
+            {
+                Serializer = raw ? new BinaryReflectiveSerializer {RawMode = true} : null
+            };
+
 #if NETCOREAPP2_0
             var marshType = typeof(IIgnite).Assembly.GetType("Apache.Ignite.Core.Impl.Binary.Marshaller");
-            var marsh = Activator.CreateInstance(marshType, new object[] { null, null });
+            var marsh = Activator.CreateInstance(marshType, new object[] { cfg, null });
             marshType.GetProperty("CompactFooter").SetValue(marsh, false);
 
             var bytes = marshType.GetMethod("Marshal").MakeGenericMethod(typeof(object))
@@ -357,7 +363,7 @@ namespace Apache.Ignite.Core.Tests
 
             return (T)res;
 #else
-            var marsh = new Marshaller(null) { CompactFooter = false };
+            var marsh = new Marshaller(cfg) { CompactFooter = false };
 
             return marsh.Unmarshal<T>(marsh.Marshal(obj));
 #endif
index a8ea9f1..4446409 100644 (file)
@@ -237,6 +237,24 @@ namespace Apache.Ignite.Core.Impl.Binary
                     ? GetRawReader(field, r => r.ReadDouble())
                     : GetReader(field, (f, r) => r.ReadDouble(f));
             }
+            else if (type == typeof(IntPtr))
+            {
+                writeAction = raw
+                    ? GetRawWriter<IntPtr>(field, (w, o) => w.WriteLong((long) o))
+                    : GetWriter<IntPtr>(field, (f, w, o) => w.WriteLong(f, (long) o));
+                readAction = raw
+                    ? GetRawReader(field, r => (IntPtr) r.ReadLong())
+                    : GetReader(field, (f, r) => (IntPtr) r.ReadLong(f));
+            }
+            else if (type == typeof(UIntPtr))
+            {
+                writeAction = raw
+                    ? GetRawWriter<UIntPtr>(field, (w, o) => w.WriteLong((long) o))
+                    : GetWriter<UIntPtr>(field, (f, w, o) => w.WriteLong(f, (long) o));
+                readAction = raw
+                    ? GetRawReader(field, r => (UIntPtr) r.ReadLong())
+                    : GetReader(field, (f, r) => (UIntPtr) r.ReadLong(f));
+            }
             else
             {
                 throw new IgniteException(string.Format("Unsupported primitive type '{0}' [Field={1}, " +
@@ -268,7 +286,8 @@ namespace Apache.Ignite.Core.Impl.Binary
                 return;
             }
 
-            Type elemType = field.FieldType.GetElementType();
+            var elemType = field.FieldType.GetElementType();
+            Debug.Assert(elemType != null);
 
             if (elemType == typeof (bool))
             {
@@ -478,8 +497,8 @@ namespace Apache.Ignite.Core.Impl.Binary
                 !new[] {typeof(long), typeof(ulong)}.Contains(Enum.GetUnderlyingType(nullableType ?? type)))
             {
                 writeAction = raw
-                    ? GetRawWriter<object>(field, (w, o) => w.WriteEnum(o), true)
-                    : GetWriter<object>(field, (f, w, o) => w.WriteEnum(f, o), true);
+                    ? GetRawWriter<object>(field, (w, o) => w.WriteEnum(o))
+                    : GetWriter<object>(field, (f, w, o) => w.WriteEnum(f, o));
                 readAction = raw ? GetRawReader(field, MthdReadEnumRaw) : GetReader(field, MthdReadEnum);
             }
             else if (type == typeof(IDictionary) || type == typeof(Hashtable))
@@ -510,6 +529,21 @@ namespace Apache.Ignite.Core.Impl.Binary
                 writeAction = GetWriter<DateTime?>(field, (f, w, o) => w.WriteTimestamp(f, o));
                 readAction = GetReader(field, (f, r) => r.ReadTimestamp(f));
             }
+            else if (type.IsPointer)
+                unsafe
+                {
+                    // Expression trees do not work with pointers properly, use reflection.
+                    var fieldName = BinaryUtils.CleanFieldName(field.Name);
+                    writeAction = raw
+                        ? (BinaryReflectiveWriteAction) ((o, w) =>
+                            w.GetRawWriter().WriteLong((long) Pointer.Unbox(field.GetValue(o))))
+                        : ((o, w) => w.WriteLong(fieldName, (long) Pointer.Unbox(field.GetValue(o))));
+
+                    readAction = raw
+                        ? (BinaryReflectiveReadAction) ((o, r) =>
+                            field.SetValue(o, Pointer.Box((void*) r.GetRawReader().ReadLong(), field.FieldType)))
+                        : ((o, r) => field.SetValue(o, Pointer.Box((void*) r.ReadLong(fieldName), field.FieldType)));
+                }
             else
             {
                 writeAction = raw ? GetRawWriter(field, MthdWriteObjRaw) : GetWriter(field, MthdWriteObj);
@@ -561,8 +595,7 @@ namespace Apache.Ignite.Core.Impl.Binary
         /// Gets the reader with a specified write action.
         /// </summary>
         private static BinaryReflectiveWriteAction GetWriter<T>(FieldInfo field,
-            Expression<Action<string, IBinaryWriter, T>> write,
-            bool convertFieldValToObject = false)
+            Expression<Action<string, IBinaryWriter, T>> write)
         {
             Debug.Assert(field != null);
             Debug.Assert(field.DeclaringType != null);   // non-static
@@ -573,8 +606,10 @@ namespace Apache.Ignite.Core.Impl.Binary
             var targetParamConverted = Expression.Convert(targetParam, field.DeclaringType);
             Expression fldExpr = Expression.Field(targetParamConverted, field);
 
-            if (convertFieldValToObject)
-                fldExpr = Expression.Convert(fldExpr, typeof (object));
+            if (field.FieldType != typeof(T))
+            {
+                fldExpr = Expression.Convert(fldExpr, typeof(T));
+            }
 
             // Call Writer method
             var writerParam = Expression.Parameter(typeof(IBinaryWriter));
@@ -589,8 +624,7 @@ namespace Apache.Ignite.Core.Impl.Binary
         /// Gets the reader with a specified write action.
         /// </summary>
         private static BinaryReflectiveWriteAction GetRawWriter<T>(FieldInfo field,
-            Expression<Action<IBinaryRawWriter, T>> write,
-            bool convertFieldValToObject = false)
+            Expression<Action<IBinaryRawWriter, T>> write)
         {
             Debug.Assert(field != null);
             Debug.Assert(field.DeclaringType != null);   // non-static
@@ -601,8 +635,8 @@ namespace Apache.Ignite.Core.Impl.Binary
             var targetParamConverted = Expression.Convert(targetParam, field.DeclaringType);
             Expression fldExpr = Expression.Field(targetParamConverted, field);
 
-            if (convertFieldValToObject)
-                fldExpr = Expression.Convert(fldExpr, typeof (object));
+            if (field.FieldType != typeof(T))
+                fldExpr = Expression.Convert(fldExpr, typeof (T));
 
             // Call Writer method
             var writerParam = Expression.Parameter(typeof(IBinaryWriter));
@@ -677,7 +711,9 @@ namespace Apache.Ignite.Core.Impl.Binary
             Expression readExpr = Expression.Invoke(read, fldNameParam, readerParam);
 
             if (typeof(T) != field.FieldType)
+            {
                 readExpr = Expression.Convert(readExpr, field.FieldType);
+            }
 
             // Assign field value
             var targetParam = Expression.Parameter(typeof(object));
index b98ad5f..55668c4 100644 (file)
@@ -1298,7 +1298,7 @@ namespace Apache.Ignite.Core.Impl.Binary
         /// <param name="type">Type.</param>
         private unsafe void WritePrimitive<T>(T val, Type type)
         {
-            // .Net defines 14 primitive types. We support 12 - excluding IntPtr and UIntPtr.
+            // .NET defines 14 primitive types.
             // Types check sequence is designed to minimize comparisons for the most frequent types.
 
             if (type == typeof(int))
@@ -1337,6 +1337,16 @@ namespace Apache.Ignite.Core.Impl.Binary
                 var val0 = TypeCaster<ulong>.Cast(val);
                 WriteLongField(*(long*)&val0);
             }
+            else if (type == typeof(IntPtr))
+            {
+                var val0 = TypeCaster<IntPtr>.Cast(val).ToInt64();
+                WriteLongField(val0);
+            }
+            else if (type == typeof(UIntPtr))
+            {
+                var val0 = TypeCaster<UIntPtr>.Cast(val).ToUInt64();
+                WriteLongField(*(long*)&val0);
+            }
             else
                 throw BinaryUtils.GetUnsupportedTypeException(type, val);
         }
index fc91edb..e597382 100644 (file)
@@ -403,7 +403,7 @@ namespace Apache.Ignite.Core.Impl.Binary
                 {
                     writer.WriteByteArray(entry.Name, (byte[]) entry.Value);
                 }
-                if (type == typeof(sbyte))
+                else if (type == typeof(sbyte))
                 {
                     writer.WriteByte(entry.Name, (byte) (sbyte) entry.Value);
                 }
index 6e6bf7d..4f41110 100644 (file)
@@ -44,10 +44,10 @@ namespace Apache.Ignite.Core.Impl.Common
             {
                 return Casters<TFrom>.Caster(obj);
             }
-            catch (InvalidCastException)
+            catch (InvalidCastException e)
             {
                 throw new InvalidCastException(string.Format("Specified cast is not valid: {0} -> {1}", typeof (TFrom),
-                    typeof (T)));
+                    typeof (T)), e);
             }
 #else
             return Casters<TFrom>.Caster(obj);
@@ -77,10 +77,15 @@ namespace Apache.Ignite.Core.Impl.Common
                 {
                     // Just return what we have
                     var pExpr = Expression.Parameter(typeof(TFrom));
-                    
+
                     return Expression.Lambda<Func<TFrom, T>>(pExpr, pExpr).Compile();
                 }
 
+                if (typeof(T) == typeof(UIntPtr) && typeof(TFrom) == typeof(long))
+                {
+                    return l => unchecked ((T) (object) (UIntPtr) (ulong) (long) (object) l);
+                }
+
                 var paramExpr = Expression.Parameter(typeof(TFrom));
                 var convertExpr = Expression.Convert(paramExpr, typeof(T));
 
index 7d81184..5646179 100644 (file)
@@ -9,5 +9,4 @@
        <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertClosureToMethodGroup/@EntryIndexedValue">DO_NOT_SHOW</s:String>
        <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002EXml_002ECodeStyle_002EFormatSettingsUpgrade_002EXmlMoveToCommonFormatterSettingsUpgrade/@EntryIndexedValue">True</s:Boolean>
        <s:Boolean x:Key="/Default/Environment/UnitTesting/ShadowCopy/@EntryValue">False</s:Boolean>
-       <s:Boolean x:Key="/Default/Environment/UnitTesting/WrapLongLinesInUnitTestSessionOutput/@EntryValue">False</s:Boolean>
 </wpf:ResourceDictionary>
\ No newline at end of file