Bug 39432 - Object is being disposed prematurely
Summary: Object is being disposed prematurely
Status: RESOLVED ANSWERED
Alias: None
Product: iOS
Classification: Xamarin
Component: Xamarin.iOS.dll ()
Version: XI 9.8 (tvOS / C7)
Hardware: Macintosh Mac OS
: Normal normal
Target Milestone: Untriaged
Assignee: Manuel de la Peña [MSFT]
URL:
Depends on:
Blocks:
 
Reported: 2016-03-08 01:58 UTC by bryan costanich
Modified: 2016-03-11 09:39 UTC (History)
3 users (show)

Tags:
Is this bug a regression?: ---
Last known good build:


Attachments
repro solution (19.39 KB, application/zip)
2016-03-08 01:58 UTC, bryan costanich
Details
repro solution with objective-c marshaling error (17.78 KB, application/zip)
2016-03-08 03:44 UTC, bryan costanich
Details
Device logs for marshaling error. (80.38 KB, text/plain)
2016-03-08 03:45 UTC, bryan costanich
Details
Screen shot showing the constructors not quite working right (218.68 KB, image/png)
2016-03-08 15:54 UTC, bryan costanich
Details
Solution without infinite loop or crash. (18.56 KB, application/zip)
2016-03-08 20:44 UTC, Manuel de la Peña [MSFT]
Details


Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.


Please create a new report on Developer Community or GitHub with your current version information, steps to reproduce, and relevant error messages or log files if you are hitting an issue that looks similar to this resolved bug and you do not yet see a matching new report.

Related Links:
Status:
RESOLVED ANSWERED

Description bryan costanich 2016-03-08 01:58:46 UTC
Created attachment 15297 [details]
repro solution

I have a custom NSTextContainer that i'm using in an iOS app. Everything was working in the simulator just fine, but when I ran it on the device, I got an Objective-C err saying that I needed an IntPtr ctor, so I modified my code as follows (note that this is F#, so I can't really have a throw-away ctor like in c#):

type FastTextStorage(handle:IntPtr) as this =
  inherit NSTextStorage(handle)

  let mutable data = new NSMutableAttributedString ()

  new () = new FastTextStorage(IntPtr.Zero)

  ...


But now, when I create it, it seems to get disposed immediately. Note the following code:


module foo =

  let mutable myTexty:NSTextContainer = null

  let CreateFastTextContainer(size:CGSize) = 
    let textContainer = new NSTextContainer (CGSize (size.Width, nfloat.MaxValue), WidthTracksTextView = true)
    let textLayout = new NSLayoutManager ()
    let textStorage = new FastTextStorage ()
    textLayout.AddTextContainer (textContainer)
    textStorage.AddLayoutManager (textLayout) //<- ERROR HAPPENS HERE

    myTexty <- textContainer
    textContainer

Even though i've just created the textStorage object, it fails almost immediately with the following error:

Cannot access a disposed object.
Object name: 'FastTextStorage'.
Comment 1 Sebastien Pouliot 2016-03-08 02:30:36 UTC
> new () = new FastTextStorage(IntPtr.Zero)

Never, ever, call any NSObject-subclass .ctor(IntPtr) with a `IntPtr.Zero` value. That's, at best, creating a managed instance that _looks_ like a disposed object (since it's handle is nil) and can result in crashes (for any native API that does not to a null check).

That ctor is only to be used when the object is surfaced to managed code. It is not meant to be used by customer code (and all of Xamarin.iOS.dll* types have this .ctor as protected to avoid this).

* it was not the case for classic and a source of bugs in applications.
Comment 2 bryan costanich 2016-03-08 02:35:59 UTC
So then how should I construct the FastTextStorage class? 

If i don't have that intptr constructor, i get this err on the device:

Failed to marshal the Objective-C object 0x155d95ad0 (type: Brightwood_FastTextStorage_FastTextStorage). Could not find an existing managed instance for this object, nor was it possible to create a new managed instance (because the type 'Brightwood.FastTextStorage.FastTextStorage' does not have a constructor that takes one IntPtr argument).

This also raises a more general question: in F#, what's the correct pattern here? For objects that need to be instantiated from xibs and not.
Comment 3 Sebastien Pouliot 2016-03-08 02:42:19 UTC
You need both a default .ctor and a .ctor(IntPtr). Each calling their base .ctor (of the same signature).

