Skip to content

Commit cc5fff5

Browse files
committed
IGNITE-6951 .NET: Support pointer serialization
This closes apache#3267
1 parent fa55a6e commit cc5fff5

File tree

8 files changed

+168
-22
lines changed

8 files changed

+168
-22
lines changed

modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinarySelfTest.cs

+54
Original file line numberDiff line numberDiff line change
@@ -1583,6 +1583,49 @@ public void TestMultidimensionalArrays()
15831583
Assert.AreEqual(obj2.MultidimUInt, resObj2.MultidimUInt);
15841584
}
15851585

1586+
/// <summary>
1587+
/// Tests pointer types.
1588+
/// </summary>
1589+
[Test]
1590+
public unsafe void TestPointers([Values(false, true)] bool raw)
1591+
{
1592+
// Values.
1593+
var vals = new[] {IntPtr.Zero, new IntPtr(long.MinValue), new IntPtr(long.MaxValue)};
1594+
foreach (var intPtr in vals)
1595+
{
1596+
Assert.AreEqual(intPtr, TestUtils.SerializeDeserialize(intPtr));
1597+
}
1598+
1599+
var uvals = new[] {UIntPtr.Zero, new UIntPtr(long.MaxValue), new UIntPtr(ulong.MaxValue)};
1600+
foreach (var uintPtr in uvals)
1601+
{
1602+
Assert.AreEqual(uintPtr, TestUtils.SerializeDeserialize(uintPtr));
1603+
}
1604+
1605+
// Type fields.
1606+
var ptrs = new Pointers
1607+
{
1608+
ByteP = (byte*) 123,
1609+
IntP = (int*) 456,
1610+
VoidP = (void*) 789,
1611+
IntPtr = new IntPtr(long.MaxValue),
1612+
UIntPtr = new UIntPtr(ulong.MaxValue),
1613+
IntPtrs = new[] {new IntPtr(long.MinValue)},
1614+
UIntPtrs = new[] {new UIntPtr(long.MaxValue), new UIntPtr(ulong.MaxValue)}
1615+
};
1616+
1617+
var res = TestUtils.SerializeDeserialize(ptrs, raw);
1618+
1619+
Assert.IsTrue(ptrs.ByteP == res.ByteP);
1620+
Assert.IsTrue(ptrs.IntP == res.IntP);
1621+
Assert.IsTrue(ptrs.VoidP == res.VoidP);
1622+
1623+
Assert.AreEqual(ptrs.IntPtr, res.IntPtr);
1624+
Assert.AreEqual(ptrs.IntPtrs, res.IntPtrs);
1625+
Assert.AreEqual(ptrs.UIntPtr, res.UIntPtr);
1626+
Assert.AreEqual(ptrs.UIntPtrs, res.UIntPtrs);
1627+
}
1628+
15861629
/// <summary>
15871630
/// Tests the compact footer setting.
15881631
/// </summary>
@@ -2692,5 +2735,16 @@ public void ReadBinary(IBinaryReader reader)
26922735
MultidimUInt = reader.ReadObject<uint[,,]>("MultidimUInt");
26932736
}
26942737
}
2738+
2739+
private unsafe class Pointers
2740+
{
2741+
public IntPtr IntPtr { get; set; }
2742+
public IntPtr[] IntPtrs { get; set; }
2743+
public UIntPtr UIntPtr { get; set; }
2744+
public UIntPtr[] UIntPtrs { get; set; }
2745+
public byte* ByteP { get; set; }
2746+
public int* IntP { get; set; }
2747+
public void* VoidP { get; set; }
2748+
}
26952749
}
26962750
}

modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/Serializable/PrimitivesTest.cs

+38-2
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,11 @@ public void TestPrimitives()
126126
Guid = Guid.NewGuid(),
127127
Guids = new[] {Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid()},
128128
String = "hello world",
129-
Strings = new[] {"hello", "world"}
129+
Strings = new[] {"hello", "world"},
130+
IntPtr = new IntPtr(12345),
131+
IntPtrs = new[] {IntPtr.Zero, new IntPtr(1), new IntPtr(-1), new IntPtr(long.MaxValue)},
132+
UIntPtr = new UIntPtr(1234567),
133+
UIntPtrs = new[] {UIntPtr.Zero, new UIntPtr(1), new UIntPtr(long.MaxValue), new UIntPtr(ulong.MaxValue)}
130134
};
131135

