cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Support different AssemblyLoadContext internally within AutoCAD/Civil 3D

Support different AssemblyLoadContext internally within AutoCAD/Civil 3D

Not entirely sure whether this is the right forum to post ( because we don't have a AutoCAD idea forum like Civil 3D ideas forum), but anyway I just give it a shot here. 

 

First, I'm writing Civil 3D plugins rather than AutoCAD plugins, but since Civil 3D is built on top of AutoCAD architecture, and this is an architectural issue, I figure that it should be solved at AutoCAD level, hence here.

 

The issue is that we need to be able to use AssemblyLoadContext internally within AutoCAD/Civil 3D for our plugin, just like Inventor. For precisely the very same problem. This is very important because different plugins can have same dependency DLLs ( say System.Text.Json.dll),  but different version, which results in DLL hell.

 

From what I can tell, subclassing AssemblyLoadContext and keep a reference to it in the concrete class inheriting IExtensionApplication should work... in theory. But in practice, the all important Load method is simply not being called ( I've tested it so I know). This is why I said that it's not supported at the moment

 

Some of the related issues that I can find on the forum:

  1. https://forums.autodesk.com/t5/net-forum/unable-to-load-system-text-json-dll-9-0-0-0-and-or-dependen...
  2.  

 

I'm aware that we have some sort of workarounds, but I feel that they are inadequate. Let me address them one by one.

 

  1. Use Proxy method as proposed by @moogalm , but if you look at the solution, it's quite messy and tedious as it involves proxying the dependencies calls one by one. Things get worse when we have dependencies upon dependencies. Admittedly I haven't test it because it's... too complicated to me to digest at one go. 
  2. Use AssemblyLoadContext.Default.Resolving as per @ActivistInvestor  post here. But I'm not sure whether this is the right solution because AssemblyLoadContext.Default.Resolving means all the plugins will share the exact DLL with the same version ( I'm not sure about this point though, my guess is that AssemblyLoadContext.Default a static property means that everyone will have access to the same event, and hence, everyone is referring to a central DLL). This is what we don't want as we want different plugin to use their own version for the same DLL ( say, the Json.dll above).  However, I am currently using this approach because somehow it works, contrary to my expectation.
  3. AppDomain.CurrentDomain.AssemblyResolve. But it wouldn't always safely isolate the dependencies per plugin, too, like the above. My experience in using this shows that it works on some machines, but not others. 
  4.  

Revit seems to have a hot discussion on this internally. So AutoCAD/Civil 3D should also have this feature.

 

PS: I've opened up an internal feedback item on this. Hopefully those can, will go and vote on it too. 

7 Comments
ricaun
Advisor

I have been playing with AssemblyLoadContext for some time and my first experiment with isolation in Revit 2025:

 

 

The idea was simple, every time a command/application is execute is gonna check if is in the default AssemblyLoadContext, if is the case gonna create a custom AssemblyLoadContext to load the same assembly and re-execute the same command/application in the isolated context.

 

Is a little annoying to code and apply in each command/application so I recently create package that inject the code to force a class to be isolated in a custom AssemblyLoadContext and executed the methods in that context.

 

The package uses Fody to inject code in the build process: 

In Revit works really well.

 

I did experiment with AutoCAD and works, but AutoCAD does something that I didn't expected.

When you load any assembly inside AutoCAD and have the ExtensionApplicationAttribute, AutoCAD just create an instance of the IExtensionApplication and Initialize. 

Here is a simple sample creating a custom CustomAssemblyLoadContext if is not the Default and load the assembly.

 

public class CustomContextApp : IExtensionApplication
{
    public void Initialize()
    {
        var assembly = Assembly.GetExecutingAssembly();
        var context = AssemblyLoadContext.GetLoadContext(assembly);
        if (AssemblyLoadContext.Default == context)
        {
            var assemblyLoadContext = new CustomAssemblyLoadContext("Custom", assembly.Location);
            var extensionAssembly = assemblyLoadContext.LoadFromAssemblyPath(assembly.Location);
            return;
        }

        // Initialize logic here
        ComponentManager.ItemInitialized += ComponentManager_ItemInitialized;
    }

    private void ComponentManager_ItemInitialized(object sender, RibbonItemEventArgs e)
    {
        ComponentManager.ItemInitialized -= ComponentManager_ItemInitialized;

        var ribbonControl = ComponentManager.Ribbon;
        var ribbonPanel = ribbonControl.CreateOrSelectPanel("Test", "Test");

        var button = ribbonPanel.CreateButton("Context")
            .SetDescription("Description")
            .SetLargeImage("Resources/Cube-Grey-Light.tiff")
            .SetToolTipImage("Resources/Cube-Grey-Light.tiff")
            .SetToolTip("ToolTip");

        button.SetDescription(this.GetType().Assembly.GetContext().Name);
    }

    public void Terminate()
    {
        var assembly = Assembly.GetExecutingAssembly();
        var context = AssemblyLoadContext.GetLoadContext(assembly);
        if (AssemblyLoadContext.Default == context)
            return;

        // Terminate logic here
    }
}

 

My guess AutoCAD just need to load the assembly in a custom AssemblyLoadContext and the IExtensionApplication should work by default, and looks like the commands is overwrite so works like a charm.

 

Here is the CustomAssemblyLoadContext.cshttps://gist.github.com/ricaun/76ee1bd6c9a5c26bfa77bd191adbcafc

Basically uses the AssemblyDependencyResolver to resolve the dependencies in the same folder as the main addin assembly, no event is required.

 

This approach only works because AutoCAD re-execute the IExtensionApplication when is loaded in the CustomAssemblyLoadContext 😊

soonhui
Advisor

@ricaun , your CustomContextApp doesn't work on my side, with VS 2026 Insiders and in debug mode. The reason is because Initialize method of IExtensionApplication is called once and once only. So during the only time when it's called, the return statement at line 11 is hit, hence the next line ComponentManager.Initialized ( where all the ribbon controls are loaded) is simply not called, and will never be. 

 

So I'm curious how come on your side the Initialize is called multiple times?

ricaun
Advisor

@soonhui wrote:
So I'm curious how come on your side the Initialize is called multiple times?

I'm forcing to load the same assembly using LoadFromAssemblyPath (Line 10) in the new context, the IExtensionApplication trigger again when loaded. I tested using debug with VS2026 and the Initialize() trigger two times one with the Default context and the second time with the Custom context.

 

I'm using AutoCAD 2025 and bundle, here is my configurations.

  <Components Description="AutoCAD 2025+">
    <RuntimeRequirements OS="Win64" Platform="AutoCAD*" SeriesMin="R25.0" />
    <ComponentEntry AppName="AutoCADAddin.Sample" ModuleName="./AutoCADAddin.Sample.dll" LoadOnAppearance="True" />
  </Components>

 

soonhui
Advisor

@ricaun , by using bundle like what you did, I can indeed, get the plugin to load without problem.

 

The problematic case only comes when I'm loading the Civil3D/AutoCAD as an external program from VS 2026, with the following launchSettings.json

 

{
"profiles": {
"AutoCAD": {
"commandName": "Executable",
"executablePath": "%AutoCADDir%\\acad.exe",
"commandLineArgs": "/ld \"%AutoCADDir%\\AecBase.dbx\" /p \"<<C3D_Metric>>\" /product C3D /b \"E:\\Genieko\\Genabler\\Source\\GenablerPlugin\\Plugin\\LoadPlugin.scr\"",
"workingDirectory": "E:\\Genieko\\Genabler\\Source\\GenablerPlugin\\Plugin\\bin\\Debug"
}
}
}

 

scr file

NETLOAD "E:\Genieko\Genabler\Source\GenablerPlugin\Plugin\bin\Debug\GenablerPlugin.dll"
FILEDIA 1

 

It appears that depending on how you launch the AutoCAD/Civil 3D, Initialize maybe called differently? This seems like another bug...

 

ricaun
Advisor

@soonhui wrote:
It appears that depending on how you launch the AutoCAD/Civil 3D, Initialize maybe called differently? This seems like another bug...

Interesting, NETLOAD loads different. I only use bundle because that's how I gonna ship the plugin.

 

I'm coping the bundle using some Target after the build process, this enable me to debug as an external program from VS 2026. 

  <Target Name="_CopyBundleFiles" AfterTargets="Build" Condition="$(CopyBundleFiles) and $(TargetFramework) != ''">
    <ItemGroup>
      <PackageContentsFile Include="$(ProjectDir)\PackageContents.xml" />
      <BundleItems Include="$(OutputPath)**\*" />
    </ItemGroup>

    <Copy SourceFiles="@(BundleItems)" SkipUnchangedFiles="false" DestinationFolder="$(BundleDestinationFolder)\%(RecursiveDir)" ContinueOnError="true" Retries="1" />
    <Copy SourceFiles="@(PackageContentsFile)" SkipUnchangedFiles="false" DestinationFolder="$(BundleDestinationFolder)" ContinueOnError="true" />

    <Message Text="$(MSBuildProjectName) -&gt; $(BundleDestinationFolder) -&gt; [@(BundleItems -> '%(Identity)', ', ')] ($(TargetFramework)) " Importance="high" />
  </Target>

  <Target Name="_CleanBundleFiles" AfterTargets="Clean" Condition="$(CopyBundleFiles) and $(TargetFramework) != ''">
    <RemoveDir Directories="$(BundleDestinationFolder)" ContinueOnError="true" />
    <Delete Files="$(BundleDestinationFolder)\PackageContents.xml" ContinueOnError="true" />
  </Target>

 

soonhui
Advisor

@ricaun , then I do find it strange that how all these things work at all.

 

From what I can gather the Target is just a copy operation-- copying your DLLs file for Bundle ( publishing plugin purpose). As far as debug and running from VS is concerned, it's still loading from bin debug, which means, it's still the same NETLOAD as mine. But why NETLOAD on your side works , and on mine, it doesn't?

The .bundle approach appears to work for both of us, though. 

ricaun
Advisor

@soonhui wrote:

From what I can gather the Target is just a copy operation-- copying your DLLs file for Bundle ( publishing plugin purpose). As far as debug and running from VS is concerned, it's still loading from bin debug, which means, it's still the same NETLOAD as mine. But why NETLOAD on your side works , and on mine, it doesn't?


I'm never running the NETLOAD in my side. 

 

AutoCAD loads the PackageContents.xml and loads the ModuleName using some internal method that force the Security - Signed File to trigger. Looks like is not the same as using NETLOAD.

 

I never found in the AutoCAD API some utility method to load PackageContents.xml, Revit has the LoadPackageContents method, if AutoCAD has some similar method would be easier know the reason .bundle loads different from NETLOAD in this AssemblyLoadContext situation.

 

 

 

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

Submit Idea