The first one is for user code, i.e. when a managed instance creates a native instance. The 2nd one is when a native instance is created first (e.g. XIB) and a managed instance is surfaced later.
Comment 4 bryan costanich 2016-03-08 02:45:15 UTC
i'm not sure that's possible in F#.

https://msdn.microsoft.com/en-us/library/dd233192.aspx
Comment 5 bryan costanich 2016-03-08 02:52:33 UTC
This is also confusing:

Failed to marshal the Objective-C object 0x155d95ad0 (type: Brightwood_FastTextStorage_FastTextStorage). Could not find an existing managed instance for this object, nor was it possible to create a new managed instance (because the type 'Brightwood.FastTextStorage.FastTextStorage' does not have a constructor that takes one IntPtr argument).

Is there something else going on there? I'm not calling from a xib. it shouldn't even need that ctor, yeah?
Comment 6 Sebastien Pouliot 2016-03-08 03:29:00 UTC
wrt comment #4 if F# can't have multiple .ctor then it sounds like a bad choice to bind (or extend many) NSObject subclasses. The `init*` methods all requires different .ctor that calls different native methods. Since I never heard of that I suspect there's some workarounds that F# people could tell you about. I have not spent enough time with it to help you there.


wrt comment #5 anything that surface a native object (into managed code) will require this .ctor, instantiating a XIB file is a common case but not the only one where this happens.

The attached sample (changed not to use IntPtr.Zero) seems to crash from endless recursion (on device, works fine on simulator). Maybe it's some local changes I have at the moment... but, just to be sure, please attach all the logs (application output and device logs) that you get.
Comment 7 bryan costanich 2016-03-08 03:44:20 UTC
Sebastien, thanks for the input.

#4 - i'll ask around. 

#5 - where are you seeing the recursion? i'm only seeing that failed to marshal error. and i'm seeing it only on device. see new attachment for repro. 



--- App output 
Please ensure your device is connected...
Connected to: bryan's iPad pro
Launched application 'com.brightwood.brightwood-fasttextstorage' on 'bryan's iPad pro' with pid 740
2016-03-07 19:37:57.532 Brightwood.FastTextStorage[740:642533] Xamarin.iOS: IDE Port: 10000 Transport: USB
2016-03-07 19:37:57.563 Brightwood.FastTextStorage[740:642533] Xamarin.iOS: Successfully received USB connection from the IDE on port 10000, fd: 5
2016-03-07 19:37:57.564 Brightwood.FastTextStorage[740:642533] Xamarin.iOS: Processing: 'start debugger: sdb'
2016-03-07 19:37:57.564 Brightwood.FastTextStorage[740:642524] Xamarin.iOS: Debugger loaded with custom transport (fd: 5)
2016-03-07 19:37:57.565 Brightwood.FastTextStorage[740:642533] Xamarin.iOS: Successfully received USB connection from the IDE on port 10000, fd: 6
2016-03-07 19:37:57.566 Brightwood.FastTextStorage[740:642533] Xamarin.iOS: Processing: 'connect output'
2016-03-07 19:37:57.566 Brightwood.FastTextStorage[740:642533] Xamarin.iOS: Successfully received USB connection from the IDE on port 10000, fd: 7
2016-03-07 19:37:57.568 Brightwood.FastTextStorage[740:642533] Xamarin.iOS: Processing: 'start profiler: no'
2016-03-07 19:37:57.568 Brightwood.FastTextStorage[740:642524] Xamarin.iOS: Profiler not loaded (disabled)
Loaded assembly: /private/var/mobile/Containers/Bundle/Application/9B42870C-96EB-4C62-81C3-3B524EE04AF6/Brightwood.FastTextStorage.app/.monotouch-64/System.dll [External]
Loaded assembly: /private/var/mobile/Containers/Bundle/Application/9B42870C-96EB-4C62-81C3-3B524EE04AF6/Brightwood.FastTextStorage.app/.monotouch-64/Mono.Dynamic.Interpreter.dll [External]
Loaded assembly: /private/var/mobile/Containers/Bundle/Application/9B42870C-96EB-4C62-81C3-3B524EE04AF6/Brightwood.FastTextStorage.app/.monotouch-64/System.Core.dll [External]
Loaded assembly: /private/var/mobile/Containers/Bundle/Application/9B42870C-96EB-4C62-81C3-3B524EE04AF6/Brightwood.FastTextStorage.app/.monotouch-64/Xamarin.iOS.dll [External]
Loaded assembly: /private/var/mobile/Containers/Bundle/Application/9B42870C-96EB-4C62-81C3-3B524EE04AF6/Brightwood.FastTextStorage.app/.monotouch-64/FSharp.Core.dll [External]
Loaded assembly: /private/var/mobile/Containers/Bundle/Application/9B42870C-96EB-4C62-81C3-3B524EE04AF6/Brightwood.FastTextStorage.app/.monotouch-64/Brightwood.FastTextStorage.exe
Thread started: Finalizer #2
Comment 8 bryan costanich 2016-03-08 03:44:50 UTC
Created attachment 15299 [details]
repro solution with objective-c marshaling error
Comment 9 bryan costanich 2016-03-08 03:45:40 UTC
Created attachment 15300 [details]
Device logs for marshaling error.
Comment 10 Manuel de la Peña [MSFT] 2016-03-08 10:19:40 UTC
Hi!