132136
var vals = new[] {new Primitives(), val1};
@@ -246,6 +250,18 @@ public void TestPrimitives()
246250
Assert.AreEqual(val.Strings, res.Strings);
247251
Assert.AreEqual(val.Strings, bin.GetField<string[]>("strings"));
248252

253+
Assert.AreEqual(val.IntPtr, res.IntPtr);
254+
Assert.AreEqual(val.IntPtr, bin.GetField<IntPtr>("intptr"));
255+
256+
Assert.AreEqual(val.IntPtrs, res.IntPtrs);
257+
Assert.AreEqual(val.IntPtrs, bin.GetField<IntPtr[]>("intptrs"));
258+
259+
Assert.AreEqual(val.UIntPtr, res.UIntPtr);
260+
Assert.AreEqual(val.UIntPtr, bin.GetField<UIntPtr>("uintptr"));
261+
262+
Assert.AreEqual(val.UIntPtrs, res.UIntPtrs);
263+
Assert.AreEqual(val.UIntPtrs, bin.GetField<UIntPtr[]>("uintptrs"));
264+
249265
VerifyFieldTypes(bin);
250266
}
251267
}
@@ -306,7 +322,7 @@ public void TestPrimitivesNullable()
306322
DateTime.Now, DateTime.MinValue, DateTime.MaxValue, DateTime.UtcNow, null
307323
},
308324
Guid = Guid.NewGuid(),
309-
Guids = new Guid?[] {Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), null},
325+
Guids = new Guid?[] {Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), null}
310326
};
311327

312328
var vals = new[] {new PrimitivesNullable(), val1};
@@ -476,6 +492,12 @@ private static void VerifyFieldTypes(IBinaryObject bin)
476492

477493
Assert.AreEqual("Object", binType.GetFieldTypeName("datetime"));
478494
Assert.AreEqual("Object", binType.GetFieldTypeName("datetimes"));
495+
496+
Assert.AreEqual("Object", binType.GetFieldTypeName("intptr"));
497+
Assert.AreEqual("Object", binType.GetFieldTypeName("intptrs"));
498+
499+
Assert.AreEqual("Object", binType.GetFieldTypeName("uintptr"));
500+
Assert.AreEqual("Object", binType.GetFieldTypeName("uintptrs"));
479501
}
480502

481503
/// <summary>
@@ -524,6 +546,10 @@ public class Primitives : ISerializable
524546
public DateTime[] DateTimes { get; set; }
525547
public string String { get; set; }
526548
public string[] Strings { get; set; }
549+
public IntPtr IntPtr { get; set; }
550+
public IntPtr[] IntPtrs { get; set; }
551+
public UIntPtr UIntPtr { get; set; }
552+
public UIntPtr[] UIntPtrs { get; set; }
527553

528554
public Primitives()
529555
{
@@ -581,6 +607,12 @@ protected Primitives(SerializationInfo info, StreamingContext context)
581607

582608
String = info.GetString("string");
583609
Strings = (string[]) info.GetValue("strings", typeof(string[]));
610+
611+
IntPtr = (IntPtr) info.GetInt64("intptr");
612+
IntPtrs = (IntPtr[]) info.GetValue("intptrs", typeof(IntPtr[]));
613+
614+
UIntPtr = (UIntPtr) info.GetInt64("uintptr");
615+
UIntPtrs = (UIntPtr[]) info.GetValue("uintptrs", typeof(UIntPtr[]));
584616
}
585617

586618
public void GetObjectData(SerializationInfo info, StreamingContext context)
@@ -619,6 +651,10 @@ public void GetObjectData(SerializationInfo info, StreamingContext context)
619651
info.AddValue("datetimes", DateTimes, typeof(DateTime[]));
620652
info.AddValue("string", String, typeof(string));
621653
info.AddValue("strings", Strings, typeof(string[]));
654+
info.AddValue("intptr", IntPtr);
655+
info.AddValue("intptrs", IntPtrs, typeof(IntPtr[]));
656+
info.AddValue("uintptr", UIntPtr);
657+
info.AddValue("uintptrs", UIntPtrs, typeof(UIntPtr[]));
622658
}
623659
}
624660

modules/platforms/dotnet/Apache.Ignite.Core.Tests/TestUtils.Common.cs

+9-3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ namespace Apache.Ignite.Core.Tests
2222
using System.Collections.Generic;
2323
using System.Linq;
2424
using System.Threading;
25+
using Apache.Ignite.Core.Binary;
2526
using Apache.Ignite.Core.Cluster;
2627
using Apache.Ignite.Core.Discovery.Tcp;
2728
using Apache.Ignite.Core.Discovery.Tcp.Static;
@@ -341,11 +342,16 @@ public static void AssertHandleRegistryHasItems(IIgnite grid, int expectedCount,
341342
/// <summary>
342343
/// Serializes and deserializes back an object.
343344
/// </summary>
344-
public static T SerializeDeserialize<T>(T obj)
345+
public static T SerializeDeserialize<T>(T obj, bool raw = false)
345346
{
347+
var cfg = new BinaryConfiguration
348+
{
349+
Serializer = raw ? new BinaryReflectiveSerializer {RawMode = true} : null
350+
};
351+
346352
#if NETCOREAPP2_0
347353
var marshType = typeof(IIgnite).Assembly.GetType("Apache.Ignite.Core.Impl.Binary.Marshaller");
348-
var marsh = Activator.CreateInstance(marshType, new object[] { null, null });
354+
var marsh = Activator.CreateInstance(marshType, new object[] { cfg, null });
349355
marshType.GetProperty("CompactFooter").SetValue(marsh, false);
350356

351357
var bytes = marshType.GetMethod("Marshal").MakeGenericMethod(typeof(object))
@@ -357,7 +363,7 @@ public static T SerializeDeserialize<T>(T obj)
357363

358364
return (T)res;
359365
#else
360-
var marsh = new Marshaller(null) { CompactFooter = false };
366+
var marsh = new Marshaller(cfg) { CompactFooter = false };
361367

362368
return marsh.Unmarshal<T>(marsh.Marshal(obj));
363369
#endif

modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReflectiveActions.cs

+47-11
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,24 @@ private static void HandlePrimitive(FieldInfo field, out BinaryReflectiveWriteAc
237237
? GetRawReader(field, r => r.ReadDouble())
238238
: GetReader(field, (f, r) => r.ReadDouble(f));
239239
}
240+
else if (type == typeof(IntPtr))
241+
{
242+
writeAction = raw
243+
? GetRawWriter<IntPtr>(field, (w, o) => w.WriteLong((long) o))
244+
: GetWriter<IntPtr>(field, (f, w, o) => w.WriteLong(f, (long) o));
245+
readAction = raw
246+
? GetRawReader(field, r => (IntPtr) r.ReadLong())
247+
: GetReader(field, (f, r) => (IntPtr) r.ReadLong(f));
248+
}
249+
else if (type == typeof(UIntPtr))
250+
{
251+
writeAction = raw
252+
? GetRawWriter<UIntPtr>(field, (w, o) => w.WriteLong((long) o))
253+
: GetWriter<UIntPtr>(field, (f, w, o) => w.WriteLong(f, (long) o));
254+
readAction = raw
255+
? GetRawReader(field, r => (UIntPtr) r.ReadLong())
256+
: GetReader(field, (f, r) => (UIntPtr) r.ReadLong(f));
257+
}
240258
else
241259
{
242260
throw new IgniteException(string.Format("Unsupported primitive type '{0}' [Field={1}, " +
@@ -268,7 +286,8 @@ private static void HandleArray(FieldInfo field, out BinaryReflectiveWriteAction
268286
return;
269287
}
270288

271-
Type elemType = field.FieldType.GetElementType();
289+
var elemType = field.FieldType.GetElementType();
290+
Debug.Assert(elemType != null);
272291

273292
if (elemType == typeof (bool))
274293
{
@@ -478,8 +497,8 @@ private static void HandleOther(FieldInfo field, out BinaryReflectiveWriteAction
478497
!new[] {typeof(long), typeof(ulong)}.Contains(Enum.GetUnderlyingType(nullableType ?? type)))
479498
{
480499
writeAction = raw
481-
? GetRawWriter<object>(field, (w, o) => w.WriteEnum(o), true)
482-
: GetWriter<object>(field, (f, w, o) => w.WriteEnum(f, o), true);
500+
? GetRawWriter<object>(field, (w, o) => w.WriteEnum(o))
501+
: GetWriter<object>(field, (f, w, o) => w.WriteEnum(f, o));
483502
readAction = raw ? GetRawReader(field, MthdReadEnumRaw) : GetReader(field, MthdReadEnum);
484503
}
485504
else if (type == typeof(IDictionary) || type == typeof(Hashtable))
@@ -510,6 +529,21 @@ private static void HandleOther(FieldInfo field, out BinaryReflectiveWriteAction
510529
writeAction = GetWriter<DateTime?>(field, (f, w, o) => w.WriteTimestamp(f, o));
511530
readAction = GetReader(field, (f, r) => r.ReadTimestamp(f));
512531
}
532+
else if (type.IsPointer)
533+
unsafe
534+
{
535+
// Expression trees do not work with pointers properly, use reflection.
536+
var fieldName = BinaryUtils.CleanFieldName(field.Name);
537+
writeAction = raw
538+
? (BinaryReflectiveWriteAction) ((o, w) =>
539+
w.GetRawWriter().WriteLong((long) Pointer.Unbox(field.GetValue(o))))
540+
: ((o, w) => w.WriteLong(fieldName, (long) Pointer.Unbox(field.GetValue(o))));
541+
542+
readAction = raw
543+
? (BinaryReflectiveReadAction) ((o, r) =>
544+
field.SetValue(o, Pointer.Box((void*) r.GetRawReader().ReadLong(), field.FieldType)))
545+
: ((o, r) => field.SetValue(o, Pointer.Box((void*) r.ReadLong(fieldName), field.FieldType)));
546+
}
513547
else
514548
{
515549
writeAction = raw ? GetRawWriter(field, MthdWriteObjRaw) : GetWriter(field, MthdWriteObj);
@@ -561,8 +595,7 @@ private static bool IsTimestamp(FieldInfo field, bool forceTimestamp, bool raw)
561595
/// Gets the reader with a specified write action.
562596
/// </summary>
563597
private static BinaryReflectiveWriteAction GetWriter<T>(FieldInfo field,
564-
Expression<Action<string, IBinaryWriter, T>> write,
565-
bool convertFieldValToObject = false)
598+
Expression<Action<string, IBinaryWriter, T>> write)
566599
{
567600
Debug.Assert(field != null);
568601
Debug.Assert(field.DeclaringType != null); // non-static
@@ -573,8 +606,10 @@ private static BinaryReflectiveWriteAction GetWriter<T>(FieldInfo field,
573606
var targetParamConverted = Expression.Convert(targetParam, field.DeclaringType);
574607
Expression fldExpr = Expression.Field(targetParamConverted, field);
575608

576-
if (convertFieldValToObject)
577-
fldExpr = Expression.Convert(fldExpr, typeof (object));
609+
if (field.FieldType != typeof(T))
610+
{
611+
fldExpr = Expression.Convert(fldExpr, typeof(T));
612+
}
578613

579614
// Call Writer method
580615
var writerParam = Expression.Parameter(typeof(IBinaryWriter));
@@ -589,8 +624,7 @@ private static BinaryReflectiveWriteAction GetWriter<T>(FieldInfo field,
589624
/// Gets the reader with a specified write action.
590625
/// </summary>
591626
private static BinaryReflectiveWriteAction GetRawWriter<T>(FieldInfo field,
592-
Expression<Action<IBinaryRawWriter, T>> write,
593-
bool convertFieldValToObject = false)
627+
Expression<Action<IBinaryRawWriter, T>> write)
594628
{
595629
Debug.Assert(field != null);
596630
Debug.Assert(field.DeclaringType != null); // non-static
@@ -601,8 +635,8 @@ private static BinaryReflectiveWriteAction GetRawWriter<T>(FieldInfo field,
601635
var targetParamConverted = Expression.Convert(targetParam, field.DeclaringType);
602636
Expression fldExpr = Expression.Field(targetParamConverted, field);
603637

604-
if (convertFieldValToObject)
605-
fldExpr = Expression.Convert(fldExpr, typeof (object));
638+
if (field.FieldType != typeof(T))
639+
fldExpr = Expression.Convert(fldExpr, typeof (T));
606640

607641
// Call Writer method
608642
var writerParam = Expression.Parameter(typeof(IBinaryWriter));
@@ -677,7 +711,9 @@ private static BinaryReflectiveReadAction GetReader<T>(FieldInfo field,
677711
Expression readExpr = Expression.Invoke(read, fldNameParam, readerParam);
678712

679713
if (typeof(T) != field.FieldType)
714+
{
680715
readExpr = Expression.Convert(readExpr, field.FieldType);
716+
}
681717

682718
// Assign field value
683719
var targetParam = Expression.Parameter(typeof(object));

modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriter.cs

+11-1
Original file line numberDiff line numberDiff line change
@@ -1298,7 +1298,7 @@ public void SetCustomTypeDataFlag(bool hasCustomTypeData)
12981298
/// <param name="type">Type.</param>
12991299
private unsafe void WritePrimitive<T>(T val, Type type)
13001300
{
1301-
// .Net defines 14 primitive types. We support 12 - excluding IntPtr and UIntPtr.
1301+
// .NET defines 14 primitive types.
13021302
// Types check sequence is designed to minimize comparisons for the most frequent types.
13031303

13041304
if (type == typeof(int))
@@ -1337,6 +1337,16 @@ private unsafe void WritePrimitive<T>(T val, Type type)
13371337
var val0 = TypeCaster<ulong>.Cast(val);
13381338
WriteLongField(*(long*)&val0);
13391339
}
1340+
else if (type == typeof(IntPtr))
1341+
{
1342+
var val0 = TypeCaster<IntPtr>.Cast(val).ToInt64();
1343+
WriteLongField(val0);
1344+
}
1345+
else if (type == typeof(UIntPtr))
1346+
{
1347+
var val0 = TypeCaster<UIntPtr>.Cast(val).ToUInt64();
1348+
WriteLongField(*(long*)&val0);
1349+
}
13401350
else
13411351
throw BinaryUtils.GetUnsupportedTypeException(type, val);
13421352
}

modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/SerializableSerializer.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ private static void WriteEntry(IBinaryWriter writer, SerializationEntry entry)
403403
{
404404
writer.WriteByteArray(entry.Name, (byte[]) entry.Value);
405405
}
406-
if (type == typeof(sbyte))
406+
else if (type == typeof(sbyte))
407407
{
408408
writer.WriteByte(entry.Name, (byte) (sbyte) entry.Value);
409409
}

modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/TypeCaster.cs

+8-3
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@ public static T Cast<TFrom>(TFrom obj)
4444
{
4545
return Casters<TFrom>.Caster(obj);
4646
}
47-
catch (InvalidCastException)
47+
catch (InvalidCastException e)
4848
{
4949
throw new InvalidCastException(string.Format("Specified cast is not valid: {0} -> {1}", typeof (TFrom),
50-
typeof (T)));
50+
typeof (T)), e);
5151
}
5252
#else
5353
return Casters<TFrom>.Caster(obj);
@@ -77,10 +77,15 @@ private static Func<TFrom, T> Compile()
7777
{
7878
// Just return what we have
7979
var pExpr = Expression.Parameter(typeof(TFrom));
80-
80+
8181
return Expression.Lambda<Func<TFrom, T>>(pExpr, pExpr).Compile();
8282
}
8383

84+
if (typeof(T) == typeof(UIntPtr) && typeof(TFrom) == typeof(long))
85+
{
86+
return l => unchecked ((T) (object) (UIntPtr) (ulong) (long) (object) l);
87+
}
88+
8489
var paramExpr = Expression.Parameter(typeof(TFrom));
8590
var convertExpr = Expression.Convert(paramExpr, typeof(T));
8691

modules/platforms/dotnet/Apache.Ignite.sln.DotSettings

-1
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,4 @@
99
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertClosureToMethodGroup/@EntryIndexedValue">DO_NOT_SHOW</s:String>
1010
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002EXml_002ECodeStyle_002EFormatSettingsUpgrade_002EXmlMoveToCommonFormatterSettingsUpgrade/@EntryIndexedValue">True</s:Boolean>
1111
<s:Boolean x:Key="/Default/Environment/UnitTesting/ShadowCopy/@EntryValue">False</s:Boolean>
12-
<s:Boolean x:Key="/Default/Environment/UnitTesting/WrapLongLinesInUnitTestSessionOutput/@EntryValue">False</s:Boolean>
1312
</wpf:ResourceDictionary>

0 commit comments

Comments
 (0)