TheSwamp

Code Red => .NET => Topic started by: Keith™ on April 01, 2020, 09:12:44 PM

Title: Autocad crashing due to unreferenced object
Post by: Keith™ on April 01, 2020, 09:12:44 PM
This is another head scratcher.

I have a form class with some controls on it. It's irrelevant what controls at this point.
I invoke the form from an AutoCAD commandmethod, this is a static function so I either need to make the form static or have an explicit instance defined in the function.

Because I want to share data between drawings from within the form, I can't just create a new form in each drawing, so I have a private static instance defined.

In my commandmethod, I check null and disposed before operating on them, if either are true, I instantiate a new form, otherwise I just bring it to the front.

Herein lies the problem. If the form gets disposed somehow (it happens occasionally, still working on that) and I instantiate a new one, it will work fine in the current drawing, but if I open a new drawing or switch to a different drawing, the DocumentBecameCurrent event fires from the disposed form and crashes AutoCAD because everything has been disposed.

I've tried removing the event handler if the form is being disposed, but that doesn't seem to be working.

I am certain it has something to do with the static object getting disposed while an application level event is still active.

Thoughts?
Title: Re: Autocad crashing due to unreferenced object
Post by: MickD on April 01, 2020, 10:13:55 PM
Here's what I have for a similar situation, it handles the form and changing doc events, hope it helps.

Code - C#: [Select]
  1.         private static My_MainFrm form;
  2.         private static bool docevent;
  3.  
  4.         /// <summary>
  5.         /// Loads the MyApp Main Form
  6.         /// </summary>
  7.         [CommandMethod("MYAPP")]
  8.         public static void MyApp()
  9.         {
  10.             // set the doc event handler:
  11.             if (!docevent)
  12.             {
  13.                 _AcAp.Application.DocumentManager.DocumentBecameCurrent +=
  14.                        docCollDocActivated;
  15.                 docevent = true;
  16.             }
  17.  
  18.             if (form == null || form.IsDisposed)
  19.             {
  20.                 form = new My_MainFrm();
  21.             }
  22.             _AcAp.Application.ShowModelessDialog(null, form, false);
  23.         }
  24.        
  25.         // the doc event handler:
  26.         public static void docCollDocActivated(object senderObj, DocumentCollectionEventArgs docColDocActEvtArgs)
  27.         {
  28.             if (form != null && form.Visible)
  29.             {
  30.                 NativeMethods.SetFocusToEditor(_AcAp.Application.MainWindow.Handle);
  31.                 form.UpdateDocument(); // form method to update dialog with current doc data
  32.             }
  33.         }
  34.  

And the native methods helper class (there may be better ways now, this code is getting on a bit now :) ). The one and only method didn't need to go into it's own class but I was preparing for further native methods if/when required.
it just sets the focus back to the drawing editor when changing drawings or showing the dialog and may not be needed (but is handy).

Code - C#: [Select]
  1. public class NativeMethods
  2.     {
  3.         [DllImport("user32.dll")]
  4.         private static extern IntPtr SetFocus(IntPtr hwnd);
  5.  
  6.         public static void SetFocusToEditor(IntPtr hwnd){
  7.             SetFocus( hwnd );
  8.         }
  9.     }
  10.  
Title: Re: Autocad crashing due to unreferenced object
Post by: It's Alive! on April 01, 2020, 10:41:25 PM
How are you creating the events? In the form? If you might be able to override Dispose in the form to unload your reactor
Title: Re: Autocad crashing due to unreferenced object
Post by: Keith™ on April 01, 2020, 11:05:02 PM
How are you creating the events? In the form? If you might be able to override Dispose in the form to unload your reactor

In the form, but now that I've looked at the example MickD provided, I am thinking I should just move it to the commandmethod. This will ensure there is only ever one event handler and setting the bool prevents multiple handlers.

Thanks for the ideas...now to see if this resolves my problems.
Title: Re: Autocad crashing due to unreferenced object
Post by: gile on April 02, 2020, 02:25:13 AM
Hi,

