Browse Source

Enhancement Allow unload AssemblyLoadContext which contains Avalonia 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
release/11.1.0-beta2
MakesYT 2 years ago
committed by Max Katz
parent
commit
4ab7b0babe
  1. 2
      Avalonia.Desktop.slnf
  2. 26
      Avalonia.sln
  3. 12
      api/Avalonia.nupkg.xml
  4. 10
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/App.axaml
  5. 23
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/App.axaml.cs
  6. 57
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/AssemblyLoadContextH.cs
  7. 9
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/MainWindow.axaml
  8. 134
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/MainWindow.axaml.cs
  9. 31
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/PlugTool.cs
  10. 21
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/Program.cs
  11. 11
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/Styles1.axaml
  12. 38
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext.csproj
  13. 18
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/app.manifest
  14. 12
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/ControlStyle.axaml
  15. 9
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Program.cs
  16. 17
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/TestControl.axaml
  17. 10
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/TestControl.axaml.cs
  18. 51
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/UnloadableAssemblyLoadContextPlug.csproj
  19. 12
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Window1.axaml
  20. 22
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Window1.axaml.cs
  21. 8
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Window1ViewModel.cs
  22. 18
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/app.manifest
  23. 5
      src/Avalonia.Base/AvaloniaProperty.cs
  24. 191
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  25. 6
      src/Avalonia.Base/Platform/AssetLoader.cs
  26. 10
      src/Avalonia.Base/Platform/IAssetLoader.cs
  27. 12
      src/Avalonia.Base/Platform/Internal/AssemblyDescriptorResolver.cs
  28. 10
      src/Avalonia.Base/Platform/StandardAssetLoader.cs
  29. 2
      src/Avalonia.Controls/TopLevel.cs
  30. 10
      tests/Avalonia.UnitTests/MockAssetLoader.cs

2
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",

26
Avalonia.sln

@ -1,4 +1,3 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
@ -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
@ -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
@ -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}

12
api/Avalonia.nupkg.xml

@ -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>

10
samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/App.axaml

@ -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>

23
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();
}
}

57
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;
}
}

9
samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/MainWindow.axaml

@ -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>

134
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<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();
}
}
}
}

31
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;
}
}

21
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<App>()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace();
}

11
samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/Styles1.axaml

@ -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>

38
samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext.csproj

@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<IncludeAvaloniaGenerators>true</IncludeAvaloniaGenerators>
</PropertyGroup>
<ItemGroup>
<Compile Update="**\*.xaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
<AvaloniaResource Include="**\*.xaml">
<SubType>Designer</SubType>
</AvaloniaResource>
<AvaloniaResource Include="Assets\*" />
<AvaloniaResource Include="Assets\Fonts\*" />
</ItemGroup>
<ItemGroup>
<Folder Include="Models\"/>
<AvaloniaResource Include="Assets\**"/>
</ItemGroup>
<ImportGroup>
<Import Project="..\..\..\build\BuildTargets.targets" Condition="Exists('..\..\..\build\BuildTargets.targets')" />
<Import Project="..\..\..\build\SourceGenerators.props" />
</ImportGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" />
<ProjectReference Include="..\..\..\src\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" />
<ProjectReference Include="..\..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\..\src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj" />
<ProjectReference Include="..\..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
</ItemGroup>
</Project>

18
samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/app.manifest

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embedded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="AvaloniaApplication2.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>

12
samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/ControlStyle.axaml

@ -0,0 +1,12 @@
<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>
<StyleInclude Source="TestControl.axaml"></StyleInclude>
</Styles>

9
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";
}

17
samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/TestControl.axaml

@ -0,0 +1,17 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:UnloadableAssemblyLoadContextPlug">
<Design.PreviewWith>
<controls:TestControl />
</Design.PreviewWith>
<Style Selector="controls|TestControl">
<!-- Set Defaults -->
<Setter Property="Template">
<ControlTemplate>
<TextBlock Text="Templated Control" />
</ControlTemplate>
</Setter>
</Style>
</Styles>

10
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
{
}

51
samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/UnloadableAssemblyLoadContextPlug.csproj

@ -0,0 +1,51 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<AssemblyName>UnloadableAssemblyLoadContextPlug</AssemblyName>
<RootNamespace>UnloadableAssemblyLoadContextPlug</RootNamespace>
</PropertyGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\**"/>
</ItemGroup>
<ItemGroup>
<Compile Update="**\*.xaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
<AvaloniaResource Include="**\*.xaml">
<SubType>Designer</SubType>
</AvaloniaResource>
<AvaloniaResource Include="Assets\*" />
<AvaloniaResource Include="Assets\Fonts\*" />
</ItemGroup>
<ImportGroup>
<Import Project="..\..\..\build\BuildTargets.targets" Condition="Exists('..\..\..\build\BuildTargets.targets')" />
<Import Project="..\..\..\build\SourceGenerators.props" />
</ImportGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" />
<ProjectReference Include="..\..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" />
<ProjectReference Condition="'$(Configuration)' == 'Debug'" Include="..\..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\..\src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj" />
<ProjectReference Include="..\..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj" />
<ProjectReference Include="..\..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
<ProjectReference Include="..\..\..\src\Markup\Avalonia.Markup.Xaml.Loader\Avalonia.Markup.Xaml.Loader.csproj" />
</ItemGroup>
<ItemGroup>
<UpToDateCheckInput Remove="Views\MainWindow.axaml" />
</ItemGroup>
</Project>

