Welcome to DLL Hell!
If you frequent this blog, you probably read my last attempt at this topic when I convinced myself that Costura.Fody would solve it. Well, not so fast. It was still an exercise worth doing, but ultimately it failed at solving the single biggest scourge of all Revit plug-in developers – the DLL Hell.
Some might ask what it is that I am talking about. Well last time I pointed out the seemingly benign warnings that one might see in Dynamo:
It’s not that easy to tell someone to just ignore it. It should only be ignored under specific conditions:
- shit continues to work even if you don’t know why
For others not so lucky, continue reading…but first let’s start at the beginning…
Let’s have a look at Revit 2019 before we get to Dynamo. I have no additional plugins installed that require 3rd party references/DLLs, and have Dynamo 188.8.131.5226. I don’t have any packages installed yet. The point is to have a look at Fusion Logs and all of the DLL bindings that are created by bare Revit 2019 and Dynamo which nowadays comes out of the box with Revit. First thing that jumps out at me is the sheer number of `CoreNodeModels` that are loaded into Revit. Why is there 3 versions out of the box?
So the v.2.0.2.XXXX is probably not much of an issue. I mean I would still question why did the DLLs with different Build Numbers get published into production, but if the Major versions are the same this is more about just sloppiness, then anything else. I doubt there are actual differences in Types or Methods between builds.
Now, the v.1.2.1.XXXX loaded alongside the latest v.2.0.2 is more of an issue for me. Why? Because as far as I remember there were breaking changes between these versions, so the chance of this causing an issue is much greater. Now, it still depends on actual changes in this specific assembly, but still, I am not feeling warm and fuzzy when I see this. Looking at the log I can see it’s one of the internal Dynamo libraries that called it:
It looks like this DynamoRaaS has a reference to really old Dynamo Core, and even though other libraries were updated, this one wasn’t. It is not part of the Open Source Dynamo libraries, so the most I can do is just point this out to the developers.
The exact same issue happens to DSCoreNodes, DynamoCore, DynamoCoreWpf, DynamoServices and this time its caused by a library called SimpleRaaS, DynamoRaaS, DynamoRaaS and SimpleRaaS respectively. Some libraries like DynamoCoreWpf has been loaded with v.2.0.2, v.2.0.1 and v.1.2.1. That’s three (3) versions of the same assembly in the same context.
That’s Dynamo DLL Hell in full swing. Just not to pile on Dynamo itself, this particular hell started in Revit itself. Let’s take for example the famous Newtonsoft.Json DLL, and all of the issues it has caused. Most recently to guys over at Speckle: Speckle Enters DLL Hell. So for a while now, the consensus solution to multiple output/input ports in Dynamo has been this notion that its caused by the incompatible version of the Newtonsoft.Json dll. How is this possible? I mean these guys are surely distributing just the version needed by Speckle to run fine (in this case it was v.11.0.1). Well, there is not much that Speckle guys can do here, short of changing how Revit handles external DLLs. As is right now, they are all loaded in via LoadFrom method, and they are all therefor placed in the same LoadFrom context. There is a really good article by Suzanne Cook. It explains the different context’s that DLLs can be loaded into, and potential drawbacks from using one or the other. The worst of them all seems to be LoadFrom context, and somehow that’s exactly what Revit and Dynamo both decided to use:
As you can see above in the highlighted section if assembly was already loaded with the same identity (token), it will be returned even if you supply a path to a different one via LoadFrom method. So basically v.11.0.1 is ignored at runtime when bindings are being resolved, and instead v.9.0.0 is used. How do I know that? Well, Fusion Logs to the rescue again:
So Revit 2019 loads v.184.108.40.206, v.220.127.116.11 and v.18.104.22.168 all required by Autodesk.Bcg.Http, DSCoreNodes, ModelBrowser respectively. How much fun is that? Well, do not fret because there is a catch. With all these versions of Newtonsoft.Json available, how do I know which one is actually used at runtime? Well, there is also the Revit.exe.config and binding redirection that I forgot to mention. For Revit 2019 it looks like this:
What is binding redirection you might ask? Well in some instances when you are lazy, and don’t want to track down all of those placed in your application where a different version of the DLL might have been used, you can use binding redirection, as the “catch all” solution. The way it works is that every time any assembly tries to resolve the Newtonsoft.Json dll reference, it will first check the Global Assembly Cache (GAC) to see if its there, but then it will also check for any overrides in the application config file. Here comes our magical binding redirection. This particular one, catches all requests for Newtonsoft.Json v.0.0.0.0 through v.22.214.171.124 and always returns v.126.96.36.199. So when Dynamo asks for v.188.8.131.52 it will actually be served with v.184.108.40.206.
Given that fun solution, it appears to me Speckle is running into something different all together. Since LoadFrom will still load the v.220.127.116.11 that they distributed Speckle with into the context, but will return a reference to v.18.104.22.168 at runtime, we got ourselves a pickle just like Suzanne described it. At runtime it serves a different type that is needed, and it throws a cast exception.
I am not sure that this is allowed, but if Matteo is reading this, why don’t you try editing the Revit.exe.config, and override the binding redirection to 0.0.0.0-22.214.171.124 -> 126.96.36.199. See if this solves the issue you guys have been running into.
Another potential solution that would have been a fun exercise to try, is to use GAC and install our dependencies into it. I was led to believe that anything that is loaded into GAC, can be used in the default Load context (different then LoadFrom context), and when we do that we can use multiple assemblies with the same identity, but different versions. Somehow Windows was supposed to automatically resolve those for us. However, I am skeptical about this solution, because Dynamo itself resides in the LoadFrom context, which is different than all CORE pieces of Revit. Yes, I know, isn’t Dynamo distributed with Revit. If it is, then it must be “core” right? No. Sorry. Dynamo is not cool enough. It’s just another Add-In, and has been treated as such by Revit developers. They don’t want to have anything to do with it. It seems since they inherited it from the original Dynamo team, the DynamoRevit repo has died. Virtually no improvements have been made. Moving on…
Here’s a few things that I did learn for sure:
- Revit loads all 3rd party plugins using the LoadFrom context.
- Dynamo also does that…
- … this means that all Dynamo packages and Revit addins can happily conflict with each other. That’s so much fun.
Was there a better way to do things or LoadFrom is the only way, and we cannot blame Revit developers for serving us up with this pita?
I am not sure, but Microsoft recommends solutions in the following order:
- use the default Load context if you can
- use the .NET Framework Add-In Model
I never tried the .NET Framework Add-In Model, but it clearly was designed to handle just this issue we are having here. Perhaps someone should have a look at it, and see if all of our lives can be made easier. In the meantime we will continue burning in the DLL Hell.