diff --git a/Avalonia.Desktop.slnf b/Avalonia.Desktop.slnf index 40a3802a59..465e579a44 100644 --- a/Avalonia.Desktop.slnf +++ b/Avalonia.Desktop.slnf @@ -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", diff --git a/Avalonia.sln b/Avalonia.sln index 1ac6b77e4e..25753461a9 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -1,4 +1,3 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 @@ -291,6 +290,13 @@ 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 @@ -677,6 +683,14 @@ 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 @@ -764,6 +778,9 @@ 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 diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml index 9a11864491..00ec23467d 100644 --- a/api/Avalonia.nupkg.xml +++ b/api/Avalonia.nupkg.xml @@ -1075,6 +1075,18 @@ baseline/netstandard2.0/Avalonia.Dialogs.dll target/netstandard2.0/Avalonia.Dialogs.dll + + CP0006 + M:Avalonia.Platform.IAssetLoader.InvalidateAssemblyCache + baseline/netstandard2.0/Avalonia.Base.dll + target/netstandard2.0/Avalonia.Base.dll + + + CP0006 + M:Avalonia.Platform.IAssetLoader.InvalidateAssemblyCache(System.String) + baseline/netstandard2.0/Avalonia.Base.dll + target/netstandard2.0/Avalonia.Base.dll + CP0006 P:Avalonia.Media.IRadialGradientBrush.RadiusX diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/App.axaml b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/App.axaml new file mode 100644 index 0000000000..856100f059 --- /dev/null +++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/App.axaml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/App.axaml.cs b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/App.axaml.cs new file mode 100644 index 0000000000..7dfdcded75 --- /dev/null +++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/App.axaml.cs @@ -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(); + } +} diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/AssemblyLoadContextH.cs b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/AssemblyLoadContextH.cs new file mode 100644 index 0000000000..780be4d4b8 --- /dev/null +++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/AssemblyLoadContextH.cs @@ -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; + } +} diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/MainWindow.axaml b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/MainWindow.axaml new file mode 100644 index 0000000000..54e9585a57 --- /dev/null +++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/MainWindow.axaml @@ -0,0 +1,9 @@ + + Welcome to Avalonia! + diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/MainWindow.axaml.cs b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/MainWindow.axaml.cs new file mode 100644 index 0000000000..959740da59 --- /dev/null +++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/MainWindow.axaml.cs @@ -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(Control control) + where T : Control + { + var queue = new Queue(); + 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(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(); + } + + } + + } + + +} diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/PlugTool.cs b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/PlugTool.cs new file mode 100644 index 0000000000..fb5cc28265 --- /dev/null +++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/PlugTool.cs @@ -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; + } +} diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/Program.cs b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/Program.cs new file mode 100644 index 0000000000..1aced7f099 --- /dev/null +++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/Program.cs @@ -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() + .UsePlatformDetect() + .WithInterFont() + .LogToTrace(); +} diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/Styles1.axaml b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/Styles1.axaml new file mode 100644 index 0000000000..958ee15a50 --- /dev/null +++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/Styles1.axaml @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext.csproj b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext.csproj new file mode 100644 index 0000000000..e39a35a294 --- /dev/null +++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext.csproj @@ -0,0 +1,38 @@ + + + WinExe + net7.0 + enable + true + app.manifest + true + true + + + + + %(Filename) + + + Designer + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/app.manifest b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/app.manifest new file mode 100644 index 0000000000..64334402c8 --- /dev/null +++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/app.manifest @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/ControlStyle.axaml b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/ControlStyle.axaml new file mode 100644 index 0000000000..888e73c2bb --- /dev/null +++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/ControlStyle.axaml @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Program.cs b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Program.cs new file mode 100644 index 0000000000..60c37ec32f --- /dev/null +++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Program.cs @@ -0,0 +1,9 @@ +namespace AvaloniaPlug; + +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. + private static string test = "23"; +} \ No newline at end of file diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/TestControl.axaml b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/TestControl.axaml new file mode 100644 index 0000000000..a0dfedaa62 --- /dev/null +++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/TestControl.axaml @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/TestControl.axaml.cs b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/TestControl.axaml.cs new file mode 100644 index 0000000000..01fdf5a832 --- /dev/null +++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/TestControl.axaml.cs @@ -0,0 +1,10 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; + +namespace UnloadableAssemblyLoadContextPlug; + +public class TestControl : TemplatedControl +{ +} + diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/UnloadableAssemblyLoadContextPlug.csproj b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/UnloadableAssemblyLoadContextPlug.csproj new file mode 100644 index 0000000000..564c169818 --- /dev/null +++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/UnloadableAssemblyLoadContextPlug.csproj @@ -0,0 +1,51 @@ + + + net7.0 + enable + true + app.manifest + true + UnloadableAssemblyLoadContextPlug + UnloadableAssemblyLoadContextPlug + + + + + + + + %(Filename) + + + Designer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Window1.axaml b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Window1.axaml new file mode 100644 index 0000000000..83a389f31e --- /dev/null +++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Window1.axaml @@ -0,0 +1,12 @@ + + + diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Window1.axaml.cs b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Window1.axaml.cs new file mode 100644 index 0000000000..8236d1a338 --- /dev/null +++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Window1.axaml.cs @@ -0,0 +1,22 @@ +using System.Diagnostics; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using AvaloniaPlug; + +namespace UnloadableAssemblyLoadContextPlug; + +public partial class Window1 : Window +{ + public Window1() + { + InitializeComponent(); + DataContext=new Window1ViewModel(); + } + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + + + } +} diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Window1ViewModel.cs b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Window1ViewModel.cs new file mode 100644 index 0000000000..7d6bee2c61 --- /dev/null +++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Window1ViewModel.cs @@ -0,0 +1,8 @@ + + +namespace UnloadableAssemblyLoadContextPlug; + +public partial class Window1ViewModel +{ + public string Text { get; set; } = "12"; +} diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/app.manifest b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/app.manifest new file mode 100644 index 0000000000..39c2b00338 --- /dev/null +++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/app.manifest @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index a56f3f098c..9a45338bf4 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -223,6 +223,11 @@ namespace Avalonia return !(a == b); } + public void Unregister(Type type) + { + _metadata.Remove(type); + _metadataCache.Remove(type); + } /// /// Registers a . /// diff --git a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs index 8e6f7b0983..a772f32bf0 100644 --- a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs +++ b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; using System.Runtime.CompilerServices; +using System.Threading; namespace Avalonia { @@ -38,6 +41,53 @@ namespace Avalonia /// internal IReadOnlyCollection Properties => _properties.Values; + private object _unregisteringLocker = new object(); + /// + /// Unregister alls registered on types + /// + /// + /// + public bool UnregisterByModule(IEnumerable types) + { + _ = types ?? throw new ArgumentNullException(nameof(types)); + + lock (_unregisteringLocker) + { + try + { + foreach (var type in types) + { + Unregister(_registered, type); + Unregister(_attached, type); + Unregister(_direct, type); + Unregister(_registeredCache,type); + Unregister(_attachedCache,type); + Unregister(_directCache,type); + Unregister(_inheritedCache,type); + } + } + catch (Exception) + { + return false; + } + } + + return true; + } + private void Unregister( Dictionary> dictionary,Type type) + { + dictionary.Remove(type); + } + private void Unregister( Dictionary> dictionary,Type type) + { + foreach (var keyValuePair in dictionary) + { + foreach (var key in keyValuePair.Value) + { + key.Value.Unregister(type); + } + } + } /// /// Gets all non-attached s registered on a type. /// @@ -47,7 +97,6 @@ namespace Avalonia public IReadOnlyList GetRegistered(Type type) { _ = type ?? throw new ArgumentNullException(nameof(type)); - if (_registeredCache.TryGetValue(type, out var result)) { return result; @@ -55,21 +104,20 @@ namespace Avalonia var t = type; result = new List(); - - while (t != null) + lock (_unregisteringLocker) { - // Ensure the type's static ctor has been run. - RuntimeHelpers.RunClassConstructor(t.TypeHandle); - - if (_registered.TryGetValue(t, out var registered)) + while (t != null) { - result.AddRange(registered.Values); + // Ensure the type's static ctor has been run. + RuntimeHelpers.RunClassConstructor(t.TypeHandle); + if (_registered.TryGetValue(t, out var registered)) + { + result.AddRange(registered.Values); + } + t = t.BaseType; } - - t = t.BaseType; + _registeredCache.Add(type, result); } - - _registeredCache.Add(type, result); return result; } @@ -81,7 +129,6 @@ namespace Avalonia public IReadOnlyList GetRegisteredAttached(Type type) { _ = type ?? throw new ArgumentNullException(nameof(type)); - if (_attachedCache.TryGetValue(type, out var result)) { return result; @@ -89,18 +136,18 @@ namespace Avalonia var t = type; result = new List(); - - while (t != null) + lock (_unregisteringLocker) { - if (_attached.TryGetValue(t, out var attached)) + while (t != null) { - result.AddRange(attached.Values); + if (_attached.TryGetValue(t, out var attached)) + { + result.AddRange(attached.Values); + } + t = t.BaseType; } - - t = t.BaseType; + _attachedCache.Add(type, result); } - - _attachedCache.Add(type, result); return result; } @@ -112,7 +159,6 @@ namespace Avalonia public IReadOnlyList GetRegisteredDirect(Type type) { _ = type ?? throw new ArgumentNullException(nameof(type)); - if (_directCache.TryGetValue(type, out var result)) { return result; @@ -120,18 +166,18 @@ namespace Avalonia var t = type; result = new List(); - - while (t != null) + lock (_unregisteringLocker) { - if (_direct.TryGetValue(t, out var direct)) + while (t != null) { - result.AddRange(direct.Values); + if (_direct.TryGetValue(t, out var direct)) + { + result.AddRange(direct.Values); + } + t = t.BaseType; } - - t = t.BaseType; + _directCache.Add(type, result); } - - _directCache.Add(type, result); return result; } @@ -143,7 +189,6 @@ namespace Avalonia public IReadOnlyList GetRegisteredInherited(Type type) { _ = type ?? throw new ArgumentNullException(nameof(type)); - if (_inheritedCache.TryGetValue(type, out var result)) { return result; @@ -182,7 +227,11 @@ namespace Avalonia } } - _inheritedCache.Add(type, result); + lock (_unregisteringLocker) + { + _inheritedCache.Add(type, result); + } + return result; } @@ -211,7 +260,7 @@ namespace Avalonia DirectPropertyBase property) { return FindRegisteredDirect(o, property) ?? - throw new ArgumentException($"Property '{property.Name} not registered on '{o.GetType()}"); + throw new ArgumentException($"Property '{property.Name} not registered on '{o.GetType()}"); } /// @@ -371,41 +420,44 @@ namespace Avalonia { _ = type ?? throw new ArgumentNullException(nameof(type)); _ = property ?? throw new ArgumentNullException(nameof(property)); - - if (!_registered.TryGetValue(type, out var inner)) + lock (_unregisteringLocker) { - inner = new Dictionary(); - inner.Add(property.Id, property); - _registered.Add(type, inner); - } - else if (!inner.ContainsKey(property.Id)) - { - inner.Add(property.Id, property); - } - - if (property.IsDirect) - { - if (!_direct.TryGetValue(type, out inner)) + if (!_registered.TryGetValue(type, out var inner)) { inner = new Dictionary(); inner.Add(property.Id, property); - _direct.Add(type, inner); + _registered.Add(type, inner); } else if (!inner.ContainsKey(property.Id)) { inner.Add(property.Id, property); } - _directCache.Clear(); - } + if (property.IsDirect) + { + if (!_direct.TryGetValue(type, out inner)) + { + inner = new Dictionary(); + inner.Add(property.Id, property); + _direct.Add(type, inner); + } + else if (!inner.ContainsKey(property.Id)) + { + inner.Add(property.Id, property); + } - if (!_properties.ContainsKey(property.Id)) - { - _properties.Add(property.Id, property); + _directCache.Clear(); + } + + if (!_properties.ContainsKey(property.Id)) + { + _properties.Add(property.Id, property); + } + + _registeredCache.Clear(); + _inheritedCache.Clear(); } - _registeredCache.Clear(); - _inheritedCache.Clear(); } /// @@ -422,26 +474,29 @@ namespace Avalonia { _ = type ?? throw new ArgumentNullException(nameof(type)); _ = property ?? throw new ArgumentNullException(nameof(property)); - + if (!property.IsAttached) { throw new InvalidOperationException( "Cannot register a non-attached property as attached."); } - - if (!_attached.TryGetValue(type, out var inner)) - { - inner = new Dictionary(); - inner.Add(property.Id, property); - _attached.Add(type, inner); - } - else + lock (_unregisteringLocker) { - inner.Add(property.Id, property); - } + if (!_attached.TryGetValue(type, out var inner)) + { + inner = new Dictionary(); + inner.Add(property.Id, property); + _attached.Add(type, inner); + } + else + { + inner.Add(property.Id, property); + } - _attachedCache.Clear(); - _inheritedCache.Clear(); + _attachedCache.Clear(); + _inheritedCache.Clear(); + } + } } } diff --git a/src/Avalonia.Base/Platform/AssetLoader.cs b/src/Avalonia.Base/Platform/AssetLoader.cs index 854610f1c9..a6a65804c7 100644 --- a/src/Avalonia.Base/Platform/AssetLoader.cs +++ b/src/Avalonia.Base/Platform/AssetLoader.cs @@ -33,6 +33,12 @@ public static class AssetLoader /// public static IEnumerable GetAssets(Uri uri, Uri? baseUri) => GetAssetLoader().GetAssets(uri, baseUri); + + /// + public static void InvalidateAssemblyCache(string name) => GetAssetLoader().InvalidateAssemblyCache(name); + /// + public static void InvalidateAssemblyCache() => GetAssetLoader().InvalidateAssemblyCache(); + #endif internal static void RegisterResUriParsers() diff --git a/src/Avalonia.Base/Platform/IAssetLoader.cs b/src/Avalonia.Base/Platform/IAssetLoader.cs index f1ce624c70..cdd4870e0f 100644 --- a/src/Avalonia.Base/Platform/IAssetLoader.cs +++ b/src/Avalonia.Base/Platform/IAssetLoader.cs @@ -77,5 +77,15 @@ namespace Avalonia.Platform /// The base URI. /// All matching assets as a tuple of the absolute path to the asset and the assembly containing the asset IEnumerable GetAssets(Uri uri, Uri? baseUri); + + /// + /// Removes the assembly from the cache. + /// + /// The Assemblies.First().GetName().Name + void InvalidateAssemblyCache(string name); + /// + /// Removes all assemblies from the cache. + /// + void InvalidateAssemblyCache(); } } diff --git a/src/Avalonia.Base/Platform/Internal/AssemblyDescriptorResolver.cs b/src/Avalonia.Base/Platform/Internal/AssemblyDescriptorResolver.cs index b12130b1f7..8360feff4a 100644 --- a/src/Avalonia.Base/Platform/Internal/AssemblyDescriptorResolver.cs +++ b/src/Avalonia.Base/Platform/Internal/AssemblyDescriptorResolver.cs @@ -9,6 +9,9 @@ namespace Avalonia.Platform.Internal; internal interface IAssemblyDescriptorResolver { IAssemblyDescriptor GetAssembly(string name); + void InvalidateAssemblyCache(string name); + void InvalidateAssemblyCache(); + } internal class AssemblyDescriptorResolver: IAssemblyDescriptorResolver @@ -44,4 +47,13 @@ internal class AssemblyDescriptorResolver: IAssemblyDescriptorResolver return rv; } + public void InvalidateAssemblyCache(string name) + { + _assemblyNameCache.Remove(name); + } + + public void InvalidateAssemblyCache() + { + _assemblyNameCache.Clear(); + } } diff --git a/src/Avalonia.Base/Platform/StandardAssetLoader.cs b/src/Avalonia.Base/Platform/StandardAssetLoader.cs index 1d9363c70d..a55ac1428b 100644 --- a/src/Avalonia.Base/Platform/StandardAssetLoader.cs +++ b/src/Avalonia.Base/Platform/StandardAssetLoader.cs @@ -155,6 +155,16 @@ public class StandardAssetLoader : IAssetLoader return Enumerable.Empty(); } + public void InvalidateAssemblyCache(string name) + { + _assemblyDescriptorResolver.InvalidateAssemblyCache(name); + } + + public void InvalidateAssemblyCache() + { + _assemblyDescriptorResolver.InvalidateAssemblyCache(); + } + public static void RegisterResUriParsers() => AssetLoader.RegisterResUriParsers(); private bool TryGetAsset(Uri uri, Uri? baseUri, [NotNullWhen(true)] out IAssetDescriptor? assetDescriptor) diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 70e531a789..ee8bd951d0 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -312,7 +312,6 @@ namespace Avalonia.Controls } }); } - /// /// Fired when the window is opened. /// @@ -714,6 +713,7 @@ namespace Avalonia.Controls OnClosed(EventArgs.Empty); LayoutManager.Dispose(); + _platformImplBindings.Clear(); } /// diff --git a/tests/Avalonia.UnitTests/MockAssetLoader.cs b/tests/Avalonia.UnitTests/MockAssetLoader.cs index 6fe8cd54f7..cf2d327e54 100644 --- a/tests/Avalonia.UnitTests/MockAssetLoader.cs +++ b/tests/Avalonia.UnitTests/MockAssetLoader.cs @@ -45,6 +45,16 @@ namespace Avalonia.UnitTests x => x.GetUnescapeAbsolutePath().IndexOf(absPath, StringComparison.Ordinal) >= 0); } + public void InvalidateAssemblyCache(string name) + { + throw new NotImplementedException(); + } + + public void InvalidateAssemblyCache() + { + throw new NotImplementedException(); + } + public void SetDefaultAssembly(Assembly asm) { throw new NotImplementedException();