Revit API Forum
Welcome to Autodesk’s Revit API Forums. Share your knowledge, ask questions, and explore popular Revit API topics.
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Document throws "InvalidObjectException" in Idling Event

12 REPLIES 12
Reply
Message 1 of 13
waleed511
1479 Views, 12 Replies

Document throws "InvalidObjectException" in Idling Event

Hi guys,

I have a modeless dialog and I use Idling event to handle its evets. So the code goes like this

 

First the External Command:

Result MenuHandler::Execute(ExternalCommandData^ commandData, String^% message, ElementSet^ elements)
{
    Transaction^ transaction = gcnew Transaction(commandData->Application->ActiveUIDocument->Document, "Menu Handler");
    transaction->Start();

    // Here I create a new Form and Show it

    transaction->Commit();
    return Result:: Succeeded;
} //MenuHandler::Execute

 

Then I queue events from the modeless dialog to be all handled when the Idling event is fired.
And the code for the IdleHandler is

 

System::Void FormsHandler:: onIdling(Object^ sender, IdlingEventArgs^ args)
{
     UIApplication^ uiApplication = dynamic_cast<Autodesk::Revit::UI::UIApplication^>(sender);
     if (uiApplication == nullptr)
           return;
         //This Next line throws 'Autodesk.Revit.Exceptions.InvalidObjectException' occurred in RevitAPI.dll
         //Additional information: The managed object is not valid.
         Transaction^ transaction = gcnew Transaction(uiApplication->ActiveUIDocument->Document, "Idle Handling");
          transaction->Start();

          //Here I handle each event in the queue and then empty the queue

          transaction->Commit();
} //FormsHandler:: onIdling

 

Can anyone help with this, whenever I try to use the Document object it throws InvalidObjectException, I assume it has something to do with the transaction but I am not sure what. Am I missing something ?

 

Thank you

12 REPLIES 12
Message 2 of 13

Hi

 

I made a quick test on my side, it works fine, but we need to check if the Document is not null before try to use it to start a transaction.

 

As you are trying to get data on the background, I would suggest you check the main properties, such as ActiveUIDocument and Document, before accessing it.

 

Regards,



Augusto Goncalves
Twitter @augustomaia
Autodesk Developer Network
Message 3 of 13

Augusto has a very valid point. It is not necessary that there always is an active document open in Revit. The Idling event would be raised even when no document is open yet, or after the last document was closed by the end user. Therefore, it is important to check if the ActiveUIDocument is actually valid.

I have a few more comments/questions:

  1. I see one thing missing in the code shown and that may be essential. Where does the subscribing to the Idling event happens?
  2. What is the point of instantiating and showing a modeless dialog in the middle of document transaction? (Please remember, you cannot make any changes to the document form the modeless dialog, or at least you are not supposed to.)
  3. Why does the Idling event handler assume that the currently active document is the one in which changes are to be made? Please keep in mind the end user may switch between documents without Revit having raised any idling event (which is only raised when the end user is doing nothing).
  4. What does it mean: “handle each event in the queue and then empty the queue,” particularly the “empty the queue” part.

Thank you

Arnošt Löbel

Autodesk Revit R&D

Arnošt Löbel
Message 4 of 13
waleed511
in reply to: arnostlobel

Hi guys, thanks for the replies.

 

@Augusto: Yes I check if it is null I just didn't add that part of the code, so these checks are there

if (uiApplication == nullptr || uiApplication->ActiveUIDocument == nullptr)
        return;
host.setUIApplication(uiApplication); //this is my host don't worry about it
if (uiApplication->ActiveUIDocument->Document != nullptr) {

      Transaction^ transaction = gcnew Transaction(uiApplication->ActiveUIDocument->Document, "Idle Handling");
      transaction->Start();
      host.getProject()->setDocument(uiApplication->ActiveUIDocument->Document);
      handleAsynchronous();
      transaction->Commit();
}

 

@Arnošt

 

1 - I subscribe to the Idling event in "OnStartup" of the ExternalApplication

if (host.getUIControlledApplication() != nullptr)
     host.getUIControlledApplication()->Idling += gcnew EventHandler<IdlingEventArgs^>(&FormsHandler::onIdling);

 

2 - A transaction is created there because whenever I show a dialog I update (if found) or create a new DataStorage element.

I can't make any changes ? cause I assumed that if I don't handle the events from the modeless dialog and wait to handle all the events when Idling is called that should work fine.

