Skip to content

Instantly share code, notes, and snippets.

@lostmsu
Created November 24, 2021 03:13
Show Gist options
  • Save lostmsu/a4268ed09af2e87de9999f8abbbd997e to your computer and use it in GitHub Desktop.
Save lostmsu/a4268ed09af2e87de9999f8abbbd997e to your computer and use it in GitHub Desktop.
BinaryFormatter serializes two distinct objects as one
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