From 82ca47444d2fd4825c4fbceda1de5fe92f332801 Mon Sep 17 00:00:00 2001 From: Bartosz Sypytkowski Date: Thu, 5 Jan 2017 12:38:36 +0100 Subject: [PATCH 1/2] MarshalSerializer for structs --- Hyperion.Tests/Hyperion.Tests.csproj | 1 + Hyperion.Tests/MarshalSerializerTests.cs | 47 +++++++++++ Hyperion/Hyperion.csproj | 1 + .../MarshalSerializerFactory.cs | 79 +++++++++++++++++++ 4 files changed, 128 insertions(+) create mode 100644 Hyperion.Tests/MarshalSerializerTests.cs create mode 100644 Hyperion/SerializerFactories/MarshalSerializerFactory.cs diff --git a/Hyperion.Tests/Hyperion.Tests.csproj b/Hyperion.Tests/Hyperion.Tests.csproj index af2d89c6..25c99298 100644 --- a/Hyperion.Tests/Hyperion.Tests.csproj +++ b/Hyperion.Tests/Hyperion.Tests.csproj @@ -87,6 +87,7 @@ + diff --git a/Hyperion.Tests/MarshalSerializerTests.cs b/Hyperion.Tests/MarshalSerializerTests.cs new file mode 100644 index 00000000..d4c56d25 --- /dev/null +++ b/Hyperion.Tests/MarshalSerializerTests.cs @@ -0,0 +1,47 @@ +#region copyright +// ----------------------------------------------------------------------- +// +// Copyright (C) 2017-2017 Akka.NET Team +// +// ----------------------------------------------------------------------- +#endregion + +using Hyperion.SerializerFactories; +using Xunit; + +namespace Hyperion.Tests +{ + public class MarshalSerializerTests : TestBase + { + public struct MyStruct + { + public readonly int X; + public readonly int Y; + public readonly int Z; + + public MyStruct(int x, int y, int z) + { + X = x; + Y = y; + Z = z; + } + } + + public MarshalSerializerTests() + { + CustomInit(new Serializer(new SerializerOptions( + serializerFactories: new[] { new MarshalSerializerFactory(), }))); + } + + [Fact] + public void CanSerializerFullyValuedTypesUsingMarshaller() + { + var expected = new MyStruct(1337, 293256, 235034634); + + Serialize(expected); + Reset(); + var actual = Deserialize(); + Assert.Equal(expected, actual); + } + } +} \ No newline at end of file diff --git a/Hyperion/Hyperion.csproj b/Hyperion/Hyperion.csproj index b1dd63da..91641bd0 100644 --- a/Hyperion/Hyperion.csproj +++ b/Hyperion/Hyperion.csproj @@ -72,6 +72,7 @@ + diff --git a/Hyperion/SerializerFactories/MarshalSerializerFactory.cs b/Hyperion/SerializerFactories/MarshalSerializerFactory.cs new file mode 100644 index 00000000..b42e5e45 --- /dev/null +++ b/Hyperion/SerializerFactories/MarshalSerializerFactory.cs @@ -0,0 +1,79 @@ +#region copyright +// ----------------------------------------------------------------------- +// +// Copyright (C) 2017-2017 Akka.NET Team +// +// ----------------------------------------------------------------------- +#endregion + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using Hyperion.Extensions; +using Hyperion.ValueSerializers; + +namespace Hyperion.SerializerFactories +{ + public class MarshalSerializerFactory : ValueSerializerFactory + { + public override bool CanSerialize(Serializer serializer, Type type) => + !serializer.Options.VersionTolerance && IsFullValueType(type, new HashSet()); + + public override bool CanDeserialize(Serializer serializer, Type type) => + !serializer.Options.VersionTolerance && IsFullValueType(type, new HashSet()); + + private bool IsFullValueType(Type type, HashSet acknowledged) + { + if (!type.IsValueType) return false; + + acknowledged.Add(type); + + var fields = type.GetFieldInfosForType(); + + var result = true; + for (int i = 0; i < fields.Length; i++) + { + var fieldType = fields[i].FieldType; + result &= acknowledged.Contains(fieldType) || IsFullValueType(fieldType, acknowledged); + } + + return result; + } + + public override ValueSerializer BuildSerializer(Serializer serializer, Type type, ConcurrentDictionary typeMapping) + { + var ser = new ObjectSerializer(type); + typeMapping.TryAdd(type, ser); + + var size = Marshal.SizeOf(type); + ObjectWriter writer = (stream, value, session) => + { + var bin = new byte[size]; + + var ptr = Marshal.AllocHGlobal(size); + Marshal.StructureToPtr(value, ptr, true); + Marshal.Copy(ptr, bin, 0, size); + Marshal.FreeHGlobal(ptr); + + stream.Write(bin, 0, size); + }; + + ObjectReader reader = (stream, session) => + { + var ptr = Marshal.AllocHGlobal(size); + var bin = new byte[size]; + stream.Read(bin, 0, size); + + Marshal.Copy(bin, 0, ptr, size); + + var value = Marshal.PtrToStructure(ptr, type); + Marshal.FreeHGlobal(ptr); + return value; + }; + ser.Initialize(reader, writer); + + return ser; + } + } +} \ No newline at end of file From 1f99e879a554dd790b4077a48fa22a34ec64de44 Mon Sep 17 00:00:00 2001 From: Bartosz Sypytkowski Date: Thu, 5 Jan 2017 12:58:24 +0100 Subject: [PATCH 2/2] replaced Marshal with GCHandle --- .../SerializerFactories/MarshalSerializerFactory.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Hyperion/SerializerFactories/MarshalSerializerFactory.cs b/Hyperion/SerializerFactories/MarshalSerializerFactory.cs index b42e5e45..49f7fbba 100644 --- a/Hyperion/SerializerFactories/MarshalSerializerFactory.cs +++ b/Hyperion/SerializerFactories/MarshalSerializerFactory.cs @@ -51,24 +51,26 @@ public override ValueSerializer BuildSerializer(Serializer serializer, Type type { var bin = new byte[size]; - var ptr = Marshal.AllocHGlobal(size); + var handle = GCHandle.Alloc(bin, GCHandleType.Pinned); + var ptr = handle.AddrOfPinnedObject(); Marshal.StructureToPtr(value, ptr, true); Marshal.Copy(ptr, bin, 0, size); - Marshal.FreeHGlobal(ptr); stream.Write(bin, 0, size); + handle.Free(); }; ObjectReader reader = (stream, session) => { - var ptr = Marshal.AllocHGlobal(size); var bin = new byte[size]; stream.Read(bin, 0, size); + var handle = GCHandle.Alloc(bin, GCHandleType.Pinned); + var ptr = handle.AddrOfPinnedObject(); Marshal.Copy(bin, 0, ptr, size); var value = Marshal.PtrToStructure(ptr, type); - Marshal.FreeHGlobal(ptr); + handle.Free(); return value; }; ser.Initialize(reader, writer);