So my modeless dialog has a button to insert new elements, so when the user clicks the button, I save a queue containing all the information about the user click and what to add, and when the Idling event is fired I create all the elements based on the data I saved and empty that queue so I not to add more elements than the number of clicks by the user.

 

3 - Yes the modeless dialog is designed so that the elements will be added to whatever document the user has active at that moment.

 

4 - It means that I don't handle user events at the same instance they click the insert element button on the dialog cause then the Document and Application pointers will be invalid, so I queue user actions till the Idling event is fired and I assumed that I can make any changes at this point, the Idling event that is.

 

Cheers

 

 

 

Message 5 of 13

Hi,

 

I would suggest you also check the ActiveUIDocument property....on my testing, this was NULL in many scenarios.

 

 

Regards,



Augusto Goncalves
Twitter @augustomaia
Autodesk Developer Network
Message 6 of 13

Hi,

 

Yes I do check for it in the first line above

if (uiApplication == nullptr || uiApplication->ActiveUIDocument == nullptr)
        return;

 

The problem is this works fine the first time it goes through, but when I try to create any Transaction after that throws the same exception.

 

So I have two places where I create transactions or use the Document object,

 1 - When a user click the ribbon button (in the Execute of the ExternalCommand) to open the dialog.

 2 - When Idling event is fired.

Whichever one of them is called first works the first time, but any subsequent calls throw the same exception.

 

Am I missing something in the way I should handle transactions ? or should I use TransactionGroup instead ?

 

 

Message 7 of 13
arnostlobel
in reply to: waleed511

Hello Waleed511,

 

Let me answer your last question as first, before I forget: No, you cannot use transaction groups in this scenario. A transaction group does not let you modify documents, and groups too, like transactions, must be closed before returning from you code back to Revit.

 

Now, back to our bullet list:

  1. Thanks for that extra information. You approach is correct.
  2. I did not mean you that you cannot do anything from your modeless dialog ever. Naturally, if you subscribe to Idling and you event handler happens to be part of the dialog, you can handle it there. However:
    1. You cannot subscribe to idling from that dialog. You have to subscribe from the external command code (or from OnStartup code).
    2. While you are in the external command, between the Transaction.Start and Commit methods, you cannot do anything (in Revit, I mean) from the modeless dialog that you show there. It is because the modeless dialog will start on a different thread. You have to finish your external command first. That is why I asked what was the point of showing the dialog before the transaction ended.
  3. Thank you. I just wanted to understand the workflow better.
  4. Oh, thanks. So those are your command events and your event queue, not Revit events. Thanks for clearing that up.

 

None of my comments above would explain why you get the exceptions. My remarks and notes just clarify the workflow. I do not see anything suspicious in your event handler, assuming that you indeed check validity of both UIApplication and UIDocument before you try to create the transaction. On the other hand, the InvalidObjectException is only thrown when a managed instance of a Revit object (like a Document, Element, etc.) does not point to a valid native object in Revit. For example, if you acquire an element from Revit and then you delete the element from the document, you still have your Element object variable (and not null) in your code, but the actual element does not exist anymore in Revit. If you then attempt to invoke any method on your Element variable, you would get this kind of Exception.

 

I suggest you try to find out what object particular is invalid. Please add the following lines to your code before the line on which you instantiate the transaction: 

 

UIDocument uidoc = UIApplication->ActiveUIDocument;
Document dbdoc = UIDocument->Document;
String title = dbdoc->Title;
Transaction trans = gcnew Transaction(dbdoc,"idle event");

 

On which line do you get the exception?

 

Thank you

 

Arnošt Löbel

Autodesk Revit R&D

Arnošt Löbel
Message 8 of 13
waleed511
in reply to: arnostlobel

Hi Arnošt,

 

I added the 4 lines of code.

 

- The first time the onIdling function is called it passes as expected, a transaction is started and commited with no problems at all.

 

- But the second time the same function is called, "InvalidObjectException" is thrown on the Third line (accessing the document Title), so the DB Document is the invalid object, and I checked it is not null it is just invalid.

 

Any idea on why it works only the first time ?

 

Message 9 of 13
arnostlobel
in reply to: waleed511

Hello waleed511,

I do not have good news for you. What you experience is not supposed to happen. In fact, I have not heard about a case like this yet. We need to continue with thorough investigation. Possibly, you can help us with that.

 

Before we go any further, we need more information about your setting. Please tell me what version of Revit you are using (what build, exactly), and what is your developer’s environment (VS version, NET version, etc.).

 

