Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AssemblyLoadContext - WPF - Collectable Assemblies are not garbage collected #13226

Open
wall-code-solutions opened this issue Aug 7, 2019 · 41 comments
Assignees
Labels
area-AssemblyLoader-coreclr question Answer questions and provide assistance, not an issue with source code or documentation.
Milestone

Comments

@wall-code-solutions
Copy link

I have a problem with a plugin that has a wpf usercontrol. If an instance of the control is created then the plugin assembly will not be garbage collected. Whats wrong here?

Project1:
public interface IPlugin
    {
        object GuiControl { get; }
    }
Project2:
public class Plugin : IPlugin
    {
        public object GuiControl { get; }

        public Plugin()
        {
            GuiControl = new UserControl1(); // Comment this line out => Assemblies are now garbage collected
        }

        ~Plugin()
        {
            Console.WriteLine("Plugin: Calling destructor");
        }
    }
Project3:
public class CollectableAssemblyLoadContext : AssemblyLoadContext
    {
        public CollectableAssemblyLoadContext(string name) : base(name, true) { }
              
        protected override Assembly Load(AssemblyName assemblyName) => null;

        public Assembly Load(byte[] rawAssembly)
        {
            using (var memoryStream = new MemoryStream(rawAssembly))
            {
                var assembly = LoadFromStream(memoryStream);
                return assembly;
            }
        }
    }

class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            for (int i = 0; i < 500; i++)
                TestRun();

            int counter = 0;
            //while(counter == 100)
            //{
            counter++;

            GC.Collect();
            GC.WaitForPendingFinalizers();

            //Thread.Sleep(2500);
            //}

            Console.WriteLine("Assemblies: {0}", AppDomain.CurrentDomain.GetAssemblies().Where(p => p.IsCollectible).Count()); // 500 Assemblies - Why?
            Console.WriteLine("ALC's: {0}", AssemblyLoadContext.All.Count()); // 1 ALC "Default"

            Console.ReadKey();
        }       
        
        public static void TestRun()
        {
            var context = new CollectableAssemblyLoadContext(Guid.NewGuid().ToString());
            var assembly = context.LoadFromAssemblyPath(Path.Combine(Directory.GetCurrentDirectory(), "GuiPlugin.dll"));

            var instance = (IPlugin) Activator.CreateInstance(assembly.GetType("GuiPlugin.Plugin"));            

            context.Unload();
        }
    }
@mattwarren
Copy link
Contributor

Out of interest, if you add [MethodImpl(MethodImplOptions.NoInlining)] to your TestRun() method, does that make any difference?

I've seen that attribute used in a several examples, as it helps prevent assemblies being inadvertently 'rooted', e.g.

@AndyAyersMS
Copy link
Member

It's possible that the jit might extend GC lifetimes of inlinee locals beyond the extent of the inlinee. dotnet/coreclr#9479 tried to put a stop to this, so if you find examples where this happens I'd be happy to take a look.

For allocations within the same method where you force GC, even if you null out your locals after using them, the jit may have stashed references in jit temporaries that don't get nulled.

Keeping your expected to be dead allocations and references in a noinline method is the most reliable fix.

@AaronRobinsonMSFT
Copy link
Member

cc @janvorli

@wall-code-solutions
Copy link
Author

Out of interest, if you add [MethodImpl(MethodImplOptions.NoInlining)] to your TestRun() method, does that make any difference?

I've seen that attribute used in a several examples, as it helps prevent assemblies being inadvertently 'rooted', e.g.

Adding [MethodImpl(MethodImplOptions.NoInlining)] to the method TestRun() did not help.

@wall-code-solutions
Copy link
Author

It's strange ... if i am change
GuiControl = new UserControl1(); // Comment this line out => Assemblies are now garbage collected
to
GuiControl = new UserControl(); // Comment this line out => Assemblies are now garbage collected

Assemblies are now garbage collected.

@janvorli
Copy link
Member

@x1c3 how do UserControl and UserControl1 differ?

@wall-code-solutions
Copy link
Author

wall-code-solutions commented Aug 16, 2019

@janvorli

UserControl1 inherit from UserControl. UserControl1 has no special code implementation. It is empty.

