@@ -85,18 +85,33 @@ public NSObjectDataHandle ()
8585 }
8686 }
8787
88+ public NSObjectDataHandle ( IntPtr handle )
89+ : base ( handle )
90+ {
91+ //Runtime.NSLog ($"NSObjectDataHandle (0x{handle:x}): wrapped existing pointer");
92+ }
93+
94+ public void Invalidate ( )
95+ {
96+ handle = IntPtr . Zero ;
97+ //Runtime.NSLog ($"NSObjectDataHandle.Disown (): disowned 0x{handle:x}");
98+ }
99+
88100 public unsafe NSObjectData * Data {
89101 get => ( NSObjectData * ) handle ;
90102 }
91103
92104 public override bool IsInvalid {
93- get => handle ! = IntPtr . Zero ;
105+ get => handle = = IntPtr . Zero ;
94106 }
95107
96108 protected override bool ReleaseHandle ( )
97109 {
98- unsafe {
99- NativeMemory . Free ( ( void * ) handle ) ;
110+ if ( handle != IntPtr . Zero ) {
111+ unsafe {
112+ NativeMemory . Free ( ( void * ) handle ) ;
113+ }
114+ //Runtime.NSLog ($"NSObjectDataHandle.ReleaseHandle (): released 0x{handle:x}");
100115 }
101116 handle = IntPtr . Zero ;
102117 return true ;
@@ -150,9 +165,19 @@ unsafe NativeHandle handle {
150165
151166 internal unsafe NSObjectData * GetData ( )
152167 {
153- return AllocateData ( ) . Data ;
168+ var rv = AllocateData ( ) . Data ;
169+
170+ //Runtime.NSLog ($"{GetType ().Name}.GetData () id={objectId} data: 0x{(IntPtr) rv}");
171+ if ( rv is null ) {
172+ //Runtime.NSLog ($"{GetType ().Name}.GetData (): no handle\n{Environment.StackTrace}");
173+ throw new ObjectDisposedException ( "No data???" ) ;
174+ }
175+
176+ return rv ;
154177 }
155178
179+ static int objectIdCounter ;
180+ int objectId ;
156181 unsafe NSObjectDataHandle AllocateData ( )
157182 {
158183 if ( data_handle is not null )
@@ -166,6 +191,9 @@ unsafe NSObjectDataHandle AllocateData ()
166191 return previousValue ;
167192 }
168193
194+ objectId = Interlocked . Increment ( ref objectIdCounter ) ;
195+ //Runtime.NSLog ($"{GetType ().Name}.AllocateData () id={objectId} allocated 0x{((IntPtr) data.Data):x}");
196+
169197 if ( ! Runtime . IsCoreCLR ) // This condition (and the assignment to __handle_for_mono if applicable) is trimmed away by the linker.
170198 __data_for_mono = data . Data ;
171199
@@ -999,10 +1027,54 @@ protected virtual void Dispose (bool disposing)
9991027 ReleaseManagedRef ( ) ;
10001028 } else {
10011029 NSObject_Disposer . Add ( this ) ;
1030+ RecreateDataHandle ( ) ;
10021031 }
10031032 }
10041033 }
10051034
1035+ #nullable enable
1036+ void RecreateDataHandle ( )
1037+ {
1038+ // OK, this code is _weird_.
1039+ // We need to delay the deletion of the native memory pointed to by data_handle until
1040+ // after this instance has been collected. A CriticalHandle seems to fit this purpose like glove, until
1041+ // you realize that a CriticalHandle is only kept alive until the parent object _becomes finalizable_,
1042+ // not _is collected_, which is very different - in other words, resurrected objects don't keep CriticalHandles
1043+ // they contain alive. This is a problem because every single managed NSObject instance is resurrected, and we
1044+ // need the native memory to stay alive after resurrection.
1045+ //
1046+ // So this solution depends on a few bits:
1047+ // * At this point, this instance may have become finalizable, but the native memory shouldn't have been freed yet.
1048+ // * The original NSObjectDataHandle (aka CriticalHandle) will be collected in this/upcoming GC cycle, and can't
1049+ // be trusted to keep the native memory alive anymore.
1050+ // * So we just create a new one, pointing to the same native memory, and replace the original NSObjectDataHandle (aka
1051+ // CriticalHandle) with it
1052+ // * This works, because since this instance has become / will become resurrected, it's not finalizable anymore,
1053+ // and it will keep the new NSObjectDataHandle instance (and the native memory it points to) alive.
1054+ // * Now if this instance is deemed finalizable, and then resurrected *again*, bad things will likely happen. This
1055+ // is a bit more unlikely though, because we don't re-register the finalizer for execution, so unless somebody
1056+ // else does that, it's quite unlikely this instance will become resurrected a second time.
1057+ var previous_data = data_handle ;
1058+ if ( previous_data is null ) {
1059+ throw new InvalidOperationException ( $ "Huh? (1)") ;
1060+ // return;
1061+ }
1062+
1063+ unsafe {
1064+ data_handle = new NSObjectDataHandle ( ( IntPtr ) previous_data . Data ) ;
1065+ }
1066+
1067+ if ( previous_data . IsInvalid ) {
1068+ throw new InvalidOperationException ( $ "Huh? (2)") ;
1069+ // return;
1070+ }
1071+
1072+ //Runtime.NSLog ($"{GetType ().Name}: id={objectId} previous value invalidated and disposed. Current flags: {flags}");
1073+ previous_data . Invalidate ( ) ;
1074+ previous_data . Dispose ( ) ;
1075+ }
1076+ #nullable disable
1077+
10061078 [ Register ( "__NSObject_Disposer" ) ]
10071079 [ Preserve ( AllMembers = true ) ]
10081080 internal class NSObject_Disposer : NSObject {
0 commit comments