Skip to content

Commit

Permalink
Enhancement Allow unload AssemblyLoadContext which contains Avalonia …
Browse files Browse the repository at this point in the history
…content #13935 (#13974)

* Try fix #13935

* Fix

* Fix

* add sample

* Fix

* try load Style by reflection

* try

* Fixed an error when registering properties when uninstalling assemblies

* Allowed to delete the IAssemblyDescriptorResolver StandardAssetLoader _assemblyNameCache

* Resolving merge conflicts

* Fix

* Add exegesis

* optimize

* fix

* Resolving merge conflicts

* nuke
  • Loading branch information
MakesYT authored and maxkatz6 committed Apr 6, 2024
1 parent 6a34b18 commit 4ab7b0b
Show file tree
Hide file tree
Showing 30 changed files with 727 additions and 70 deletions.
2 changes: 2 additions & 0 deletions Avalonia.Desktop.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
"samples\\RenderDemo\\RenderDemo.csproj",
"samples\\SampleControls\\ControlSamples.csproj",
"samples\\Sandbox\\Sandbox.csproj",
"samples\\UnloadableAssemblyLoadContext\\UnloadableAssemblyLoadContextPlug\\UnloadableAssemblyLoadContextPlug.csproj",
"samples\\UnloadableAssemblyLoadContext\\UnloadableAssemblyLoadContext\\UnloadableAssemblyLoadContext.csproj",
"src\\Avalonia.Base\\Avalonia.Base.csproj",
"src\\Avalonia.Build.Tasks\\Avalonia.Build.Tasks.csproj",
"src\\Avalonia.Controls.ColorPicker\\Avalonia.Controls.ColorPicker.csproj",
Expand Down
26 changes: 25 additions & 1 deletion Avalonia.sln
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
Expand Down Expand Up @@ -302,6 +301,15 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BuildTasks", "BuildTasks",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PInvoke", "tests\TestFiles\BuildTasks\PInvoke\PInvoke.csproj", "{0A948D71-99C5-43E9-BACB-B0BA59EA25B4}"
EndProject

Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UnloadableAssemblyLoadContext", "UnloadableAssemblyLoadContext", "{9CCA131B-DE95-4D44-8788-C3CAE28574CD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnloadableAssemblyLoadContext", "samples\UnloadableAssemblyLoadContext\UnloadableAssemblyLoadContext\UnloadableAssemblyLoadContext.csproj", "{D7FE3E0F-3FE0-4F87-A2F5-24F1454D84C0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnloadableAssemblyLoadContextPlug", "samples\UnloadableAssemblyLoadContext\UnloadableAssemblyLoadContextPlug\UnloadableAssemblyLoadContextPlug.csproj", "{DA5F1FF9-4259-4C54-B443-85CFA226EE6A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.RenderTests.WpfCompare", "tests\Avalonia.RenderTests.WpfCompare\Avalonia.RenderTests.WpfCompare.csproj", "{9AE1B827-21AC-4063-AB22-C8804B7F931E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -711,6 +719,18 @@ Global
{0A948D71-99C5-43E9-BACB-B0BA59EA25B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0A948D71-99C5-43E9-BACB-B0BA59EA25B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0A948D71-99C5-43E9-BACB-B0BA59EA25B4}.Release|Any CPU.Build.0 = Release|Any CPU
{D7FE3E0F-3FE0-4F87-A2F5-24F1454D84C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D7FE3E0F-3FE0-4F87-A2F5-24F1454D84C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D7FE3E0F-3FE0-4F87-A2F5-24F1454D84C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D7FE3E0F-3FE0-4F87-A2F5-24F1454D84C0}.Release|Any CPU.Build.0 = Release|Any CPU
{DA5F1FF9-4259-4C54-B443-85CFA226EE6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DA5F1FF9-4259-4C54-B443-85CFA226EE6A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DA5F1FF9-4259-4C54-B443-85CFA226EE6A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DA5F1FF9-4259-4C54-B443-85CFA226EE6A}.Release|Any CPU.Build.0 = Release|Any CPU
{9AE1B827-21AC-4063-AB22-C8804B7F931E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9AE1B827-21AC-4063-AB22-C8804B7F931E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9AE1B827-21AC-4063-AB22-C8804B7F931E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9AE1B827-21AC-4063-AB22-C8804B7F931E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -799,6 +819,10 @@ Global
{9D6AEF22-221F-4F4B-B335-A4BA510F002C} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{5BF0C3B8-E595-4940-AB30-2DA206C2F085} = {9D6AEF22-221F-4F4B-B335-A4BA510F002C}
{0A948D71-99C5-43E9-BACB-B0BA59EA25B4} = {5BF0C3B8-E595-4940-AB30-2DA206C2F085}
{9CCA131B-DE95-4D44-8788-C3CAE28574CD} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{D7FE3E0F-3FE0-4F87-A2F5-24F1454D84C0} = {9CCA131B-DE95-4D44-8788-C3CAE28574CD}
{DA5F1FF9-4259-4C54-B443-85CFA226EE6A} = {9CCA131B-DE95-4D44-8788-C3CAE28574CD}
{9AE1B827-21AC-4063-AB22-C8804B7F931E} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}
Expand Down
12 changes: 12 additions & 0 deletions api/Avalonia.nupkg.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1075,6 +1075,18 @@
<Left>baseline/netstandard2.0/Avalonia.Dialogs.dll</Left>
<Right>target/netstandard2.0/Avalonia.Dialogs.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Platform.IAssetLoader.InvalidateAssemblyCache</Target>
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left>
<Right>target/netstandard2.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Platform.IAssetLoader.InvalidateAssemblyCache(System.String)</Target>
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left>
<Right>target/netstandard2.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>P:Avalonia.Media.IRadialGradientBrush.RadiusX</Target>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="UnloadableAssemblyLoadContext.App"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->

<Application.Styles>
<FluentTheme />
</Application.Styles>
</Application>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;

namespace UnloadableAssemblyLoadContext;

public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}

public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow();
}

base.OnFrameworkInitializationCompleted();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#region

using System;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Avalonia;
using Avalonia.Platform;
using Avalonia.Styling;

#endregion

namespace UnloadableAssemblyLoadContext;

public class AssemblyLoadContextH : AssemblyLoadContext
{
private readonly AssemblyDependencyResolver _resolver;

public AssemblyLoadContextH(string pluginPath, string name) : base(isCollectible: true, name: name)
{
_resolver = new AssemblyDependencyResolver(pluginPath);
Unloading += (sender) =>
{
AvaloniaPropertyRegistry.Instance.UnregisterByModule(sender.Assemblies.First().DefinedTypes);
Application.Current.Styles.Remove(MainWindow.Style);
AssetLoader.InvalidateAssemblyCache(sender.Assemblies.First().GetName().Name);
MainWindow.Style= null;
};
}

protected override Assembly Load(AssemblyName assemblyName)
{
var assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
if (assemblyPath != null)
{
if (assemblyPath.EndsWith("WinRT.Runtime.dll") || assemblyPath.EndsWith("Microsoft.Windows.SDK.NET.dll")|| assemblyPath.EndsWith("Avalonia.Controls.dll")|| assemblyPath.EndsWith("Avalonia.Base.dll")|| assemblyPath.EndsWith("Avalonia.Markup.Xaml.dll"))
{
return null;
}

return LoadFromAssemblyPath(assemblyPath);
}

return null;
}

protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
{
var libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
if (libraryPath != null)
{
return LoadUnmanagedDllFromPath(libraryPath);
}

return IntPtr.Zero;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="UnloadableAssemblyLoadContext.MainWindow"
Title="UnloadableAssemblyLoadContext">
Welcome to Avalonia!
</Window>
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.Styling;
using Avalonia.Markup.Xaml.XamlIl.Runtime;
using Avalonia.Platform;
using Avalonia.Platform.Internal;
using Avalonia.Styling;
using Avalonia.Threading;
using Avalonia.VisualTree;

namespace UnloadableAssemblyLoadContext;

public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
if (Debugger.IsAttached)
{
this.AttachDevTools();
}
}
private PlugTool _plugTool;
protected override void OnOpened(EventArgs e)
{
base.OnOpened(e);
test();
//Content = _plugTool.FindControl("UnloadableAssemblyLoadContextPlug.TestControl");


}
public T? GetChildOfType<T>(Control control)
where T : Control
{
var queue = new Queue<Control>();
queue.Enqueue(control);

while (queue.Count > 0)
{
var currentControl = queue.Dequeue();
foreach (var child in currentControl.GetVisualChildren())
{
var childControl = child as Control;
if (childControl != null)
{
var childControlStyles = childControl.Styles;
if (childControlStyles.Count>1)
{

}
queue.Enqueue(childControl);
}
}
}

return null;
}
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
GetChildOfType<Control>(this);


Thread.CurrentThread.IsBackground = false;
var weakReference = _plugTool.Unload();
while (weakReference.IsAlive)
{
GC.Collect();
GC.WaitForPendingFinalizers();
Thread.Sleep(100);
}

Console.WriteLine("Done");


}

public static IStyle Style;
public void test(){

//Notice : 你可以删除UnloadableAssemblyLoadContextPlug.dll所在文件夹中有关Avalonia的所有Dll,但这不是必须的
//Notice : You can delete all Dlls about Avalonia in the folder where UnloadableAssemblyLoadContextPlug.dll is located, but this is not necessary
FileInfo fileInfo = new FileInfo("..\\..\\..\\..\\UnloadableAssemblyLoadContextPlug\\bin\\Debug\\net7.0\\UnloadableAssemblyLoadContextPlug.dll");
var AssemblyLoadContextH = new AssemblyLoadContextH(fileInfo.FullName,"test");

var assembly = AssemblyLoadContextH.LoadFromAssemblyPath(fileInfo.FullName);
var assemblyDescriptorResolver =
_plugTool=new PlugTool();
_plugTool.AssemblyLoadContextH = AssemblyLoadContextH;

var styles = new Styles();
var styleInclude = new StyleInclude(new Uri("avares://UnloadableAssemblyLoadContextPlug", UriKind.Absolute));
styleInclude.Source=new Uri("ControlStyle.axaml", UriKind.Relative);
styles.Add(styleInclude);
Style = styles;
Application.Current.Styles.Add(styles);
foreach (var type in assembly.GetTypes())
{
if (type.FullName=="AvaloniaPlug.Window1")
{
//创建type实例
Window instance = (Window)type.GetConstructor( new Type[0]).Invoke(null);

Dispatcher.UIThread.InvokeAsync(() =>
{
instance.Show();
instance.Close();

}).Wait();

instance = null;

//instance.Show();
}

}

}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;
using System.Linq;
using Avalonia.Controls;

namespace UnloadableAssemblyLoadContext;

public class PlugTool
{
public AssemblyLoadContextH AssemblyLoadContextH;
public WeakReference Unload()
{
var weakReference = new WeakReference(AssemblyLoadContextH);
AssemblyLoadContextH.Unload();
AssemblyLoadContextH = null;
return weakReference;
}

public Control? FindControl(string type)
{
var type1 = AssemblyLoadContextH.Assemblies.
FirstOrDefault(x => x.GetName().Name == "UnloadableAssemblyLoadContextPlug")?.
GetType(type);
if (type1.IsSubclassOf(typeof(Control)))
{
var constructorInfo = type1.GetConstructor( Type.EmptyTypes).Invoke(null) as Control;
return constructorInfo;
}

return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using Avalonia;

namespace UnloadableAssemblyLoadContext;

class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);

// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Design.PreviewWith>
<Border Padding="20">
<!-- Add Controls for Previewer Here -->
</Border>
</Design.PreviewWith>

<!-- Add Styles Here -->
</Styles>

Loading

0 comments on commit 4ab7b0b

Please sign in to comment.