public partial class UserControl1 : UserControl
    {
        public UserControl1()
        {
            InitializeComponent();            
        }

        ~UserControl1()
        {
            Console.WriteLine("UserControl1: Calling destructor");
        }
    }

<UserControl x:Class="GuiPlugin.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:GuiPlugin"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
            
    </Grid>
</UserControl>

@janvorli
Copy link
Member

@x1c3 I have just noticed you only call GC.Collect(); GC.WaitForPendingFinalizers(); once. That doesn't guarantee that the unload will complete after that. If you want to make sure that everything is unloaded at some specific point, you need to do the following (as described in the https://docs.microsoft.com/en-us/dotnet/standard/assembly/unloadability-howto#example-source-with-unloadability-issues).
So in your test, you'd need to:

  • Add [MethodImpl(MethodImplOptions.NoInlining)] to the TestRun
  • Change the TestRun to return WeakReference and return new WeakReference(context) from it
  • In your Main, have a loop that calls GC.Collect(); GC.WaitForPendingFinalizers(); while the weak reference is alive. Since you call the RunTest 500 times in a loop, you could either do it after each call to TestRun or have an array of 500 WeakReferences that you set while calling the RunTest and then have a loop that calls the GC.Collect(); GC.WaitForPendingFinalizers(); until all of the WeakReferences become invalid.

@wall-code-solutions
Copy link
Author

@janvorli

I think that the problem is not the implementation, but the wpf control (UserControl1). Without this UserControl1 the assembly load context and the loaded assembly is garbage collected immediately.

[STAThread]
        static void Main(string[] args)
        {
            //for (int i = 0; i < 500; i++) 

            WeakReference weakReference;
            TestRun(out weakReference);

            for (int i = 0; weakReference.IsAlive && (i < 10000); i++)
            {
                GC.Collect();
                GC.WaitForPendingFinalizers();
                Console.WriteLine("{0}: " + weakReference.IsAlive.ToString(), i);
            }                  
        }        

        [MethodImpl(MethodImplOptions.NoInlining)]
        public static void TestRun(out WeakReference weakReference)
        {
            var context = new CollectableAssemblyLoadContext(Guid.NewGuid().ToString());
            weakReference = new WeakReference(context);

            var assembly = context.LoadFromAssemblyPath(Path.Combine(Directory.GetCurrentDirectory(), "GuiPlugin.dll"));

            var type = assembly.GetType("GuiPlugin.Plugin");
            Activator.CreateInstance(type);
            type = null;

            context.Unload();
        }

@janvorli
Copy link
Member

@x1c3 so do I understand it correctly that the code above fails to unload with UserControl1 even with many iterations in the for loop?

@wall-code-solutions
Copy link
Author

wall-code-solutions commented Aug 20, 2019

@janvorli
Correct, the weakReference status is always "alive" = true.

@janvorli
Copy link
Member

@x1c3 I've tried to replicate this locally and I get the following output:

Plugin: Calling destructor
0: True
1: True
2: False

So it seems to work. I wonder - is your main app that you are testing it with a WPF app or a console app? I am testing it with a console one, so that may cause the difference.

@wall-code-solutions
Copy link
Author

@janvorli
Here a repro: https://github.com/X1C3/WpfPlugin

I am using a console app. I would like to know, what is different in your implementation.

@janvorli
Copy link
Member

Ok, my local repro has the UserControl in a separate assembly. That makes the difference. I've just split your Plugin project into two - one with the Plugin.cs and the other with the UserControl1 files.
I have not tried to look into why it makes a difference yet.

@wall-code-solutions
Copy link
Author

@janvorli
Thanks for the hint. Now it works if the UserControl is moved to a seperate assembly file. But that should not be the final solution.

@janvorli
Copy link
Member

I agree that it is strange. I'll debug it to get full understanding of the different behavior.

@janvorli janvorli self-assigned this Aug 22, 2019
@janvorli
Copy link
Member

@x1c3 I have found why it works when the control is in a separate assembly. I have missed before that your custom CollectableAssemblyLoadContext doesn't resolve other assemblies than the plugin one into this context (the Load method returns null). So when the control is in a separate assembly, it is loaded into the default context and thus it doesn't prevent unloading of the context. But that's not what you wanted do end up with.