Maybe I misunderstand the issue but, according it seems to me it's safer to always dispose a modal dialog after used, you can get the data from properties of the instance of dialog and store them as static members in the class containing the CommandMethod so that they're accessible between drawings.

Here's a minimalist example with a sort of input box.

Dilaog class:
Code - C#: [Select]
  1. using System.Windows.Forms;
  2.  
  3. namespace SeparationOfConcernSample
  4. {
  5.     public partial class DialogBox : Form
  6.     {
  7.         public string Data1 => textBox1.Text;
  8.  
  9.         public DialogBox(string data1)
  10.         {
  11.             InitializeComponent();
  12.             textBox1.Text = data1;
  13.         }
  14.     }
  15. }
  16.  

Commands class:
Code - C#: [Select]
  1. using Autodesk.AutoCAD.Runtime;
  2.  
  3. using System.Windows.Forms;
  4.  
  5. using AcAp = Autodesk.AutoCAD.ApplicationServices.Application;
  6.  
  7. namespace SeparationOfConcernSample
  8. {
  9.     public class Commands
  10.     {
  11.         static string data1 = "";
  12.  
  13.         [CommandMethod("INPUT")]
  14.         public static void InputCmd()
  15.         {
  16.             using (var dialog = new DialogBox(data1))
  17.             {
  18.                 if (AcAp.ShowModalDialog(dialog) == DialogResult.OK)
  19.                 {
  20.                     data1 = dialog.Data1;
  21.                 }
  22.             }
  23.         }
  24.  
  25.         [CommandMethod("OUTPUT")]
  26.         public static void OutputCmd()
  27.         {
  28.             AcAp.ShowAlertDialog(data1);
  29.         }
  30.     }
  31. }
  32.  
Title: Re: Autocad crashing due to unreferenced object
Post by: It's Alive! on April 02, 2020, 05:26:43 AM
I would try this, this way the form owns the reactor

Code - C#: [Select]
  1.     public partial class MyForm : Form
  2.     {
  3.         public MyForm()
  4.         {
  5.             InitializeComponent();
  6.             Bricscad.ApplicationServices.Application.DocumentManager.
  7.                 DocumentBecameCurrent += DocumentManager_DocumentBecameCurrent;
  8.         }
  9.  
  10.         private void DocumentManager_DocumentBecameCurrent(object sender, Bricscad.ApplicationServices.DocumentCollectionEventArgs e)
  11.         {
  12.             //
  13.         }
  14.     }
  15.  

and

Code - C#: [Select]
  1. partial class MyForm
  2.     {
  3.         /// <summary>
  4.         /// Required designer variable.
  5.         /// </summary>
  6.         private System.ComponentModel.IContainer components = null;
  7.  
  8.         /// <summary>
  9.         /// Clean up any resources being used.
  10.         /// </summary>
  11.         /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
  12.         protected override void Dispose(bool disposing)
  13.         {
  14.             if (disposing && (components != null))
  15.             {
  16.                 Bricscad.ApplicationServices.Application.
  17.                     DocumentManager.DocumentBecameCurrent -= DocumentManager_DocumentBecameCurrent; //<<<just add this line
  18.  
  19.                 components.Dispose();
  20.             }
  21.             base.Dispose(disposing);
  22.         }
  23.  
  24.         #region Windows Form Designer generated code
  25.  
  26.         /// <summary>
  27.         /// Required method for Designer support - do not modify
  28.         /// the contents of this method with the code editor.
  29.         /// </summary>
  30.         private void InitializeComponent()
  31.         {
  32.             this.components = new System.ComponentModel.Container();
  33.             this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
  34.             this.ClientSize = new System.Drawing.Size(800, 450);
  35.             this.Text = "MyForm";
  36.         }
  37.  
  38.         #endregion
  39.     }
  40.  
