mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
338 lines
11 KiB
338 lines
11 KiB
// 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 |
|
{ |
|
/// <summary> |
|
/// Contains project contents read from external assemblies. |
|
/// Caches loaded assemblies in memory and optionally also to disk. |
|
/// </summary> |
|
public class ProjectContentRegistry : IDisposable |
|
{ |
|
internal DomPersistence persistence; |
|
Dictionary<string, IProjectContent> contents = new Dictionary<string, IProjectContent>(StringComparer.OrdinalIgnoreCase); |
|
|
|
/// <summary> |
|
/// Disposes all project contents stored in this registry. |
|
/// </summary> |
|
public virtual void Dispose() |
|
{ |
|
List<IProjectContent> list; |
|
lock (contents) { |
|
list = new List<IProjectContent>(contents.Values); |
|
contents.Clear(); |
|
} |
|
// dispose outside the lock |
|
foreach (IProjectContent pc in list) { |
|
pc.Dispose(); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Activate caching assemblies to disk. |
|
/// Cache files will be saved in the specified directory. |
|
/// </summary> |
|
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; |
|
|
|
/// <summary> |
|
/// 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. |
|
/// </summary> |
|
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<IProjectContent> GetLoadedProjectContents() |
|
{ |
|
lock (contents) { // we need to return a copy because we have to lock |
|
return new List<IProjectContent>(contents.Values); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// 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! |
|
/// </summary> |
|
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<string> keys = new List<string>(); |
|
foreach (KeyValuePair<string, IProjectContent> 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; |
|
} |
|
} |
|
} |
|
}
|
|
|