diff --git a/Directory.Packages.props b/Directory.Packages.props
index 02ecbcfbec..f207629a4b 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -23,12 +23,12 @@
+
-
@@ -45,6 +45,7 @@
+
diff --git a/ILSpy/App.xaml.cs b/ILSpy/App.xaml.cs
index 0665244f3f..0ae19f9e17 100644
--- a/ILSpy/App.xaml.cs
+++ b/ILSpy/App.xaml.cs
@@ -23,7 +23,6 @@
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
-using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Documents;
@@ -36,8 +35,6 @@
using Medo.Application;
-using Microsoft.VisualStudio.Composition;
-
using TomsToolbox.Wpf.Styles;
using ICSharpCode.ILSpyX.TreeView;
@@ -47,6 +44,11 @@
using System.Globalization;
using System.Threading;
+using Microsoft.Extensions.DependencyInjection;
+
+using TomsToolbox.Composition.MicrosoftExtensions;
+using TomsToolbox.Essentials;
+
namespace ICSharpCode.ILSpy
{
///
@@ -59,10 +61,9 @@ public partial class App : Application
public static IExportProvider ExportProvider { get; private set; }
- internal class ExceptionData
+ internal record ExceptionData(Exception Exception)
{
- public Exception Exception;
- public string PluginName;
+ public string PluginName { get; init; }
}
public App()
@@ -80,6 +81,14 @@ public App()
InitializeComponent();
+ if (!InitializeDependencyInjection(SettingsService.Instance))
+ {
+ // There is something completely wrong with DI, probably some service registration is missing => nothing we can do to recover, so stop and shut down.
+ Exit += (_, _) => MessageBox.Show(StartupExceptions.FormatExceptions(), "Sorry we crashed!");
+ Shutdown(1);
+ return;
+ }
+
if (!Debugger.IsAttached)
{
AppDomain.CurrentDomain.UnhandledException += ShowErrorBox;
@@ -92,8 +101,6 @@ public App()
Resources.RegisterDefaultStyles();
- InitializeMef().GetAwaiter().GetResult();
-
// Register the export provider so that it can be accessed from WPF/XAML components.
ExportProviderLocator.Register(ExportProvider);
// Add data templates registered via MEF.
@@ -103,7 +110,7 @@ public App()
ThemeManager.Current.Theme = sessionSettings.Theme;
if (!string.IsNullOrEmpty(sessionSettings.CurrentCulture))
{
- Thread.CurrentThread.CurrentUICulture = CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo(sessionSettings.CurrentCulture);
+ Thread.CurrentThread.CurrentUICulture = CultureInfo.DefaultThreadCurrentUICulture = new(sessionSettings.CurrentCulture);
}
EventManager.RegisterClassHandler(typeof(Window),
@@ -125,6 +132,13 @@ public App()
SettingsService.Instance.AssemblyListManager.CreateDefaultAssemblyLists();
}
+ public new static App Current => (App)Application.Current;
+
+ public new MainWindow MainWindow {
+ get => (MainWindow)base.MainWindow;
+ set => base.MainWindow = value;
+ }
+
private static void SingleInstance_NewInstanceDetected(object sender, NewInstanceEventArgs e) => ExportProvider.GetExportedValue().HandleSingleInstanceCommandLineArguments(e.Args).HandleExceptions();
static Assembly ResolvePluginDependencies(AssemblyLoadContext context, AssemblyName assemblyName)
@@ -136,22 +150,17 @@ static Assembly ResolvePluginDependencies(AssemblyLoadContext context, AssemblyN
return context.LoadFromAssemblyPath(assemblyFileName);
}
- private static async Task InitializeMef()
+ private static bool InitializeDependencyInjection(SettingsService settingsService)
{
// Add custom logic for resolution of dependencies.
// This necessary because the AssemblyLoadContext.LoadFromAssemblyPath and related methods,
// do not automatically load dependencies.
AssemblyLoadContext.Default.Resolving += ResolvePluginDependencies;
- // Cannot show MessageBox here, because WPF would crash with a XamlParseException
- // Remember and show exceptions in text output, once MainWindow is properly initialized
try
{
- // Set up VS MEF. For now, only do MEF1 part discovery, since that was in use before.
- // To support both MEF1 and MEF2 parts, just change this to:
- // var discovery = PartDiscovery.Combine(new AttributedPartDiscoveryV1(Resolver.DefaultInstance),
- // new AttributedPartDiscovery(Resolver.DefaultInstance));
- var discovery = new AttributedPartDiscoveryV1(Resolver.DefaultInstance);
- var catalog = ComposableCatalog.Create(Resolver.DefaultInstance);
+ var services = new ServiceCollection();
+
+
var pluginDir = Path.GetDirectoryName(typeof(App).Module.FullyQualifiedName);
if (pluginDir != null)
{
@@ -160,46 +169,39 @@ private static async Task InitializeMef()
var name = Path.GetFileNameWithoutExtension(plugin);
try
{
- var asm = AssemblyLoadContext.Default.LoadFromAssemblyPath(plugin);
- var parts = await discovery.CreatePartsAsync(asm);
- catalog = catalog.AddParts(parts);
+ var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(plugin);
+ services.BindExports(assembly);
}
catch (Exception ex)
{
- StartupExceptions.Add(new ExceptionData { Exception = ex, PluginName = name });
+ // Cannot show MessageBox here, because WPF would crash with a XamlParseException
+ // Remember and show exceptions in text output, once MainWindow is properly initialized
+ StartupExceptions.Add(new(ex) { PluginName = name });
}
}
}
+
// Add the built-in parts: First, from ILSpyX
- var xParts = await discovery.CreatePartsAsync(typeof(IAnalyzer).Assembly);
- catalog = catalog.AddParts(xParts);
+ services.BindExports(typeof(IAnalyzer).Assembly);
// Then from ILSpy itself
- var createdParts = await discovery.CreatePartsAsync(Assembly.GetExecutingAssembly());
- catalog = catalog.AddParts(createdParts);
-
- // If/When the project switches to .NET Standard/Core, this will be needed to allow metadata interfaces (as opposed
- // to metadata classes). When running on .NET Framework, it's automatic.
- // catalog.WithDesktopSupport();
- // If/When any part needs to import ICompositionService, this will be needed:
- // catalog.WithCompositionService();
- var config = CompositionConfiguration.Create(catalog);
- var exportProviderFactory = config.CreateExportProviderFactory();
- ExportProvider = new ExportProviderAdapter(exportProviderFactory.CreateExportProvider());
-
- // This throws exceptions for composition failures. Alternatively, the configuration's CompositionErrors property
- // could be used to log the errors directly. Used at the end so that it does not prevent the export provider setup.
- config.ThrowOnErrors();
- }
- catch (CompositionFailedException ex) when (ex.InnerException is AggregateException agex)
- {
- foreach (var inner in agex.InnerExceptions)
- {
- StartupExceptions.Add(new ExceptionData { Exception = inner });
- }
+ services.BindExports(Assembly.GetExecutingAssembly());
+ // Add the settings service
+ services.AddSingleton(settingsService);
+
+ var serviceProvider = services.BuildServiceProvider(new ServiceProviderOptions { ValidateOnBuild = true });
+
+ ExportProvider = new ExportProviderAdapter(serviceProvider);
+
+ return true;
}
catch (Exception ex)
{
- StartupExceptions.Add(new ExceptionData { Exception = ex });
+ if (ex is AggregateException aggregate)
+ StartupExceptions.AddRange(aggregate.InnerExceptions.Select(item => new ExceptionData(ex)));
+ else
+ StartupExceptions.Add(new(ex));
+
+ return false;
}
}
@@ -207,15 +209,7 @@ protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
- var output = new StringBuilder();
-
- if (StartupExceptions.FormatExceptions(output))
- {
- MessageBox.Show(output.ToString(), "Sorry we crashed!");
- Environment.Exit(1);
- }
-
- MainWindow = new MainWindow();
+ MainWindow = new();
MainWindow.Show();
}
diff --git a/ILSpy/AssemblyTree/AssemblyTreeModel.cs b/ILSpy/AssemblyTree/AssemblyTreeModel.cs
index b94668f14e..33570c34fc 100644
--- a/ILSpy/AssemblyTree/AssemblyTreeModel.cs
+++ b/ILSpy/AssemblyTree/AssemblyTreeModel.cs
@@ -21,18 +21,17 @@
using System.Collections.Specialized;
using System.ComponentModel;
using System.ComponentModel.Composition;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
-using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Navigation;
using System.Windows.Threading;
-using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Documentation;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.TypeSystem;
@@ -160,13 +159,15 @@ private bool HandleCommandLineArguments(CommandLineArguments args)
/// Called on startup or when passed arguments via WndProc from a second instance.
/// In the format case, spySettings is non-null; in the latter it is null.
///
- private void HandleCommandLineArgumentsAfterShowList(CommandLineArguments args, ISettingsProvider? spySettings = null)
+ private async Task HandleCommandLineArgumentsAfterShowList(CommandLineArguments args, ISettingsProvider? spySettings = null)
{
var sessionSettings = SettingsService.Instance.SessionSettings;
var relevantAssemblies = commandLineLoadedAssemblies.ToList();
commandLineLoadedAssemblies.Clear(); // clear references once we don't need them anymore
- NavigateOnLaunch(args.NavigateTo, sessionSettings.ActiveTreeViewPath, spySettings, relevantAssemblies);
+
+ await NavigateOnLaunch(args.NavigateTo, sessionSettings.ActiveTreeViewPath, spySettings, relevantAssemblies);
+
if (args.Search != null)
{
var searchPane = App.ExportProvider.GetExportedValue();
@@ -180,7 +181,7 @@ public async Task HandleSingleInstanceCommandLineArguments(string[] args)
{
var cmdArgs = CommandLineArguments.Create(args);
- await Dispatcher.InvokeAsync(() => {
+ await Dispatcher.InvokeAsync(async () => {
if (!HandleCommandLineArguments(cmdArgs))
return;
@@ -192,11 +193,11 @@ await Dispatcher.InvokeAsync(() => {
window.WindowState = WindowState.Normal;
}
- HandleCommandLineArgumentsAfterShowList(cmdArgs);
+ await HandleCommandLineArgumentsAfterShowList(cmdArgs);
});
}
- private async void NavigateOnLaunch(string? navigateTo, string[]? activeTreeViewPath, ISettingsProvider? spySettings, List relevantAssemblies)
+ private async Task NavigateOnLaunch(string? navigateTo, string[]? activeTreeViewPath, ISettingsProvider? spySettings, List relevantAssemblies)
{
var initialSelection = SelectedItem;
if (navigateTo != null)
@@ -386,26 +387,31 @@ public void Initialize()
Dispatcher.BeginInvoke(DispatcherPriority.Loaded, OpenAssemblies);
}
- private void OpenAssemblies()
+ private async Task OpenAssemblies()
{
- HandleCommandLineArgumentsAfterShowList(App.CommandLineArguments, SettingsService.Instance.SpySettings);
+ await HandleCommandLineArgumentsAfterShowList(App.CommandLineArguments, SettingsService.Instance.SpySettings);
- AvalonEditTextOutput output = new();
- if (FormatExceptions(App.StartupExceptions.ToArray(), output))
+ if (FormatExceptions(App.StartupExceptions.ToArray(), out var output))
{
+ output.Title = "Startup errors";
+
+ DockWorkspace.Instance.AddTabPage();
DockWorkspace.Instance.ShowText(output);
}
}
- private static bool FormatExceptions(App.ExceptionData[] exceptions, ITextOutput output)
+ private static bool FormatExceptions(App.ExceptionData[] exceptions, [NotNullWhen(true)] out AvalonEditTextOutput? output)
{
- var stringBuilder = new StringBuilder();
- var result = exceptions.FormatExceptions(stringBuilder);
- if (result)
- {
- output.Write(stringBuilder.ToString());
- }
- return result;
+ output = null;
+
+ var result = exceptions.FormatExceptions();
+ if (result.IsNullOrEmpty())
+ return false;
+
+ output = new();
+ output.Write(result);
+ return true;
+
}
private void ShowAssemblyList(string name)
diff --git a/ILSpy/ExportProviderAdapter.cs b/ILSpy/ExportProviderAdapter.cs
deleted file mode 100644
index d883248c4c..0000000000
--- a/ILSpy/ExportProviderAdapter.cs
+++ /dev/null
@@ -1,103 +0,0 @@
-// Copyright (c) 2024 Tom Englert for the SharpDevelop Team
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this
-// software and associated documentation files (the "Software"), to deal in the Software
-// without restriction, including without limitation the rights to use, copy, modify, merge,
-// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
-// to whom the Software is furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all copies or
-// substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
-// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
-// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
-// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-// DEALINGS IN THE SOFTWARE.
-
-using System;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-
-using Microsoft.VisualStudio.Composition;
-
-using TomsToolbox.Composition;
-using TomsToolbox.Essentials;
-
-namespace ICSharpCode.ILSpy;
-
-#nullable enable
-
-///
-/// Adapter for Microsoft.VisualStudio.Composition. to .
-///
-public sealed class ExportProviderAdapter : IExportProvider
-{
- private static readonly Type DefaultMetadataType = typeof(Dictionary);
-
- private readonly ExportProvider _exportProvider;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The export provider.
- public ExportProviderAdapter(ExportProvider exportProvider)
- {
- _exportProvider = exportProvider;
- }
-
- event EventHandler? IExportProvider.ExportsChanged { add { } remove { } }
-
- T IExportProvider.GetExportedValue(string? contractName) where T : class
- {
- return _exportProvider.GetExportedValue(contractName) ?? throw new InvalidOperationException($"No export found for type {typeof(T).FullName} with contract '{contractName}'");
- }
-
- T? IExportProvider.GetExportedValueOrDefault(string? contractName) where T : class
- {
- return _exportProvider.GetExportedValues(contractName).SingleOrDefault();
- }
-
- bool IExportProvider.TryGetExportedValue(string? contractName, [NotNullWhen(true)] out T? value) where T : class
- {
- value = _exportProvider.GetExportedValues(contractName).SingleOrDefault();
-
- return !Equals(value, default(T));
- }
-
- IEnumerable IExportProvider.GetExportedValues(string? contractName) where T : class
- {
- return _exportProvider.GetExportedValues(contractName);
- }
-
- IEnumerable