Title: Re: Autocad crashing due to unreferenced object
Post by: gile on April 02, 2020, 08:11:33 AM
Quote
Because I want to share data between drawings from within the form, I can't just create a new form in each drawing
Yes you can.
If you want the data to be stored in the Form class, you can expose them as static properties and from the Command side just show the dialog to edit the data.

Dialog class:
Code - C#: [Select]
  1. using System;
  2. using System.Windows.Forms;
  3.  
  4. namespace SeparationOfConcernSample
  5. {
  6.     public partial class DialogBox : Form
  7.     {
  8.         public static string Data { get; private set; } = "";
  9.  
  10.         public DialogBox()
  11.         {
  12.             InitializeComponent();
  13.             textBox1.Text = Data;
  14.         }
  15.  
  16.         private void buttonOK_Click(object sender, EventArgs e)
  17.         {
  18.             Data = textBox1.Text;
  19.         }
  20.     }
  21. }

Commands class:
Code - C#: [Select]
  1. using Autodesk.AutoCAD.Runtime;
  2.  
  3. using AcAp = Autodesk.AutoCAD.ApplicationServices.Application;
  4.  
  5. namespace SeparationOfConcernSample
  6. {
  7.     public class Commands
  8.     {
  9.         [CommandMethod("INPUT")]
  10.         public static void InputCmd()
  11.         {
  12.             using (var dialog = new DialogBox())
  13.             {
  14.                 AcAp.ShowModalDialog(dialog);
  15.             }
  16.         }
  17.  
  18.         [CommandMethod("OUTPUT")]
  19.         public static void OutputCmd()
  20.         {
  21.             AcAp.ShowAlertDialog(DialogBox.Data);
  22.         }
  23.     }
  24. }
  25.  
Title: Re: Autocad crashing due to unreferenced object
Post by: MickD on April 02, 2020, 05:24:52 PM
For what it's worth, the code I posted has been working in production app's for 6-7 years on 100's of pc's without issue. It saves it's settings on close and refreshes its data when switching drawing tabs. The only problem I've had is when the user goes from 2 screens to 1 they sometimes loose the dialog so I have a hacky command to bring it back to center of main screen :)

I let the Doc manager dispatch the event and the callback event handler just lets the current form know the doc has changed so it can do its thing. Swings and round-a-bouts I guess and I don't think that's the problem.
Dispose is called when the form is closed and I handle saving all the config prop's in the form closed event:

Code - C#: [Select]
  1.         private void My_MainFrm_FormClosing(object sender, FormClosingEventArgs e)
  2.         {
  3.             Properties.Settings.Default.FormSize = Size;
  4.             Properties.Settings.Default.DialogLocation = Location;
  5.             Properties.Settings.Default.OpenSaveDirectory = _lastOpenedDirectory;
  6.             Properties.Settings.Default.ZoomFactor = (int)numUpDownZoom.Value;
  7.             Properties.Settings.Default.Save();
  8.         }
  9.  
  10.  
Title: Re: Autocad crashing due to unreferenced object
Post by: Keith™ on April 02, 2020, 07:02:11 PM
Hi,

Maybe I misunderstand the issue but, according it seems to me it's safer to always dispose a modal dialog after used, you can get the data from properties of the instance of dialog and store them as static members in the class containing the CommandMethod so that they're accessible between drawings.

I actually force a dispose on the form when it closes. The event handler doesn't dispose but literally every other variable and control does. I believe this is because it is static.

I would try this, this way the form owns the reactor

The problem is that when the form is disposed, the event handler remains active. I determined this by grabbing the handle of the window on close. After disposing the form, and reinitializing a new one, the event handler gave me the handle to the disposed form, not the current form, despite the form being disposed AND the error handler being defined in the form.

Yes you can.
If you want the data to be stored in the Form class, you can expose them as static properties and from the Command side just show the dialog to edit the data.
Ok, so I'm not actually storing data between drawings. I am manipulating data across several drawings. Nearly everything is built as a palette. Opening, closing or activating a drawing should force the application to read certain data from the current drawing and display it with the option of putting it into another drawing, or as in the case of one function, changing a value of an attribute in one drawing will prompt an update in another open drawing provided certain criteria are met.