The real culprit is in the WPF itself. The MS.Internal.Resources.ResourceManagerWrapper holds on the assembly containing the control. And it is rooted (transitively) by two different roots. I've just digged that from the !gcroots command in WinDbg at the point where the unload fails.
My knowledge of WPF and its source code is zero, so I am not sure if there is a way to somehow explicitly remove the ResourceManagerWrapper for our specific assembly using existing APIs or not. But looking at the source code of MS.Internal.AppModel.ResourceContainer that manages those, it seems assemblies can be only added, but never removed.

So it seems WPF will need to be made aware of unloadability to make your scenario work.

@wall-code-solutions
Copy link
Author

@janvorli
Thanks for analysing. Should this restriction fix by the wpf team? Is it kind of a bug or more an extension in wpf - what is the next step?

@janvorli
Copy link
Member

Someone will need to fix this in the WPF repo (https://github.com/dotnet/wpf). Since it is open source, it doesn't have to be the wpf team.
I would not call it a bug, rather a missing support for unloading. The unloadability support was added quite recently and so many libraries will need to catch up. I'll create a github issue in the wpf repo for making WPF unloadability friendly.

@janvorli
Copy link
Member

janvorli commented Sep 3, 2019

Created issue here: dotnet/wpf#1764

@msftgits msftgits transferred this issue from dotnet/coreclr Jan 31, 2020
@msftgits msftgits added this to the Future milestone Jan 31, 2020
@netcorefan1
Copy link

netcorefan1 commented Nov 17, 2020

I mentioned your issue in my post since, at least to me, seems related. The staff marked my post initially with the "investigate" label and now sent it to future milestone. This make me think that the fix is something that can take months and more.
Thanks

UnloadWpfLibrary.zip
(Solution file inside "MainApp" folder)

@netcorefan1
Copy link

netcorefan1 commented Nov 18, 2020

I just want to update this issue with a new sample project where I applied the workaround suggested from @janvorli .
UnloadWpfLibraryWithWorkaround.zip
(Solution file inside "MainApp" folder)

This time one of the two assemblies is unloaded, but all of the others are still loaded included WpfLibrary.
I think that for me it's time to give up and recur to IPC (named pipes) although I am not sure if this could be a valid replacement.
May be I missed something and someone more expert can do further progress and attach here the project with the correct modifications, it would be of great benefit for all the users that want to use ALC to load and unload WPF.
It would be a total of 4 projects just to load and unload a wpf assembly on demand and this is not exactly clean, but if the final result is the same it would be acceptable.
P.s. Most of the sample projects here are uploaded in the repos and immediately deleted which lead to broken links! I want to encourage people to upload sample projects here as attachment!!

@janvorli
Copy link
Member

@netcorefan1 I have tried your latest sample and after the loop with GC collect, I have verified that everything was correctly unloaded. So I wonder if you were testing it when running under visual studio or standalone. We had some issues with Visual Studio itself locking the assemblies.
I've tested it by putting Debugger.Break() after the loop and running it under WinDbg. After that break was hit, I've dumped all LoaderAllocator objects on the managed heap and there was none, which means the unload has completed.

@netcorefan1
Copy link

@janvorli Thanks for your help! I have tried through VS and standalone. In VS, after a few GC iterations the only assembly unloaded is ProxyClass.dll, but all the other WPF framework assemblies remain loaded:
sample1

In standalone, in process explorer the same WPF Framework assemblies are still loaded:
sample3

In standalone memory is not released. It should be around 4-5 mb (because when I instantiate MainWindow.cs it loads all the Framework dependencies and memory usage increases to 25Mb):
sample2

I opened in StackOverflow a thread related to that matter where I show sample code which basically is made of just a WPF app. In that project I demonstrate that after some time (still to understand why at random time) the memory increase due to opening and closing a WPF MainWindow is freed and return back to its initial value. And I was asking to the users if its really worth to deal with AssemblyLoadContext which force to have 4 different projects when with one project I end up with the same memory reduction. You should find some interesting things in that thread.
Thanks
P.s. I was sure that referencing ProxyInterface.dll in the MainApp was enough, but I have been forced to reference ProxyClass.dll too, otherwise I get "Could not load file or assembly 'WpfLibrary" when I call "inst.ShowWindow(), even if the cast to the interface is successful and even if I manually copied WpfLibrary.dll in the working directory. This is very strange!

@janvorli
Copy link
Member

The WPF assemblies are loaded into the default context, that's why they are not unloaded. Your implementation of the WpfAppAssemblyLoadContext returns null from the Load method, which means that all dependencies of the ProxyClass.dll should be loaded into the default context.

@netcorefan1
Copy link

The Load method is called only two times, for System.Runtime and ProxyInterface. I left them to return null because they are already loaded in the default context. None of the wpf assemblies pass through the same Load method although they are references of ProxyClass. All the wpf assemblies goes automatically into the default context, no matter what I do and I have no idea of what to do because Microsoft does not provide any doc on how to include the framework assemblies in the custom unloadable context. I don't understand why has not been implemented a simple boolean switch which allow to auto load in ALC all the references of the target assembly, not just the assembly itself.
Could you give me the right directions? Because honestly I am afraid that I will never be able to do this job with my current skills.

@janvorli
Copy link
Member

@vitek-karas do you have any idea why the Load method on the assembly load context is not called for the WpfLibrary.dll and PresentationFramework even though the ProxyClass.dll directly references it?

This is the list of references in the ProxyClass.dll:
// Referenced assemblies (in metadata order):
// PresentationFramework, Version=5.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 (unresolved)
// ProxyInterface, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null (unresolved)
// System.Runtime, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a (unresolved)
// WpfLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null (unresolved)

I've tried the repro locally and I can confirm that it is really called just for the System.Runtime and ProxyInterface

@vitek-karas
Copy link
Member

If the goal is to load a WPF control as a "plugin" and when unloaded all of WPF will be gone as well, then this is not currently possible.

  • WPF assemblies are from framework Microsoft.WindowsDesktop.App and as such they are always loaded into the default load context (the loading happens lazily, but a good way to think about it is that they are "Registered" with the default load context). This is why the code works at all, since there's no direct reference to WPF assemblies anywhere in the build, they are references as a framework because of the net5.0-windows TFM.
  • Currently it's not supported to load any framework assemblies into secondary (unloadable) load contexts. This is specifically true for WPF. WPF assemblies store quite a bit of static state which is "process wide", so allowing then to be loaded potentially multiple times would lead to trouble. Note that there's no "block' in place, so it's possible to try this, but it's very likely it will break.
  • As @janvorli mentioned there seems to be an issue with WPF where it holds onto assemblies with custom controls longer than it should - Add support for unloading Assembly with WPF control in .NET Core wpf#1764. This would prevent the control to be unloaded itself.

Currently the recommended solution is to load frameworks into the default load context (as this app does) and only the plugins are loaded into a custom load context - which can be unloaded. The framework remains loaded for the lifetime of the process. Unfortunately dotnet/wpf#1764 makes it rather hard if not impossible to work with unloading when using WPF.

As for the project design - a good guide how to build a plugin app is here: https://docs.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support (it has a working sample app here: https://github.com/dotnet/samples/tree/master/core/extensions/AppWithPlugin). Specifically I want to point out how to reference the "shared" assemblies from the plugin: https://docs.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support#simple-plugin-with-no-dependencies. In your case this is about the project reference from ProxyClass to ProxyInterface. The plugin should specify Private=false and ExcludeAssets=runtime, this makes it so that the ProxyInterface is not included in the build output of the plugin.

Then you should not need a project reference from the MainApp to the plugin (ProxyClass in your case) and using the AssemblyDependencyResolver in your custom load context to resolve its dependencies.

@janvorli
Copy link
Member

@vitek-karas I was actually also asking about the WpfLibrary, which is an assembly built by the sample project and that the ProxyClass depends on (verified using ILSpy). It seems strange that we don't call the Load method for it at all.

@vitek-karas
Copy link
Member

I see the Load method being called for WpfLibrary. Actually even for PresentationFramework. It doesn't happen immediately, only once the ShowWindow is called, but it does happen.

@janvorli
Copy link
Member

Ah, that's possible, I haven't realized that the load happens later and just debugged it till it loaded the test assembly.

@netcorefan1
Copy link

@vitek-karas @janvorli
Guys, many thanks for the detailed explanations. It's not what I wanted to hear, but at least this will put an end to something which was unclear at all. I saw that long time ago @vatsan-madhavan removed assignment for dotnet/wpf#1764 as a possible enhancement, therefore I have to deduce that this feature will never be supported.

@ryalanms added my issue to Future milestone. I am afraid he was not aware that this is not supported and probably will never be supported. Someone can remove my issue from Future milestone?
This is issue is specific to WPF and if I understood well @vitek-karas, it also applies to WinForms and any other framework (may be a more generic title would be better?)

Now...
Most people will have to be enough lucky to enter here in order to discover why the framework assemblies are not unloaded (despite all possible type of implementations I have tried and they will try without success).
There are applications where it is necessary to have a Gui on demand and rarely needed. Keeping a full framework assembly for a Gui which has been opened only once and for entire lifetime of the application (which could be hours, days, weeks and more) is something that everyone would like to avoid.
Given that AssemblyLoadContext is unable to unload the entire framework, the final question for people that read will be "and now what can I do?"

As far as I know, there are two only possible alternatives:

  1. Interprocess communication through Named Pipes
  2. Make the entire application a gui with a custom entry point and call the gui when needed. More info in the thread I opened on Github. Here I show that when the Gui is closed, the entire memory loaded from the framework is freed and the application return to the same initial memory resource usage. This does not occurs immediately and seems to be that the unused memory is freed when the system free memory is near to exhaustion.

I am not sure what is the best between the two solutions (and I never had to deal with IPC), so if I said something wrong please correct me. With solution 2, although memory will return to the initial state soon or later, the framework will be still loaded for the full lifetime of the application and I am not sure how this could affect system resource in comparison with an IPC solution which is supposed to keep everything isolated.
It would be great for me and the other users if you guys could point us to the right direction for the possible alternatives.

@vitek-karas
Copy link
Member

The WPF specific issue is that even if you do include the entire framework in the app, using plugins which rely on WPF may mean that the plugin cannot be unloaded (the plugin itself, not WPF).

Having WPF loaded should not hurt performance outside of some relatively small memory consumption. WPF assemblies should be R2R images so there should be very little JITing happening and so most of the memory should be files mapped into memory - so called "shared" memory. Meaning that if there's another WPF app using the same framework, the memory will be shared between the processes. Private working set introduced by having the assemblies loaded should be very small. That said WPF itself probably allocates lot of resources (all of the graphics stuff) - I don't know when/how is that freed if at all. That's something WPF owners should be able to answer.

As for recommendations:

  • If you want/need a very clean isolation then out-of-proc is the best.
  • Making the app GUI but run it as console is not that strange. For example Powershell Core does exactly that (pwsh), it's a .NET Core app build with WindowsDesktop framework, but the entry point is a console entry point. They did it for example the same reason - if something later on needs either WPF or WinForms, the app must support the framework - frameworks can't be added later on (and can't be unloaded).

@vatsan-madhavan
Copy link
Member

I saw that long time ago @vatsan-madhavan removed assignment for [dotnet/wpf#1764](https://github.com/dotnet/wpf/issues/1764] as a possible enhancement, therefore I have to deduce that this feature will never be supported.

I moved to a different team, so no longer keeping issues assigned to me, that's all 😅... and I continue to help out occasionally and hope to contribute on and off. There are still folks like @ryalanms who continue to work on important stuff.

That said, eliminating global state in WPF is going to be tedious because there are certainly many global objects that are kept around intentionally. It can be done, but I can think of a handful of refactorings that that would make the framework more robust, performant and easier to contribute-to etc..., so these things always come down to prioritization 😄

@netcorefan1
Copy link

@vitek-karas Not sure if I have understood well, but in my case the plugin itself is just a WPF window and the only situation where I need WPF. Once I call CloseWindow() I don't need anymore that Window and all the related staff of the framework. A sample case could be a settings window or a window displaying an update progress, some operations that may occur rarely.
You mean that if I adopt solution 2 and say, for example, to style the window I reference MaterialDesignInXamlToolkit which adds a good amount of overhead, I will never be able to unload this library itself once closed the window, is that right? And this also applies to any other library that specifically relies on WPF framework, right? if I have WpfApp1 running with the same wpf framework and with the same MaterialDesignInXamlToolkit framework, my WpfApp2 will use the same memory already used from WpfApp1, right?

When I run the program, before instantiating the MainWindow, in task manager the memory usage is 4.6 Mb, then becomes around 25-26 Mb. After some time, whether I call GC or not, the memory decrease and return to the initial value of 4.6 Mb. Sometimes within minutes, sometimes hours, sometimes after 12 hours the memory is still 25-26. With MaterialDesign framework can easily reach hundreds of MB and for a system with limited RAM I think I should take this in consideration especially after what seems to be my wrong assumptions regarding memory management because from I understood, even if task manager shows the memory returned in its initial state, it is not really fully released.
Unless I am sure I am running another process with the same frameworks, seems to be that the only way to avoid such big consumption of system resource is IPC. Is that right?

3- This is another solution that came to me in mind right now as an additional step of solution 2 to be used in case that our app is the only one that needs wpf (and consequently can't rely on shared memory). Restart the entire application which means saving some sort of state (if needed) in a settings file, call CloseWindow() and then something like
Application.Exit(); Process.Start(Application.ExecutablePath);
Not sure on how much this could be clean since it sounds more like an hack. May be that named pipes or any other IPC framework is more desiderable.

@vatsan-madhavan Thanks for your participation. Yes, I saw how can be tedious and it's good to know that can be done. At least now we have some alternatives, not great like the ability to fully unload an assembly, but acceptable.

@vitek-karas
Copy link
Member

In theory the MaterialDesign should be unloadable, there's no limitation to that on the runtime side of things. Unfortunately the WPF issue might mean this won't work either. Note that making WPF not hold onto random assemblies is very likely a MUCH easier fix than making entire WPF unloadable.

Depending on the operation you need to do with the UI, but if it's for example the mentioned "update", you could run the application itself as a child process with some command line arguments to do the operation, while the parent simply waits for the child to exit. Maybe you won't even need any kind of complex IPC, just some command line stuff.

@netcorefan1
Copy link

Thanks for the precious information. I suppose this means that maybe needed the same workaround from @janvorli until the Team fixes the issue.

This could be a nice solution although I have some concerns. The maximum I can do to get data back is redirect standard output and make some weird string extraction to get the required data. Problem is that I can't share objects. For example I may need HttpClient to some preliminary operations in MainApp and then reuse it to the child process for the real operation. I am not sure, but I am afraid this is only possible with IPC. Anyway its worth to try both the methods and see what is best suitable. Thanks for providing another jolly to play with.

@vitek-karas
Copy link
Member

With out-of-proc solution I don't think it's possible to share objects. You can serialize objects via some JSON/XML/gRPC to the other process, but it will not "share" the object as such. I highly doubt this would work for HttpClient for example.

.NET Framework had remoting which sort of made this possible (but I'm not sure it would have worked on HttpClient), .NET Core doesn't have remoting (it had too many issues, using BinaryFormatter as the serialization format being one of them).

@netcorefan1
Copy link

Thanks vitek, you saved me from a lot of headaches. So, the trick is to leave an object on a side or another and interact with it from both the sides using commands passed through methods. Unless I encounter something unexpected, to me seems pretty straightforward. I will go for this solution and I think all the people reading this should, at least until (and if) the team will make possible to unload the framework assemblies too. Thanks to all guys!

@ryan-weil
Copy link

ryan-weil commented Mar 11, 2022

It is 2022 and I presume this problem still has no solution to it. I have a WPF app, and I too cannot unload the plugin if it creates a WPF window. I don't care about unloading the actual WPF-related dependency assemblies that get loaded, since like I said my base app is WPF anyways. I just need a way to unload the actual plugin with the WPF window in it.

Is there currently no way in .NET to create an application that has GUI plugins and unload them?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-AssemblyLoader-coreclr question Answer questions and provide assistance, not an issue with source code or documentation.
Projects
Status: No status
Development

No branches or pull requests

11 participants