The following code will fix the issue. You indeed can have several constructors in an F# class that calls the base constructor as follows:


type FastTextStorage =
  inherit NSTextStorage
  val mutable internal data : NSMutableAttributedString
  new () = { inherit NSTextStorage (); data = new NSMutableAttributedString () }
  new (coder: NSCoder) = {inherit NSTextStorage (coder); data = new NSMutableAttributedString ()}
  new (handle: IntPtr) = { inherit NSTextStorage (handle); data = new NSMutableAttributedString ()}

Notice the usage of an explicit field that will be init in the diff constructors. 

Let me know if it works for you :)
Comment 11 Manuel de la Peña [MSFT] 2016-03-08 10:32:34 UTC
Also, we do have an endless recursion happening at:

override this.LowLevelGetAttributes(location, [<System.Runtime.InteropServices.OutAttribute>] outRange)

No idea why, but it is not related to this bug. (FYI I would add also a type notation to the out range to state is a byref<NSRange>)
Comment 12 bryan costanich 2016-03-08 15:54:11 UTC
Created attachment 15309 [details]
Screen shot showing the constructors not quite working right

RE: 10 - I'm not able to get that to compile, were you? data is no longer defined in the rest of the class. Adding a screen shot.
Comment 13 Manuel de la Peña [MSFT] 2016-03-08 16:14:22 UTC
You need to use the this keyword for that to work because it is an explicit field. Just do:

this.data.FooMethod ()

Info: https://msdn.microsoft.com/en-us/library/dd469494.aspx
Comment 14 Manuel de la Peña [MSFT] 2016-03-08 20:33:18 UTC
Adding a project that fixes the infinite loop, nevertheless, I think that the format method is wrong and that careful thinking has to be done with it. I have just focused on fixing the loop and making sure that some text is displayed but did not went through the entire logic of the solution, I just wanted to point you to the right path.

There were some interesting issues, the Export attr was used on methods when the best option was to overload them and a bunch of other changes. 

I hope it helps to get back on track!
Comment 15 Manuel de la Peña [MSFT] 2016-03-08 20:44:11 UTC
Created attachment 15317 [details]
Solution without infinite loop or crash.
Comment 16 Sebastien Pouliot 2016-03-09 15:11:42 UTC
Sounds complete, but I'll let Manuel and you close it.
Comment 17 bryan costanich 2016-03-10 21:23:31 UTC
Woohoo!  Ok, thanks to Manuel and Frank Krueger's helped we solved it. But we're missing a binding:

[<Export ("replaceCharactersInRange:withString:")>]
member this.ReplaceCharactersInRange (range : NSRange, str : NSString)

Because that's the method that gets called for updates, not 
override this.Replace (range: NSRange, str: NSAttributedString)


Sebastien or Manuel, can one of you add this binding, please?
Comment 18 bryan costanich 2016-03-10 21:23:58 UTC
re-opening for the binding.
Comment 19 Manuel de la Peña [MSFT] 2016-03-11 09:39:25 UTC
The binding is already present:

[Export ("replaceCharactersInRange:withString:")]
public virtual void Replace (NSRange range, string newValue)

As well as the other one that was used:

[Export ("replaceCharactersInRange:withAttributedString:")]
public virtual void Replace (NSRange range, NSAttributedString value)

In our API both are called Replace since we can overload methods and is a lot nicer than the obj-c approach (and more c#).

Docs: https://developer.xamarin.com/api/member/MonoTouch.Foundation.NSMutableAttributedString.Replace/p/MonoTouch.Foundation.NSRange/System.String/