// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Text; using System.Threading; using System.Xml; namespace ICSharpCode.SharpDevelop.Dom { /// /// Contains project contents read from external assemblies. /// Caches loaded assemblies in memory and optionally also to disk. /// public class ProjectContentRegistry : IDisposable { internal DomPersistence persistence; Dictionary contents = new Dictionary(StringComparer.OrdinalIgnoreCase); /// /// Disposes all project contents stored in this registry. /// public virtual void Dispose() { List list; lock (contents) { list = new List(contents.Values); contents.Clear(); } // dispose outside the lock foreach (IProjectContent pc in list) { pc.Dispose(); } } /// /// Activate caching assemblies to disk. /// Cache files will be saved in the specified directory. /// public DomPersistence ActivatePersistence(string cacheDirectory) { if (cacheDirectory == null) { throw new ArgumentNullException("cacheDirectory"); } else if (persistence != null && cacheDirectory == persistence.CacheDirectory) { return persistence; } else { persistence = new DomPersistence(cacheDirectory, this); return persistence; } } ReflectionProjectContent mscorlibContent; /// /// Runs the method inside the lock of the registry. /// Use this method if you want to call multiple methods on the ProjectContentRegistry and ensure /// that no other thread accesses the registry while your method runs. /// public void RunLocked(ThreadStart method) { lock (contents) { method(); } } public virtual IProjectContent Mscorlib { get { if (mscorlibContent != null) return mscorlibContent; lock (contents) { if (contents.ContainsKey("mscorlib")) { mscorlibContent = (ReflectionProjectContent)contents["mscorlib"]; return contents["mscorlib"]; } int time = LoggingService.IsDebugEnabled ? Environment.TickCount : 0; LoggingService.Debug("Loading PC for mscorlib..."); if (persistence != null) { mscorlibContent = persistence.LoadProjectContentByAssemblyName(MscorlibAssembly.FullName); if (mscorlibContent != null) { if (time != 0) { LoggingService.Debug("Loaded mscorlib from cache in " + (Environment.TickCount - time) + " ms"); } } } if (mscorlibContent == null) { // We're using Cecil now for everything to find bugs in CecilReader faster //mscorlibContent = CecilReader.LoadAssembly(MscorlibAssembly.Location, this); // After SD 2.1 Beta 2, we're back to Reflection mscorlibContent = new ReflectionProjectContent(MscorlibAssembly, this); if (time != 0) { //LoggingService.Debug("Loaded mscorlib with Cecil in " + (Environment.TickCount - time) + " ms"); LoggingService.Debug("Loaded mscorlib with Reflection in " + (Environment.TickCount - time) + " ms"); } if (persistence != null) { persistence.SaveProjectContent(mscorlibContent); LoggingService.Debug("Saved mscorlib to cache"); } } contents["mscorlib"] = mscorlibContent; contents[mscorlibContent.AssemblyFullName] = mscorlibContent; contents[mscorlibContent.AssemblyLocation] = mscorlibContent; return mscorlibContent; } } } public virtual ICollection GetLoadedProjectContents() { lock (contents) { // we need to return a copy because we have to lock return new List(contents.Values); } } /// /// Unloads the specified project content, causing it to be reloaded when /// GetProjectContentForReference is called the next time. /// Warning: do not unload project contents that are still in use! Doing so will result /// in an ObjectDisposedException when the unloaded project content is used the next time! /// public void UnloadProjectContent(IProjectContent pc) { if (pc == null) throw new ArgumentNullException("pc"); LoggingService.Debug("ProjectContentRegistry.UnloadProjectContent: " + pc); lock (contents) { // find all keys used for the project content - might be the short name/full name/file name List keys = new List(); foreach (KeyValuePair pair in contents) { if (pair.Value == pc) keys.Add(pair.Key); } foreach (string key in keys) { contents.Remove(key); } } pc.Dispose(); } public IProjectContent GetExistingProjectContent(DomAssemblyName assembly) { return GetExistingProjectContent(assembly.FullName); } public virtual IProjectContent GetExistingProjectContent(string fileNameOrAssemblyName) { lock (contents) { if (contents.ContainsKey(fileNameOrAssemblyName)) { return contents[fileNameOrAssemblyName]; } } // GetProjectContentForReference supports redirecting .NET base assemblies to the correct version, // so GetExistingProjectContent must support it, too (otherwise assembly interdependencies fail // to resolve correctly when a .NET 1.0 assembly is used in a .NET 2.0 project) int pos = fileNameOrAssemblyName.IndexOf(','); if (pos > 0) { string shortName = fileNameOrAssemblyName.Substring(0, pos); Assembly assembly = GetDefaultAssembly(shortName); if (assembly != null) { lock (contents) { if (contents.ContainsKey(assembly.FullName)) { return contents[assembly.FullName]; } } } } return null; } public virtual IProjectContent GetProjectContentForReference(string itemInclude, string itemFileName) { lock (contents) { IProjectContent pc = GetExistingProjectContent(itemFileName); if (pc != null) { return pc; } LoggingService.Debug("Loading PC for " + itemInclude); string shortName = itemInclude; int pos = shortName.IndexOf(','); if (pos > 0) shortName = shortName.Substring(0, pos); #if DEBUG int time = Environment.TickCount; #endif try { pc = LoadProjectContent(itemInclude, itemFileName); } catch (BadImageFormatException ex) { HostCallback.ShowAssemblyLoadErrorInternal(itemFileName, itemInclude, ex.Message); } catch (Exception ex) { HostCallback.ShowError("Error loading assembly " + itemFileName, ex); } finally { #if DEBUG LoggingService.Debug(string.Format("Loaded {0} in {1}ms", itemInclude, Environment.TickCount - time)); #endif } if (pc != null) { ReflectionProjectContent reflectionProjectContent = pc as ReflectionProjectContent; if (reflectionProjectContent != null) { reflectionProjectContent.InitializeReferences(); if (reflectionProjectContent.AssemblyFullName != null) { contents[reflectionProjectContent.AssemblyFullName] = pc; } } contents[itemInclude] = pc; contents[itemFileName] = pc; } return pc; } } protected virtual IProjectContent LoadProjectContent(string itemInclude, string itemFileName) { string shortName = itemInclude; int pos = shortName.IndexOf(','); if (pos > 0) shortName = shortName.Substring(0, pos); Assembly assembly = GetDefaultAssembly(shortName); ReflectionProjectContent pc = null; if (assembly != null) { if (persistence != null) { pc = persistence.LoadProjectContentByAssemblyName(assembly.FullName); } if (pc == null) { pc = new ReflectionProjectContent(assembly, this); if (persistence != null) { persistence.SaveProjectContent(pc); } } } else { // find real file name for cecil: if (File.Exists(itemFileName)) { if (persistence != null) { pc = persistence.LoadProjectContentByAssemblyName(itemFileName); } if (pc == null) { pc = CecilReader.LoadAssembly(itemFileName, this); if (persistence != null) { persistence.SaveProjectContent(pc); } } } else { DomAssemblyName asmName = GacInterop.FindBestMatchingAssemblyName(itemInclude); if (persistence != null && asmName != null) { //LoggingService.Debug("Looking up in DOM cache: " + asmName.FullName); pc = persistence.LoadProjectContentByAssemblyName(asmName.FullName); } if (pc == null && asmName != null) { string subPath = Path.Combine(asmName.ShortName, GetVersion__Token(asmName)); subPath = Path.Combine(subPath, asmName.ShortName + ".dll"); foreach (string dir in Directory.GetDirectories(GacInterop.GacRootPathV4, "GAC*")) { itemFileName = Path.Combine(dir, subPath); if (File.Exists(itemFileName)) { pc = CecilReader.LoadAssembly(itemFileName, this); if (persistence != null) { persistence.SaveProjectContent(pc); } break; } } } if (pc == null) { HostCallback.ShowAssemblyLoadErrorInternal(itemFileName, itemInclude, "Could not find assembly file."); } } } return pc; } static string GetVersion__Token(DomAssemblyName asmName) { StringBuilder b = new StringBuilder(asmName.Version.ToString()); b.Append("__"); b.Append(asmName.PublicKeyToken); return b.ToString(); } public static Assembly MscorlibAssembly { get { return typeof(object).Assembly; } } public static Assembly SystemAssembly { get { return typeof(Uri).Assembly; } } protected virtual Assembly GetDefaultAssembly(string shortName) { // These assemblies are already loaded by SharpDevelop, so we // don't need to load them in a separate AppDomain/with Cecil. switch (shortName) { case "mscorlib": return MscorlibAssembly; case "System": // System != mscorlib !!! return SystemAssembly; case "System.Core": return typeof(System.Linq.Enumerable).Assembly; case "System.Xml": case "System.XML": return typeof(XmlReader).Assembly; case "System.Data": case "System.Windows.Forms": case "System.Runtime.Remoting": return Assembly.Load(shortName + ", Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"); case "System.Configuration": case "System.Design": case "System.Deployment": case "System.Drawing": case "System.Drawing.Design": case "System.ServiceProcess": case "System.Security": case "System.Management": case "System.Messaging": case "System.Web": case "System.Web.Services": return Assembly.Load(shortName + ", Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"); case "Microsoft.VisualBasic": return Assembly.Load("Microsoft.VisualBasic, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"); default: return null; } } } }