binding/SkiaSharp/HandleDictionary.cs (172 lines of code) (raw):
#nullable disable
using System;
using System.Collections.Generic;
using System.Threading;
using System.Runtime.InteropServices;
#if THROW_OBJECT_EXCEPTIONS
using System.Collections.Concurrent;
#endif
using SkiaSharp.Internals;
namespace SkiaSharp
{
internal static class HandleDictionary
{
private static readonly Type SkipObjectRegistrationType = typeof (ISKSkipObjectRegistration);
#if THROW_OBJECT_EXCEPTIONS
internal static readonly ConcurrentBag<Exception> exceptions = new ConcurrentBag<Exception> ();
#endif
internal static readonly Dictionary<IntPtr, WeakReference> instances = new Dictionary<IntPtr, WeakReference> ();
#if DEBUG
internal static readonly Dictionary<IntPtr, string> stackTraces = new Dictionary<IntPtr, string> ();
#endif
internal static readonly IPlatformLock instancesLock = PlatformLock.Create ();
/// <summary>
/// Retrieve the living instance if there is one, or null if not.
/// </summary>
/// <returns>The instance if it is alive, or null if there is none.</returns>
internal static bool GetInstance<TSkiaObject> (IntPtr handle, out TSkiaObject instance)
where TSkiaObject : SKObject
{
if (handle == IntPtr.Zero) {
instance = null;
return false;
}
if (SkipObjectRegistrationType.IsAssignableFrom (typeof (TSkiaObject))) {
instance = null;
return false;
}
instancesLock.EnterReadLock ();
try {
return GetInstanceNoLocks (handle, out instance);
} finally {
instancesLock.ExitReadLock ();
}
}
/// <summary>
/// Retrieve or create an instance for the native handle.
/// </summary>
/// <returns>The instance, or null if the handle was null.</returns>
internal static TSkiaObject GetOrAddObject<TSkiaObject> (IntPtr handle, bool owns, bool unrefExisting, Func<IntPtr, bool, TSkiaObject> objectFactory)
where TSkiaObject : SKObject
{
if (handle == IntPtr.Zero)
return null;
if (SkipObjectRegistrationType.IsAssignableFrom (typeof (TSkiaObject))) {
#if THROW_OBJECT_EXCEPTIONS
throw new InvalidOperationException (
$"For some reason, the object was constructed using a factory function instead of the constructor. " +
$"H: {handle.ToString ("x")} Type: {typeof (TSkiaObject)}");
#else
return objectFactory.Invoke (handle, owns);
#endif
}
instancesLock.EnterUpgradeableReadLock ();
try {
if (GetInstanceNoLocks<TSkiaObject> (handle, out var instance)) {
// some object get automatically referenced on the native side,
// but managed code just has the same reference
if (unrefExisting && instance is ISKReferenceCounted refcnt) {
#if THROW_OBJECT_EXCEPTIONS
if (refcnt.GetReferenceCount () == 1)
throw new InvalidOperationException (
$"About to unreference an object that has no references. " +
$"H: {handle.ToString ("x")} Type: {instance.GetType ()}");
#endif
refcnt.SafeUnRef ();
}
return instance;
}
var obj = objectFactory.Invoke (handle, owns);
return obj;
} finally {
instancesLock.ExitUpgradeableReadLock ();
}
}
/// <summary>
/// Retrieve the living instance if there is one, or null if not. This does not use locks.
/// </summary>
/// <returns>The instance if it is alive, or null if there is none.</returns>
private static bool GetInstanceNoLocks<TSkiaObject> (IntPtr handle, out TSkiaObject instance)
where TSkiaObject : SKObject
{
if (instances.TryGetValue (handle, out var weak) && weak.IsAlive) {
if (weak.Target is TSkiaObject match) {
if (!match.IsDisposed) {
instance = match;
return true;
}
#if THROW_OBJECT_EXCEPTIONS
} else if (weak.Target is SKObject obj) {
if (!obj.IsDisposed && obj.OwnsHandle) {
throw new InvalidOperationException (
$"A managed object exists for the handle, but is not the expected type. " +
$"H: {handle.ToString ("x")} Type: ({obj.GetType ()}, {typeof (TSkiaObject)})");
}
} else if (weak.Target is object o) {
throw new InvalidOperationException (
$"An unknown object exists for the handle when trying to fetch an instance. " +
$"H: {handle.ToString ("x")} Type: ({o.GetType ()}, {typeof (TSkiaObject)})");
#endif
}
}
instance = null;
return false;
}
/// <summary>
/// Registers the specified instance with the dictionary.
/// </summary>
internal static void RegisterHandle (IntPtr handle, SKObject instance)
{
if (handle == IntPtr.Zero || instance == null)
return;
if (instance is ISKSkipObjectRegistration)
return;
SKObject objectToDispose = null;
instancesLock.EnterWriteLock ();
try {
if (instances.TryGetValue (handle, out var oldValue) && oldValue.Target is SKObject obj && !obj.IsDisposed) {
#if THROW_OBJECT_EXCEPTIONS
if (obj.OwnsHandle) {
// a mostly recoverable error
// if there is a managed object, then maybe something happened and the native object is dead
throw new InvalidOperationException (
$"A managed object already exists for the specified native object. " +
$"H: {handle.ToString ("x")} Type: ({obj.GetType ()}, {instance.GetType ()})");
}
#endif
// this means the ownership was handed off to a native object, so clean up the managed side
objectToDispose = obj;
}
instances[handle] = new WeakReference (instance);
#if DEBUG
stackTraces[handle] = Environment.StackTrace;
#endif
} finally {
instancesLock.ExitWriteLock ();
}
// dispose the object we just replaced
objectToDispose?.DisposeInternal ();
}
/// <summary>
/// Removes the registered instance from the dictionary.
/// </summary>
internal static void DeregisterHandle (IntPtr handle, SKObject instance)
{
if (handle == IntPtr.Zero)
return;
if (instance is ISKSkipObjectRegistration)
return;
instancesLock.EnterWriteLock ();
try {
var existed = instances.TryGetValue (handle, out var weak);
if (existed && (!weak.IsAlive || weak.Target == instance)) {
instances.Remove (handle);
#if DEBUG
stackTraces.Remove (handle);
#endif
} else {
#if THROW_OBJECT_EXCEPTIONS
InvalidOperationException ex = null;
if (!existed) {
// the object may have been replaced
if (!instance.IsDisposed) {
// recoverable error
// there was no object there, but we are still alive
ex = new InvalidOperationException (
$"A managed object did not exist for the specified native object. " +
$"H: {handle.ToString ("x")} Type: {instance.GetType ()}");
}
} else if (weak.Target is SKObject o && o != instance) {
// there was an object in the dictionary, but it was NOT this object
if (!instance.IsDisposed) {
// recoverable error
// there was a new living object there, but we are still alive
ex = new InvalidOperationException (
$"Trying to remove a different object with the same native handle. " +
$"H: {handle.ToString ("x")} Type: ({o.GetType ()}, {instance.GetType ()})");
}
}
if (ex != null) {
if (instance.fromFinalizer)
exceptions.Add (ex);
else
throw ex;
}
#endif
}
} finally {
instancesLock.ExitWriteLock ();
}
}
}
}