Created
November 24, 2021 03:13
-
-
Save lostmsu/a4268ed09af2e87de9999f8abbbd997e to your computer and use it in GitHub Desktop.
BinaryFormatter serializes two distinct objects as one
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Diagnostics; | |
using System.Runtime.InteropServices; | |
using System.Runtime.Serialization; | |
using System.Runtime.Serialization.Formatters.Binary; | |
unsafe { | |
IntPtr resource1 = Marshal.AllocHGlobal(sizeof(FakeUnmanagedRefcountedObject)); | |
*(FakeUnmanagedRefcountedObject*)resource1 = default; // zero memory | |
var i1 = new UnmanagedRefcountWrapper(resource1); | |
i1.Value = 10; | |
var i2 = new UnmanagedRefcountWrapper(resource1); | |
Debug.Assert(i2.Value == 10 && i1.RefCount == 2); | |
var array = new[] { i1, i2 }; | |
var serializer = new BinaryFormatter(); | |
var storage = new MemoryStream(); | |
serializer.Serialize(storage, array); | |
// prints | |
// serializing 1908F1AE330 | |
// Debug.Assert(i1.RefCount == 4); <-- FAILS because i1.Equals(i2) so BinaryFormatter only serializes one of the two | |
// in the receiver | |
storage.Position = 0; | |
var array2 = serializer.Deserialize(storage); | |
// prints | |
// deserializing 1908F1AE330 | |
// deserializing 1908F1AE330 | |
// (e.g. twice) | |
// Debug.Assert(i1.RefCount == 4); <-- FAILS, same reason as above | |
} | |
/// <summary> | |
/// This class is just an example. The real class is implemented in unmanaged code with getters and setters exposed as DllImport-able functions | |
/// </summary> | |
[StructLayout(LayoutKind.Sequential)] | |
struct FakeUnmanagedRefcountedObject { | |
public int RefCount; | |
public int Value; | |
} | |
[Serializable] | |
unsafe sealed class UnmanagedRefcountWrapper : IDisposable { | |
IntPtr handle; | |
[OnSerialized] | |
void OnSerialized(StreamingContext context) { | |
Console.WriteLine($"serializing {(long)handle:X8}"); | |
Ref->RefCount++; | |
} | |
[OnDeserialized] | |
void OnDeserialized(StreamingContext context) { | |
Console.WriteLine($"deserializing {(long)handle:X8}"); | |
} | |
public int Value { | |
get => Ref->Value; | |
set => Ref->Value = value; | |
} | |
public int RefCount => Ref->RefCount; | |
FakeUnmanagedRefcountedObject* Ref => (FakeUnmanagedRefcountedObject*)handle; | |
public override bool Equals(object? obj) { | |
if (obj is not UnmanagedRefcountWrapper other) return false; | |
return Value == other.Value; | |
} | |
public override int GetHashCode() => Value.GetHashCode(); | |
public UnmanagedRefcountWrapper(IntPtr handle) | |
{ | |
this.handle = handle; | |
Ref->RefCount++; | |
} | |
public void Dispose() { | |
if (this.Ref is not null) { | |
this.Ref->RefCount--; | |
this.handle = IntPtr.Zero; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment