diff --git a/ILSpy/AboutPage.cs b/ILSpy/AboutPage.cs index 227bf9a8a..3f4517b7b 100644 --- a/ILSpy/AboutPage.cs +++ b/ILSpy/AboutPage.cs @@ -81,7 +81,7 @@ namespace ICSharpCode.ILSpy }; }); output.WriteLine(); - foreach (var plugin in App.CompositionContainer.GetExportedValues()) + foreach (var plugin in App.ExportProvider.GetExportedValues()) plugin.Write(output); output.WriteLine(); using (Stream s = typeof(AboutPage).Assembly.GetManifestResourceStream(typeof(AboutPage), "README.txt")) { diff --git a/ILSpy/App.xaml.cs b/ILSpy/App.xaml.cs index a09fa4a9d..c1f7d649d 100644 --- a/ILSpy/App.xaml.cs +++ b/ILSpy/App.xaml.cs @@ -18,7 +18,6 @@ using System; using System.Collections.Generic; -using System.ComponentModel.Composition.Hosting; using System.Diagnostics; using System.IO; using System.Linq; @@ -32,6 +31,8 @@ using System.Windows.Threading; using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.Options; +using Microsoft.VisualStudio.Composition; + namespace ICSharpCode.ILSpy { /// @@ -39,15 +40,18 @@ namespace ICSharpCode.ILSpy /// public partial class App : Application { - static CompositionContainer compositionContainer; - - public static CompositionContainer CompositionContainer { - get { return compositionContainer; } - } internal static CommandLineArguments CommandLineArguments; + + static ExportProvider exportProvider; + + public static ExportProvider ExportProvider => exportProvider; + + static IExportProviderFactory exportProviderFactory; - internal static IList StartupExceptions = new List(); + public static IExportProviderFactory ExportProviderFactory => exportProviderFactory; + + internal static readonly IList StartupExceptions = new List(); internal class ExceptionData { @@ -68,32 +72,52 @@ namespace ICSharpCode.ILSpy } InitializeComponent(); - var catalog = new AggregateCatalog(); - catalog.Catalogs.Add(new AssemblyCatalog(typeof(App).Assembly)); - // Don't use DirectoryCatalog, that causes problems if the plugins are from the Internet zone - // see http://stackoverflow.com/questions/8063841/mef-loading-plugins-from-a-network-shared-folder - string appPath = Path.GetDirectoryName(typeof(App).Module.FullyQualifiedName); - foreach (string plugin in Directory.GetFiles(appPath, "*.Plugin.dll")) { - string shortName = Path.GetFileNameWithoutExtension(plugin); - try { - var asm = Assembly.Load(shortName); - asm.GetTypes(); - catalog.Catalogs.Add(new AssemblyCatalog(asm)); - } catch (Exception ex) { - // 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 ExceptionData { Exception = ex, PluginName = shortName }); + // 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 pluginDir = Path.GetDirectoryName(typeof(App).Module.FullyQualifiedName); + if (pluginDir != null) { + foreach (var plugin in Directory.GetFiles(pluginDir, "*.Plugin.dll")) { + var name = Path.GetFileNameWithoutExtension(plugin); + try { + var asm = Assembly.Load(name); + var parts = discovery.CreatePartsAsync(asm).Result; + catalog = catalog.AddParts(parts); + } catch (Exception ex) { + StartupExceptions.Add(new ExceptionData { Exception = ex, PluginName = name }); + } + } } + // Add the built-in parts + catalog = catalog.AddParts(discovery.CreatePartsAsync(Assembly.GetExecutingAssembly()).Result); + // 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); + exportProviderFactory = config.CreateExportProviderFactory(); + exportProvider = 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 (Exception ex) { + StartupExceptions.Add(new ExceptionData { Exception = ex }); } - - compositionContainer = new CompositionContainer(catalog); if (!System.Diagnostics.Debugger.IsAttached) { AppDomain.CurrentDomain.UnhandledException += ShowErrorBox; Dispatcher.CurrentDispatcher.UnhandledException += Dispatcher_UnhandledException; } TaskScheduler.UnobservedTaskException += DotNet40_UnobservedTaskException; - Languages.Initialize(compositionContainer); + Languages.Initialize(exportProvider); EventManager.RegisterClassHandler(typeof(Window), Hyperlink.RequestNavigateEvent, diff --git a/ILSpy/ContextMenuEntry.cs b/ILSpy/ContextMenuEntry.cs index fc2bdce88..502a0b435 100644 --- a/ILSpy/ContextMenuEntry.cs +++ b/ILSpy/ContextMenuEntry.cs @@ -149,21 +149,22 @@ namespace ICSharpCode.ILSpy readonly SharpTreeView treeView; readonly DecompilerTextView textView; readonly ListBox listBox; + readonly Lazy[] entries; - [ImportMany(typeof(IContextMenuEntry))] - Lazy[] entries = null; + private ContextMenuProvider() + { + entries = App.ExportProvider.GetExports().ToArray(); + } - ContextMenuProvider(SharpTreeView treeView, DecompilerTextView textView = null) + ContextMenuProvider(SharpTreeView treeView, DecompilerTextView textView = null) : this() { this.treeView = treeView; this.textView = textView; - App.CompositionContainer.ComposeParts(this); } - ContextMenuProvider(ListBox listBox) + ContextMenuProvider(ListBox listBox) : this() { this.listBox = listBox; - App.CompositionContainer.ComposeParts(this); } void treeView_ContextMenuOpening(object sender, ContextMenuEventArgs e) diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index db27dfaf7..46c233051 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -42,7 +42,6 @@ - @@ -51,6 +50,7 @@ + diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs index e76746b53..c66d72bc8 100644 --- a/ILSpy/Languages/CSharpLanguage.cs +++ b/ILSpy/Languages/CSharpLanguage.cs @@ -390,7 +390,7 @@ namespace ICSharpCode.ILSpy } return new[] { Tuple.Create("EmbeddedResource", fileName) }; } - foreach (var handler in App.CompositionContainer.GetExportedValues()) { + foreach (var handler in App.ExportProvider.GetExportedValues()) { if (handler.CanHandle(fileName, options)) { entryStream.Position = 0; return new[] { Tuple.Create(handler.EntryType, handler.WriteResourceToFile(assembly, fileName, entryStream, options)) }; diff --git a/ILSpy/Languages/Languages.cs b/ILSpy/Languages/Languages.cs index 2312f7ec5..d8ef5b8ff 100644 --- a/ILSpy/Languages/Languages.cs +++ b/ILSpy/Languages/Languages.cs @@ -18,9 +18,10 @@ using System.Collections.Generic; using System.Collections.ObjectModel; -using System.ComponentModel.Composition.Hosting; using System.Linq; +using Microsoft.VisualStudio.Composition; + namespace ICSharpCode.ILSpy { public static class Languages @@ -39,10 +40,10 @@ namespace ICSharpCode.ILSpy get { return allLanguages; } } - internal static void Initialize(CompositionContainer composition) + internal static void Initialize(ExportProvider ep) { List languages = new List(); - languages.AddRange(composition.GetExportedValues()); + languages.AddRange(ep.GetExportedValues()); languages.Sort((a, b) => a.Name.CompareTo(b.Name)); #if DEBUG languages.AddRange(ILAstLanguage.GetDebugLanguages()); diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index 93c0eb658..8a234cbeb 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -20,7 +20,6 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; -using System.ComponentModel.Composition; using System.Diagnostics; using System.IO; using System.Linq; @@ -55,8 +54,7 @@ namespace ICSharpCode.ILSpy AssemblyList assemblyList; AssemblyListTreeNode assemblyListTreeNode; - [Import] - DecompilerTextView decompilerTextView = null; + readonly DecompilerTextView decompilerTextView; static MainWindow instance; @@ -80,7 +78,7 @@ namespace ICSharpCode.ILSpy this.DataContext = sessionSettings; InitializeComponent(); - App.CompositionContainer.ComposeParts(this); + decompilerTextView = App.ExportProvider.GetExportedValue(); mainPane.Content = decompilerTextView; if (sessionSettings.SplitterPosition > 0 && sessionSettings.SplitterPosition < 1) { @@ -105,13 +103,12 @@ namespace ICSharpCode.ILSpy } #region Toolbar extensibility - [ImportMany("ToolbarCommand", typeof(ICommand))] - Lazy[] toolbarCommands = null; void InitToolbar() { int navigationPos = 0; int openPos = 1; + var toolbarCommands = App.ExportProvider.GetExports("ToolbarCommand"); foreach (var commandGroup in toolbarCommands.OrderBy(c => c.Metadata.ToolbarOrder).GroupBy(c => c.Metadata.ToolbarCategory)) { if (commandGroup.Key == "Navigation") { foreach (var command in commandGroup) { @@ -148,11 +145,10 @@ namespace ICSharpCode.ILSpy #endregion #region Main Menu extensibility - [ImportMany("MainMenuCommand", typeof(ICommand))] - Lazy[] mainMenuCommands = null; void InitMainMenu() { + var mainMenuCommands = App.ExportProvider.GetExports("MainMenuCommand"); foreach (var topLevelMenu in mainMenuCommands.OrderBy(c => c.Metadata.MenuOrder).GroupBy(c => c.Metadata.Menu)) { var topLevelMenuItem = mainMenu.Items.OfType().FirstOrDefault(m => (m.Header as string) == topLevelMenu.Key); foreach (var category in topLevelMenu.GroupBy(c => c.Metadata.MenuCategory)) { diff --git a/ILSpy/Options/OptionsDialog.xaml.cs b/ILSpy/Options/OptionsDialog.xaml.cs index 8bd96661f..6908ba37a 100644 --- a/ILSpy/Options/OptionsDialog.xaml.cs +++ b/ILSpy/Options/OptionsDialog.xaml.cs @@ -30,13 +30,17 @@ namespace ICSharpCode.ILSpy.Options /// public partial class OptionsDialog : Window { - [ImportMany("OptionPages", typeof(UIElement), RequiredCreationPolicy = CreationPolicy.NonShared)] - Lazy[] optionPages = null; + + readonly Lazy[] optionPages; public OptionsDialog() { InitializeComponent(); - App.CompositionContainer.ComposeParts(this); + // These used to have [ImportMany(..., RequiredCreationPolicy = CreationPolicy.NonShared)], so they use their own + // ExportProvider instance. + // FIXME: Ideally, the export provider should be disposed when it's no longer needed. + var ep = App.ExportProviderFactory.CreateExportProvider(); + this.optionPages = ep.GetExports("OptionPages").ToArray(); ILSpySettings settings = ILSpySettings.Load(); foreach (var optionPage in optionPages.OrderBy(p => p.Metadata.Order)) { TabItem tabItem = new TabItem(); diff --git a/ILSpy/TreeNodes/ResourceNodes/ResourceEntryNode.cs b/ILSpy/TreeNodes/ResourceNodes/ResourceEntryNode.cs index 7b642afd7..ec74ad193 100644 --- a/ILSpy/TreeNodes/ResourceNodes/ResourceEntryNode.cs +++ b/ILSpy/TreeNodes/ResourceNodes/ResourceEntryNode.cs @@ -61,7 +61,7 @@ namespace ICSharpCode.ILSpy.TreeNodes public static ILSpyTreeNode Create(string key, object data) { ILSpyTreeNode result = null; - foreach (var factory in App.CompositionContainer.GetExportedValues()) { + foreach (var factory in App.ExportProvider.GetExportedValues()) { result = factory.CreateNode(key, data); if (result != null) return result; diff --git a/ILSpy/TreeNodes/ResourceNodes/ResourceTreeNode.cs b/ILSpy/TreeNodes/ResourceNodes/ResourceTreeNode.cs index 26bfb0076..0e7355774 100644 --- a/ILSpy/TreeNodes/ResourceNodes/ResourceTreeNode.cs +++ b/ILSpy/TreeNodes/ResourceNodes/ResourceTreeNode.cs @@ -122,7 +122,7 @@ namespace ICSharpCode.ILSpy.TreeNodes public static ILSpyTreeNode Create(Resource resource) { ILSpyTreeNode result = null; - foreach (var factory in App.CompositionContainer.GetExportedValues()) { + foreach (var factory in App.ExportProvider.GetExportedValues()) { result = factory.CreateNode(resource); if (result != null) break;