Passing Document instance as argument to external .exe

Passing Document instance as argument to external .exe

lwlXUTTT
Advocate Advocate
1,395 Views
16 Replies
Message 1 of 17

Passing Document instance as argument to external .exe

lwlXUTTT
Advocate
Advocate

Hello everyone,

 

First of all - I am familiar with Jeremy's post concering running executable file directly from Revit Command:

https://thebuildingcoder.typepad.com/blog/2014/07/launching-a-stand-alone-executable.html

 

I was wondering - did anyone of you try to pass active Document as an argument to external application .exe (my custom application) or does anyone have any idea how this problem can be approached?  So basically Revit-Command(RvtCmd) would execute exe file, and before the external application is executed, command (RvtCmd) also sends information about current active Document to this application.

 

Best, Lukasz

0 Likes
1,396 Views
16 Replies
Replies (16)
Message 2 of 17

Kennan.Chen
Advocate
Advocate

Generally, if you want to pass complex data across processes, you need to serialize it to string in one process and pass the string to another process where the string is to be deserialized to memory object.

 

The Revit Document object is a much more complicated object that internally has references to many other Revit objects in Revit memory, which means serialization is nearly impossible.

 

Instead of cloning and transplanting Einstein's brain to your head, why not hiring Einstein to work for you. All you need to do is communication.

 

A possible approach is to create a local C/S architect by hosting an HTTP server inside Revit. The server exposes specific APIs locally. External applications can make HTTP requests to the server to get the results they want without caring about how the results are generated.

0 Likes
Message 3 of 17

moturi.magati.george
Autodesk
Autodesk

Hi @lwlXUTTT,

 

Not sure if it helps but there is an example of External Events available in the Revit SDK. Look for "ModelessDialog" in the Samples folder.

 

https://www.autodesk.com/developer-network/platform-technologies/revit 

 

 

  Moturi George,     Developer Advocacy and Support,  ADN Open
0 Likes
Message 4 of 17

lwlXUTTT
Advocate
Advocate

Dear All,

 

Thank you for your hints,

Maybe I will also clarify my goals and why I stated my question like this.

I want to add simple .DLL to Revit (Application layer) only once (simple button), that would open a window with "Control Panel" to e.g. Print all Sheets from the Document without having this Document Open.

This is easy.

However, I know that working on the solution (external application) would be continuous work, so I will have to change my external application many times and I don't want everyone in my office to load new .DLL all the time, but instead -