Here’s is what I am thinking. First, let’s sum up what is happening:

  1. You subscribe to Revit Idling event. The subscribing happens during (and from) the OnStartup method of your External application.
  2. On the second (and consequent) invocation of the event the following happens:
    1. You cast the 'sender' attribute to UIApplication object; that is not NULL.
    2. You get ActiveUIDocument from the UIApplication; that is also not NULL.
    3. You get Document from the UIDocument; the object is not null; however, invoking methods (like Title) on that object cause InvalidObjectException, which means that the API object is not linked to an actual document object (native) in Revit.

 

I will follow up with our team and also with ADN team. (You are not an ADN member, are you?) We will try to reproduce it once we have the information I asked you about (Revit version, NET version, etc.).

The way you describe the problem and the situation makes me assume that it is not related with your modeless dialog. Since you subscribe to the event from your OnStartup (and by that I mean the actual code of the OnStartup method), and then you simply handle the event every time it is raised, the fact that you have some other modeless dialog(s) around should not affect the workflow of handling the event. For better illustration I am going to post a simplified application that shows just the code we are interested in:

 

namespace IdlingTests
{
   public class TestApplication : Autodesk.Revit.UI.IExternalApplication
   {
      private string docTitle = null;

      public Result OnStartup(UIControlledApplication application)
      {
         application.Idling += OnIdling;
         return Result.Succeeded;
      }

      public Result OnShutdown(UIControlledApplication application)
      {
         return Result.Succeeded;
      }

      internal void OnIdling(Object sender, IdlingEventArgs args)
      {
         UIApplication app = sender as UIApplication;
         if (app.ActiveUIDocument != null)
         {
            try
            {
               docTitle = app.ActiveUIDocument.Document.Title;
            }
            catch (Autodesk.Revit.Exceptions.InvalidObjectException )
            {
               TaskDialog.Show("Exception", "Invalid Active Document Object!" );
               app.Idling -= OnIdling;
            }
         }
      }
   }
}

 

The code shown is in C#, but it is going to be practically the came in C++/CLI. It would be greatly appreciated if you could confirm that this simple application still demonstrates the same problem.

 

What leaves me puzzled about this thing is that if the Document object gets somehow invalidated between Idling events, how come we do not hear about it more often (or ever, in fact.) The Idling event is used by many developers, both internal and external, and it seems to be working well. I do not want to jump to any conclusion yet; I just wanted to state this as a fact. Something is happening in your application that is not happening in other applications – we need to find out what that is.

 

Thank you

 

Arnošt Löbel

Autodesk Revit R&D

Arnošt Löbel
Message 10 of 13
waleed511
in reply to: arnostlobel

Hi Arnošt,

 

Thank you very much for your reply, 

I tried the C# code and it works fine, so I must be doing something wrong in C++ as I am mixing managed and unmanaged C++ code.

I will look more into my C++ implementation.

 

Cheers

Message 11 of 13
arnostlobel
in reply to: waleed511

Thank you; please let me know once you find anything; if you find anything.

 

The important thing for you to keep in mind is that the exception is thrown (only!) when the actual native object does not exists (more common), or if the managed instance is not linked to anything native in Revit (less common – should not happen that easily). The first case ought to be obvious and I already mentioned some scenarios, like deleting a document or element and then attempting to work with the managed Document object, or Element object respectively. The second scenario is trickier and harder to get, since something would need to forcefully decouple the managed instance from its native counterpart. I assume it could be done with some shallow copying of the managed instance, but it is not something programmers do every day, and it should not happen in the basic workflow I presented in the code sample.

 

Arnošt Löbel

Autodesk Revit R&D

Arnošt Löbel
Message 12 of 13
waleed511
in reply to: arnostlobel

Hi Arnošt,

 

I am using unmanaged C++ and so to cache the Document object between calls I used msclr::auto_gcroot, but forgot to release the pointer before I return, so the Document was being deleted automatically after the first call.

 

Thank you very much for your help.

 

Message 13 of 13
arnostlobel
in reply to: waleed511

Hello waleed511,

 

I am glad that you were able to find and resolve the problem. As you have found it requires extra precautions when mixing native and managed code, and particularly when caching object between calls to the API. As a general rule, it is not recommended to cache the document or the application object between calls, since you should be able to always get them for the next call’s arguments (with only a few exceptions).

 

Cheers

Arnošt Löbel

Autodesk Revit R&D

Arnošt Löbel

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

Autodesk DevCon in Munich May 28-29th


Rail Community