From 0068bf7308d82cd8c4f8165ef051c8961c57fcf3 Mon Sep 17 00:00:00 2001 From: Alexey Yakovlev Date: Sun, 22 Jan 2012 04:21:50 +0400 Subject: [PATCH] Added simple assembly resolution logic to GrammarLoader (work item #9865). --- Irony.GrammarExplorer/GrammarLoader.cs | 63 ++++++++++++++++++----- Irony.GrammarExplorer/fmSelectGrammars.cs | 27 +++++----- 2 files changed, 64 insertions(+), 26 deletions(-) diff --git a/Irony.GrammarExplorer/GrammarLoader.cs b/Irony.GrammarExplorer/GrammarLoader.cs index e45cd0e..0b1b079 100644 --- a/Irony.GrammarExplorer/GrammarLoader.cs +++ b/Irony.GrammarExplorer/GrammarLoader.cs @@ -3,8 +3,8 @@ * Copyright (c) Roman Ivantsov * This source code is subject to terms and conditions of the MIT License * for Irony. A copy of the license can be found in the License.txt file - * at the root of this distribution. - * By using this source code in any fashion, you are agreeing to be bound by the terms of the + * at the root of this distribution. + * By using this source code in any fashion, you are agreeing to be bound by the terms of the * MIT License. * You must not remove this notice from this software. * **********************************************************************************/ @@ -26,7 +26,36 @@ namespace Irony.GrammarExplorer { /// class GrammarLoader { private TimeSpan _autoRefreshDelay = TimeSpan.FromMilliseconds(500); - private Dictionary _cachedAssemblies = new Dictionary(); + private Dictionary _cachedGrammarAssemblies = new Dictionary(); + private static HashSet _probingPaths = new HashSet(); + private static Dictionary _loadedAssemblies = new Dictionary(); + private static bool _enableAssemblyResolution = false; + + static GrammarLoader() { + AppDomain.CurrentDomain.AssemblyLoad += (s, args) => _loadedAssemblies[args.LoadedAssembly.FullName] = args.LoadedAssembly; + AppDomain.CurrentDomain.AssemblyResolve += (s, args) => _enableAssemblyResolution ? FindAssembly(args.Name) : null; + } + + static Assembly FindAssembly(string assemblyName) { + if (_loadedAssemblies.ContainsKey(assemblyName)) { + return _loadedAssemblies[assemblyName]; + } + // use probing paths to look for assemblies + var fileName = assemblyName.Split(',').First() + ".dll"; + foreach (var path in _probingPaths) { + var fullName = Path.Combine(path, fileName); + if (File.Exists(fullName)) { + try { + return _loadedAssemblies[assemblyName] = Assembly.LoadFrom(fullName); + } + catch { + // the file seems to be bad, let's try to find another one + } + } + } + // assembly not found, don't search for it again + return _loadedAssemblies[assemblyName] = null; + } class CachedAssembly { public long FileSize; @@ -43,8 +72,15 @@ public Grammar CreateGrammar() { if (SelectedGrammar == null) return null; - var type = SelectedGrammarAssembly.GetType(SelectedGrammar.TypeName, true, true); - return Activator.CreateInstance(type) as Grammar; + // resolve dependencies while loading and creating grammars + _enableAssemblyResolution = true; + try { + var type = SelectedGrammarAssembly.GetType(SelectedGrammar.TypeName, true, true); + return Activator.CreateInstance(type) as Grammar; + } + finally { + _enableAssemblyResolution = false; + } } Assembly SelectedGrammarAssembly { @@ -54,9 +90,9 @@ Assembly SelectedGrammarAssembly { // create assembly cache entry as needed var location = SelectedGrammar.Location; - if (!_cachedAssemblies.ContainsKey(location)) { + if (!_cachedGrammarAssemblies.ContainsKey(location)) { var fileInfo = new FileInfo(location); - _cachedAssemblies[location] = + _cachedGrammarAssemblies[location] = new CachedAssembly { LastWriteTime = fileInfo.LastWriteTime, FileSize = fileInfo.Length, @@ -64,14 +100,14 @@ Assembly SelectedGrammarAssembly { }; // set up file system watcher - _cachedAssemblies[location].Watcher = CreateFileWatcher(location); + _cachedGrammarAssemblies[location].Watcher = CreateFileWatcher(location); } // get loaded assembly from cache if possible - var assembly = _cachedAssemblies[location].Assembly; + var assembly = _cachedGrammarAssemblies[location].Assembly; if (assembly == null) { assembly = LoadAssembly(location); - _cachedAssemblies[location].Assembly = assembly; + _cachedGrammarAssemblies[location].Assembly = assembly; } return assembly; @@ -88,7 +124,7 @@ private FileSystemWatcher CreateFileWatcher(string location) { return; // check if assembly was changed indeed to work around multiple FileSystemWatcher event firing - var cacheEntry = _cachedAssemblies[location]; + var cacheEntry = _cachedGrammarAssemblies[location]; var fileInfo = new FileInfo(location); if (cacheEntry.LastWriteTime == fileInfo.LastWriteTime && cacheEntry.FileSize == fileInfo.Length) return; @@ -115,7 +151,10 @@ private void OnAssemblyUpdated(string location) { AssemblyUpdated(this, EventArgs.Empty); } - Assembly LoadAssembly(string fileName) { + public static Assembly LoadAssembly(string fileName) { + // save assembly path for dependent assemblies probing + var path = Path.GetDirectoryName(fileName); + _probingPaths.Add(path); // 1. Assembly.Load doesn't block the file // 2. Assembly.Load doesn't check if the assembly is already loaded in the current AppDomain return Assembly.Load(File.ReadAllBytes(fileName)); diff --git a/Irony.GrammarExplorer/fmSelectGrammars.cs b/Irony.GrammarExplorer/fmSelectGrammars.cs index 7901b6f..8dac7ae 100644 --- a/Irony.GrammarExplorer/fmSelectGrammars.cs +++ b/Irony.GrammarExplorer/fmSelectGrammars.cs @@ -3,8 +3,8 @@ * Copyright (c) Roman Ivantsov * This source code is subject to terms and conditions of the MIT License * for Irony. A copy of the license can be found in the License.txt file - * at the root of this distribution. - * By using this source code in any fashion, you are agreeing to be bound by the terms of the + * at the root of this distribution. + * By using this source code in any fashion, you are agreeing to be bound by the terms of the * MIT License. * You must not remove this notice from this software. * **********************************************************************************/ @@ -20,7 +20,7 @@ using System.Windows.Forms; using System.Reflection; using Irony.Parsing; -using System.IO; +using System.IO; namespace Irony.GrammarExplorer { public partial class fmSelectGrammars : Form { @@ -30,25 +30,25 @@ public fmSelectGrammars() { public static GrammarItemList SelectGrammars(string assemblyPath, GrammarItemList loadedGrammars) { var fromGrammars = LoadGrammars(assemblyPath); - if (fromGrammars == null) + if (fromGrammars == null) return null; //fill the listbox and show the form fmSelectGrammars form = new fmSelectGrammars(); var listbox = form.lstGrammars; - listbox.Sorted = false; + listbox.Sorted = false; foreach(GrammarItem item in fromGrammars) { listbox.Items.Add(item); if (!ContainsGrammar(loadedGrammars, item)) listbox.SetItemChecked(listbox.Items.Count - 1, true); } - listbox.Sorted = true; + listbox.Sorted = true; if (form.ShowDialog() != DialogResult.OK) return null; GrammarItemList result = new GrammarItemList(); for (int i = 0; i < listbox.Items.Count; i++) { if (listbox.GetItemChecked(i)) { var item = listbox.Items[i] as GrammarItem; - item._loading = false; + item._loading = false; result.Add(item); } } @@ -56,18 +56,17 @@ public static GrammarItemList SelectGrammars(string assemblyPath, GrammarItemLis } private static GrammarItemList LoadGrammars(string assemblyPath) { - Assembly asm = null; + Assembly asm = null; try { - // enforce loading every time, even if assembly name is not changed - asm = Assembly.Load(File.ReadAllBytes(assemblyPath)); + asm = GrammarLoader.LoadAssembly(assemblyPath); } catch (Exception ex) { MessageBox.Show("Failed to load assembly: " + ex.Message); - return null; + return null; } var types = asm.GetTypes(); var grammars = new GrammarItemList(); foreach (Type t in types) { - if (t.IsAbstract) continue; + if (t.IsAbstract) continue; if (!t.IsSubclassOf(typeof(Grammar))) continue; grammars.Add(new GrammarItem(t, assemblyPath)); } @@ -82,13 +81,13 @@ private static bool ContainsGrammar(GrammarItemList items, GrammarItem item) { foreach (var listItem in items) if (listItem.TypeName == item.TypeName && listItem.Location == item.Location) return true; - return false; + return false; } private void btnCheckUncheck_Click(object sender, EventArgs e) { bool check = sender == btnCheckAll; for (int i = 0; i < lstGrammars.Items.Count; i++) - lstGrammars.SetItemChecked(i, check); + lstGrammars.SetItemChecked(i, check); } }//class