have a button (trigger) which would refer to external application (so button doesn't change, but the external application constantly changes). Button .DLL would be loaded only once by everyone in the office and I would not have to bother anyone with new updates of .DLL....

 

Any ideas, what would be then the best approach to such a problem?

Many thank to all suggestion in advance

 

Lukasz

0 Likes
Message 5 of 17

moturi.magati.george
Autodesk
Autodesk

Hi @lwlXUTTT,

 

I think you might need to publish your app to the Autodesk Exchange Store where all your users will get your changes as you develop. There might be a better way of doing it which might be suggested by other forum users.

 

Here is a video tutorial on how to go about it.

 

https://www.youtube.com/watch?v=D_MpPk3nsME&list=PL4CYqBFEfhpJakE-DAuchu785fU_RkAfs

 

 

 

 

  Moturi George,     Developer Advocacy and Support,  ADN Open
0 Likes
Message 6 of 17

TomB.ADV
Advocate
Advocate

I don't think this approach will be possible, the .dll is being locked by Revit.exe , you won't be able to access
it in order to do the swap between the old and the new while others using Revit.

If you have on-premesis server, you can run a worker service porject which will monitor the status of
each .dll you want to swap and if it isn't being used the worker will swap the files while the user isn't
using Revit.exe

 

You can't Run Documnet object outside of the revit engine, thats why you have Forge and APS for ...

0 Likes
Message 7 of 17

moturi.magati.george
Autodesk
Autodesk

Maybe @jeremy_tammik  might have a solution for your question

  Moturi George,     Developer Advocacy and Support,  ADN Open
0 Likes
Message 8 of 17

jeremy_tammik
Alumni
Alumni

You can simply split your application into separate classes, and host the different classes in different .NET assembly DLLs. For example, the FireRatingCloud sample includes a Revit add-in DLL that accesses a class defined and implemented in a separate DLL class library that can also be used by other clients:

  

https://github.com/jeremytammik/FireRatingCloud

  

The separate DLL class library could also reference the Revit API, and it could be swapped at will.

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
0 Likes
Message 9 of 17

lwlXUTTT
Advocate
Advocate

Hello @jeremy_tammik

Thank you for the suggestion,

 

However if I understand your answer correctly, any update of referenced/split DLL would require the restart of Revit Application ? (assuming that my application is implmenting IExternalApplication interface)

So to emphasize my goal - I would like to ask you one more time more specifically:

- is there a way to e.g. build a solution in Visual Studio to obtain new DLL and without closing Revit Application, run a command directly from Revit (implementing IExternalCommand interface), which would already use freshy exported DLL library from Visual Studio ?

 

lwlXUTTT_0-1674078482170.png

I hope the graph also helps to visualize my intension,

 

I would be enourmously grateful for any suggestions in this direction,

Regards, Lukasz

Message 10 of 17

mhannonQ65N2
Collaborator
Collaborator

It might be possible to use Shadow Copying to do this. Calling the SetShadowCopyFiles() method on the current AppDomain does change its ShadowCopyFiles property to true, even though that method is marked as obsolete. I'm not sure if this actually enables shadow copying though.

Message 11 of 17

jeremy_tammik
Alumni
Alumni

The answer is yes.

  

One solution that I am aware of is to load your .NET class library code from a dynamic in-memory byte stream instead of a static file on the hard disk.

  

This topic has been discussed repeatedly and various solutions offered both here in the forum and by The Building Coder, often for the purpose of real-time debugging:

  

https://thebuildingcoder.typepad.com/blog/about-the-author.html#5.49

  

The very earliest as well as the most recent solutions to the latter, real-time debugging, are not of interest to you, since it uses 'hot reloading' support from the Visual Studio debugger. For several years in between, that functionality did not work, and several people implemented and used byte-stream loaders to solve it instead. I think some of them used the method for seamless runtime deployment as well.

  

Please do let us know how you end up solving this, so we can share it with the community. Please also let us continue discussing the question until you have reached a satisfactory conclusion. Thank you!

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
Message 12 of 17

jeremy_tammik
Alumni
Alumni

Here are some links that might help. I have not checked them all, and there may be others as well, possibly more relevant ones. I just searched the titles for 'reload' and weeded out unrelated topics:

  

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
Message 13 of 17

lwlXUTTT
Advocate
Advocate

Good Evening Jeremy,

 

This answer would be the continuation of the post above but also the problem investigated by me in this post:

https://forums.autodesk.com/t5/revit-api-forum/problem-with-modeless-form-window-which-implements-iw...

The clue of both problems seems to be the same.

 

So, following the discussion above, loading the "Logic Assembly" into Byte Array worked really well.

However, there is one issue I am struggling with - but first implementation:

 

 

//implementation of 'Reader' class
public class ExternalAssemblyByteArrayReader
{
   //ctor
   public ExternalAssemblyByteArrayReader(string assemblyFilename)
   {
      byte[] assemblyBytes = File.ReadAllBytes(assemblyFilename);

      //static property assigned to External Application (IExternalApplication)
      Application.Assembly = Assembly.Load(assemblyBytes);
   }

   public void Launch(Assembly assembly, object data, string commandName, string methodName)
   {
      Type typ = assembly.GetType(commandName);
      object cmd = assembly.CreateInstance(typ.FullName);

      object[] args = new object[0];
      if (data is ExternalCommandData)
      {
         string message = null;
         Autodesk.Revit.DB.ElementSet elements = null;

         args = new object[] { data as ExternalCommandData, message, elements };
      }
      else if(data is UIControlledApplication)
      {
         args = new object[] { data as UIControlledApplication};
      }
            
      BindingFlags flags = (BindingFlags) ((int)BindingFlags.Default | (int)BindingFlags.InvokeMethod);
      typ.InvokeMember(methodName, flags, null, cmd, args);
   }
}

 

 

 

//"Reader" class implemented into Revit interfaces

//External Application
public class Application : IExternalApplication
{
   public static Assembly Assembly { get; set; }
   public static ExternalAssemblyByteArrayReader Reader{ get { return _reader; } }

   private static ExternalAssemblyByteArrayReader _reader;

   public Result OnStartup(UIControlledApplication uiControlledApp)
   {
      ExternalAssemblyByteArrayReader reader = new ExternalAssemblyByteArrayReader(assemblyPath);
      _reader = reader;
      reader.Launch(Application.Assembly, uiControlledApp, "LinkedDLL.MainApplication", "OnStartup");

      return Result.Succeeded;
   }
}


//External Command
public class SampleCommand : IExternalCommand
{
   public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
   {
      Application.Reader.Launch(Application.Assembly, commandData, "LinkedDLL.MainWindowCommand", "Execute");
      return Result.Succeeded;
   }
}

 

 

Solution works, however there is important thing to be considered:

Loaded Assembly would have to be defined(loaded) OnStartup in order to preserve values of static properties, which are declared in External Application. Otherwise, with every external command fired, new instance of updated Assembly is loaded and all declared static properties are reset to null. As a consequence of this limitation, every change in code (including changes to IExternalCommands) will have to be predecessed with Revit restart.... 😕

 

Therefore I would like to suggest a new direction of my interrogation:

Is there a way to load/update an Assembly only partially, in a way that Loaded Assembly 'OnStartup' remains untouched (keeping the values of all members assigned during ExternalApplication Runtime), apart form a specific class?

For example:

only Type of Name == "UpdatedCommand" and which implements IExternalCommand interface will be updated

 

As usual I would be extremally grateful for any suggestion/hints,

 

Have a nice evening,

Lukasz

0 Likes
Message 14 of 17

jeremy_tammik
Alumni
Alumni

You ask: 

  

Is there a way to load/update an Assembly only partially?

  

Yes, of course. Using your approach, you can load a single class at a time. So, some classes could be loaded up front and be persistent, and others could be loaded and updated dynamically any time you like.

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
0 Likes
Message 15 of 17

ricaun
Advisor
Advisor

It all depends on your end goal, if you need to load an assembly library on the fly in the release version of your plugin, probably your approach gonna work. You already find out some of the consequences of using Assembly.Load, and if is not part of your main plugin, you should not use Assembly.Load.

 

Now if you only want to update IExternalCommand and IExternalApplication with Revit open without the need to close and open Revit.


Here is my approach using AppLoader.


Has some minimal limitations with some Revit Api methods that only work in the Revit initialize process (Register Warnings or DockablePane).

 

In the end, the loaded plugin gonna work in the same way as loaded by Revit.

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes
Message 16 of 17

lwlXUTTT
Advocate
Advocate

Dear Jeremy,

 

Thank you for your answer. I have tried to kick-off with you suggestion, but I couldn't find a point to start. May I ask you for a reference to solution sample? For me it is not clear whether the 'partial assembly reading' should happen already during on AllBytesReading or during the Loading of the assembly? alternatively during the invoking of specific method?

byte[] assemblyBytes = File.ReadAllBytes(assemblyFilename);
Assembly LoadedAssembly = Assembly.Load(assemblyBytes);

 

As usual, I would be grateful for your help,

Best Regards, Lukasz

0 Likes
Message 17 of 17

jeremy_tammik
Alumni
Alumni

I have no experience with this myself. All I can do is search the Internet:

  

  

Sorry. So, maybe it is more effective if you search for yourself.

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
0 Likes