So the solution to my problem was to move the event handler to the class with the commandmethods, then handle the events there. That class is disposed when the application is unloaded irrespective of open or closed drawings. This has solved my problem.

Ultimately I think the issue is that once you define a variable in a static class, those variables apparently do not go out of scope just because the form holding them is disposed. It is working now though.
Title: Re: Autocad crashing due to unreferenced object
Post by: It's Alive! on April 02, 2020, 07:44:45 PM
[
The problem is that when the form is disposed, the event handler remains active. I determined this by grabbing the handle of the window on close. After disposing the form, and reinitializing a new one, the event handler gave me the handle to the disposed form, not the current form, despite the form being disposed AND the error handler being defined in the form.

Interesting, did you actually try removing the reactor on dispose? Anyway if your form is not being disposed, then you can unload the reactor on FormClosing.
DocumentManager.DocumentBecameCurrent -= DocumentManager_DocumentBecameCurrent.
MickD’s Static will work too.

I use this pattern all the time
Code: [Select]
Class
{
  Class()
  {
     Load reactor
  }
  ~Class()
  {
     Unload reactor
  }
}


Title: Re: Autocad crashing due to unreferenced object
Post by: MickD on April 02, 2020, 10:57:23 PM
I think it's becoming clearer now with more context.

Quote
Herein lies the problem. If the form gets disposed somehow (it happens occasionally, still working on that) and I instantiate a new one, it will work fine in the current drawing, but if I open a new drawing or switch to a different drawing, the DocumentBecameCurrent event fires from the disposed form and crashes AutoCAD because everything has been disposed.
I think you meant to say "the DocumentBecameCurrent event handler fires from the disposed form"
The problem with the quoted statement is that the DocumentBecameCurrent event isn't 'coming from' the form, it's being dispatched 'to' the form and if the form is disposed it should throw an exception on the null event handler method (but crashes in this case).

Quote
The problem is that when the form is disposed, the event handler remains active. I determined this by grabbing the handle of the window on close. <snip>
Again, the 'event dispatcher' remains active, the 'event handler' went with the disposed form.

Quote
After disposing the form, and re-initializing a new one, the event handler gave me the handle to the disposed form, not the current form, despite the form being disposed AND the error handler being defined in the form.
Again, replace event handler with dispatcher...
Given that, the event 'handler' registered to the 'dispatcher' is referenced to by the handle of the form that owns the event handler method but on dispose the form becomes null in the static variable.

Quote
Ultimately I think the issue is that once you define a variable in a static class, those variables apparently do not go out of scope just because the form holding them is disposed. It is working now though.
That's right, the variable can't go out of scope until the class that contains it does BUT the variable can become null when the form that was referenced by that variable becomes null (disposed).

Basically the Document was dispatching/sending an event message to a form that no longer exists. Every 'object' that needs to know when an event is fired needs to register its own callback (event handler) with the object that handles the event at a higher level (the event dispatcher). As Daniel showed, you can do this with the object that wants to handle an event when it occurs but you must de-register it when your object goes out of scope (disposed) to avoid this topic's problem.
Or you can do it how I have done it and just call whatever object is stored on the static variable, either way you are capturing the event and handling it indirectly.

Semantics maybe but I think that the problem is a lot clearer now, hope that helps anyone coming across a similar issue in the future :)



Title: Re: Autocad crashing due to unreferenced object
Post by: Keith™ on April 03, 2020, 08:58:28 PM
Ok yes, you are correct, the handler is in the form that was disposed, it is being raised from somewhere else .. obviously not the form.

Anyway, I've moved the handler outside of the form and put it into the static class for the application. As long as the application is active, the handler won't crash because I check to see of there is in fact a null object before calling a public function defined in said object.

It works, and that's a win!