Skip to content

Commit 57c3b57

Browse files
authored
Correctly dispose (again) the managed/native object relationship (#1344)
1 parent c23eab0 commit 57c3b57

File tree

2 files changed

+139
-1
lines changed

2 files changed

+139
-1
lines changed

binding/Binding/SKObject.cs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,22 @@ protected set {
6363
}
6464
}
6565

66+
protected override void DisposeUnownedManaged ()
67+
{
68+
if (ownedObjects != null) {
69+
foreach (var child in ownedObjects) {
70+
if (child.Value is SKObject c && !c.OwnsHandle)
71+
c.DisposeInternal ();
72+
}
73+
}
74+
}
75+
6676
protected override void DisposeManaged ()
6777
{
6878
if (ownedObjects != null) {
6979
foreach (var child in ownedObjects) {
70-
child.Value?.DisposeInternal ();
80+
if (child.Value is SKObject c && c.OwnsHandle)
81+
c.DisposeInternal ();
7182
}
7283
ownedObjects.Clear ();
7384
}
@@ -256,6 +267,11 @@ internal SKNativeObject (IntPtr handle, bool ownsHandle)
256267

257268
protected internal bool IsDisposed => isDisposed == 1;
258269

270+
protected virtual void DisposeUnownedManaged ()
271+
{
272+
// dispose of any managed resources that are not actually owned
273+
}
274+
259275
protected virtual void DisposeManaged ()
260276
{
261277
// dispose of any managed resources
@@ -271,9 +287,15 @@ protected virtual void Dispose (bool disposing)
271287
if (Interlocked.CompareExchange (ref isDisposed, 1, 0) != 0)
272288
return;
273289

290+
// dispose any objects that are owned/created by native code
291+
if (disposing)
292+
DisposeUnownedManaged ();
293+
294+
// destroy the native object
274295
if (Handle != IntPtr.Zero && OwnsHandle)
275296
DisposeNative ();
276297

298+
// dispose any remaining managed-created objects
277299
if (disposing)
278300
DisposeManaged ();
279301

tests/Tests/SKObjectTest.cs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,5 +484,121 @@ protected override void DisposeManaged()
484484
public static DelayedDestructionObject GetObject(IntPtr handle, bool owns = true) =>
485485
GetOrAddObject(handle, owns, (h, o) => new DelayedDestructionObject(h, o));
486486
}
487+
488+
[SkippableFact]
489+
public void ManagedObjectsAreDisposedBeforeNative()
490+
{
491+
var parent1 = ParentChildWorld.GetParent("Root");
492+
var child1 = parent1.Child;
493+
494+
parent1.Dispose();
495+
496+
var unownedParent = ParentChildWorld.DisposeUnownedManagedParent;
497+
var unownedChild = ParentChildWorld.DisposeUnownedManagedChild;
498+
499+
var managedParent = ParentChildWorld.DisposeManagedParent;
500+
var managedChild = ParentChildWorld.DisposeManagedChild;
501+
502+
var nativeParent = ParentChildWorld.DisposeNativeParent;
503+
var nativeChild = ParentChildWorld.DisposeNativeChild;
504+
505+
Assert.True(child1.IsDisposed);
506+
Assert.True(parent1.IsDisposed);
507+
508+
Assert.Equal("DisposeUnownedManaged", unownedParent.State);
509+
Assert.Equal("DisposeUnownedManaged", unownedChild.State);
510+
511+
Assert.Equal("DisposeUnownedManaged", managedParent.State);
512+
Assert.Equal("DisposeUnownedManaged", managedChild.State);
513+
514+
Assert.Equal("DisposeUnownedManaged", nativeParent.State);
515+
Assert.Equal("DisposeUnownedManaged", nativeChild.State);
516+
517+
unownedParent.Dispose();
518+
managedParent.Dispose();
519+
nativeParent.Dispose();
520+
}
521+
522+
private static class ParentChildWorld
523+
{
524+
public static readonly IntPtr ParentHandle = GetNextPtr();
525+
public static readonly IntPtr ChildHandle = GetNextPtr();
526+
527+
public static ParentObject GetParent(string state) =>
528+
SKObject.GetOrAddObject(ParentHandle, (h, o) => new ParentObject(state, h, o));
529+
530+
public static ParentObject DisposeManagedParent;
531+
public static ChildObject DisposeManagedChild;
532+
533+
public static ParentObject DisposeNativeParent;
534+
public static ChildObject DisposeNativeChild;
535+
536+
public static ParentObject DisposeUnownedManagedParent;
537+
public static ChildObject DisposeUnownedManagedChild;
538+
}
539+
540+
private class ParentObject : SKObject
541+
{
542+
public ParentObject(string state, IntPtr handle, bool owns)
543+
: base(handle, owns)
544+
{
545+
State = state;
546+
}
547+
548+
public ChildObject Child =>
549+
OwnedBy(GetOrAddObject(ParentChildWorld.ChildHandle, false, (h, o) => new ChildObject(State, h, o)), this);
550+
551+
public string State { get; }
552+
553+
protected override void DisposeUnownedManaged()
554+
{
555+
base.DisposeUnownedManaged();
556+
557+
if (State == "Root")
558+
{
559+
ParentChildWorld.DisposeUnownedManagedParent = ParentChildWorld.GetParent("DisposeUnownedManaged");
560+
ParentChildWorld.DisposeUnownedManagedChild = ParentChildWorld.DisposeUnownedManagedParent.Child;
561+
}
562+
}
563+
564+
protected override void DisposeManaged()
565+
{
566+
base.DisposeManaged();
567+
568+
if (State == "Root")
569+
{
570+
ParentChildWorld.DisposeManagedParent = ParentChildWorld.GetParent("DisposeManaged");
571+
ParentChildWorld.DisposeManagedChild = ParentChildWorld.DisposeManagedParent.Child;
572+
}
573+
}
574+
575+
protected override void DisposeNative()
576+
{
577+
base.DisposeNative();
578+
579+
if (State == "Root")
580+
{
581+
ParentChildWorld.DisposeNativeParent = ParentChildWorld.GetParent("DisposeNative");
582+
ParentChildWorld.DisposeNativeChild = ParentChildWorld.DisposeNativeParent.Child;
583+
}
584+
}
585+
586+
public override string ToString() =>
587+
$"Parent: {State}";
588+
}
589+
590+
private class ChildObject : SKObject
591+
{
592+
public ChildObject(string state, IntPtr handle, bool owns)
593+
: base(handle, owns)
594+
{
595+
State = state;
596+
}
597+
598+
public string State { get; }
599+
600+
public override string ToString() =>
601+
$"Child: {State}";
602+
}
487603
}
488604
}

0 commit comments

Comments
 (0)