12
samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Window1.axaml

@ -0,0 +1,12 @@
<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"
xmlns:avaloniaPlug="clr-namespace:AvaloniaPlug"
xmlns:unloadableAssemblyLoadContextPlug="clr-namespace:UnloadableAssemblyLoadContextPlug"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="UnloadableAssemblyLoadContextPlug.Window1"
x:DataType="unloadableAssemblyLoadContextPlug:Window1ViewModel"
Title="Window1">
<TextBlock Text="{Binding Text }" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Window>

22
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);
}
}

8
samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Window1ViewModel.cs

@ -0,0 +1,8 @@

namespace UnloadableAssemblyLoadContextPlug;
public partial class Window1ViewModel
{
public string Text { get; set; } = "12";
}

18
samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/app.manifest

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embedded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="AvaloniaPlug.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>

5
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);
}
/// <summary>
/// Registers a <see cref="AvaloniaProperty"/>.
/// </summary>

191
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
/// </summary>
internal IReadOnlyCollection<AvaloniaProperty> Properties => _properties.Values;
private object _unregisteringLocker = new object();
/// <summary>
/// Unregister all<see cref="AvaloniaProperty"/>s registered on types
/// </summary>
/// <param name="types"></param>
/// <exception cref="ArgumentNullException"></exception>
public bool UnregisterByModule(IEnumerable<Type> 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<Type, List<AvaloniaProperty>> dictionary,Type type)
{
dictionary.Remove(type);
}
private void Unregister( Dictionary<Type, Dictionary<int, AvaloniaProperty>> dictionary,Type type)
{
foreach (var keyValuePair in dictionary)
{
foreach (var key in keyValuePair.Value)
{
key.Value.Unregister(type);
}
}
}
/// <summary>
/// Gets all non-attached <see cref="AvaloniaProperty"/>s registered on a type.
/// </summary>
@ -47,7 +97,6 @@ namespace Avalonia
public IReadOnlyList<AvaloniaProperty> 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<AvaloniaProperty>();
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<AvaloniaProperty> 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<AvaloniaProperty>();
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<AvaloniaProperty> 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<AvaloniaProperty>();
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<AvaloniaProperty> 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<T> 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()}");
}
/// <summary>
@ -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<int, AvaloniaProperty>();
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<int, AvaloniaProperty>();
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<int, AvaloniaProperty>();
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();
}
/// <summary>
@ -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<int, AvaloniaProperty>();
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<int, AvaloniaProperty>();
inner.Add(property.Id, property);
_attached.Add(type, inner);
}
else
{
inner.Add(property.Id, property);
}
_attachedCache.Clear();
_inheritedCache.Clear();
_attachedCache.Clear();
_inheritedCache.Clear();
}
}
}
}

6
src/Avalonia.Base/Platform/AssetLoader.cs

@ -33,6 +33,12 @@ public static class AssetLoader
/// <inheritdoc cref="IAssetLoader.GetAssets"/>
public static IEnumerable<Uri> GetAssets(Uri uri, Uri? baseUri)
=> GetAssetLoader().GetAssets(uri, baseUri);
/// <inheritdoc cref="IAssetLoader.InvalidateAssemblyCache()"/>
public static void InvalidateAssemblyCache(string name) => GetAssetLoader().InvalidateAssemblyCache(name);
/// <inheritdoc cref="IAssetLoader.InvalidateAssemblyCache(string)"/>
public static void InvalidateAssemblyCache() => GetAssetLoader().InvalidateAssemblyCache();
#endif
internal static void RegisterResUriParsers()

10
src/Avalonia.Base/Platform/IAssetLoader.cs

@ -77,5 +77,15 @@ namespace Avalonia.Platform
/// <param name="baseUri">The base URI.</param>
/// <returns>All matching assets as a tuple of the absolute path to the asset and the assembly containing the asset</returns>
IEnumerable<Uri> GetAssets(Uri uri, Uri? baseUri);
/// <summary>
/// Removes the assembly from the cache.
/// </summary>
/// <param name="name">The Assemblies.First().GetName().Name</param>
void InvalidateAssemblyCache(string name);
/// <summary>
/// Removes all assemblies from the cache.
/// </summary>
void InvalidateAssemblyCache();
}
}

12
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();
}
}

10
src/Avalonia.Base/Platform/StandardAssetLoader.cs

@ -155,6 +155,16 @@ public class StandardAssetLoader : IAssetLoader
return Enumerable.Empty<Uri>();
}
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)

2
src/Avalonia.Controls/TopLevel.cs

@ -312,7 +312,6 @@ namespace Avalonia.Controls
}
});
}
/// <summary>
/// Fired when the window is opened.
/// </summary>
@ -714,6 +713,7 @@ namespace Avalonia.Controls
OnClosed(EventArgs.Empty);
LayoutManager.Dispose();
_platformImplBindings.Clear();
}
/// <summary>

10
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();

Loading…
Cancel
Save