Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
AssemblyContainer.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 using System;
4 using System.Collections.Generic;
5 using System.IO;
6 using System.Linq;
7 using System.Reflection;
8 using System.Runtime.CompilerServices;
9 using SiliconStudio.Core.Diagnostics;
10 
11 namespace SiliconStudio.Core.Reflection
12 {
13  public class AssemblyContainer
14  {
15  private readonly Dictionary<string, Assembly> loadedAssemblies = new Dictionary<string, Assembly>(StringComparer.InvariantCultureIgnoreCase);
16  private static readonly string[] KnownAssemblyExtensions = { ".dll", ".exe" };
17  [ThreadStatic]
18  private static AssemblyContainer loadingInstance;
19 
20  [ThreadStatic]
21  private static LoggerResult log;
22 
23  [ThreadStatic]
24  private static List<string> searchDirectoryList;
25 
26  /// <summary>
27  /// The default assembly container loader.
28  /// </summary>
29  public static readonly AssemblyContainer Default = new AssemblyContainer();
30 
31  static AssemblyContainer()
32  {
33  AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
34  }
35 
37  {
38  }
39 
40  public Assembly LoadAssemblyFromPath(string assemblyFullPath, ILogger outputLog = null, List<string> lookupDirectoryList = null)
41  {
42  if (assemblyFullPath == null) throw new ArgumentNullException("assemblyFullPath");
43 
44  log = new LoggerResult();
45 
46  lookupDirectoryList = lookupDirectoryList ?? new List<string>();
47  assemblyFullPath = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, assemblyFullPath));
48  var assemblyDirectory = Path.GetDirectoryName(assemblyFullPath);
49 
50  if (assemblyDirectory == null || !Directory.Exists(assemblyDirectory))
51  {
52  throw new ArgumentException("Invalid assembly path. Doesn't contain directory information");
53  }
54 
55  if (!lookupDirectoryList.Contains(assemblyDirectory, StringComparer.InvariantCultureIgnoreCase))
56  {
57  lookupDirectoryList.Add(assemblyDirectory);
58  }
59 
60  var previousLookupList = searchDirectoryList;
61  try
62  {
63  loadingInstance = this;
64  searchDirectoryList = lookupDirectoryList;
65 
66  return LoadAssemblyFromPathInternal(assemblyFullPath);
67  }
68  finally
69  {
70  loadingInstance = null;
71  searchDirectoryList = previousLookupList;
72 
73  if (outputLog != null)
74  {
75  log.CopyTo(outputLog);
76  }
77  }
78  }
79 
80  private Assembly LoadAssemblyByName(string assemblyName)
81  {
82  if (assemblyName == null) throw new ArgumentNullException("assemblyName");
83 
84  var assemblyPartialPathList = new List<string>();
85  assemblyPartialPathList.AddRange(KnownAssemblyExtensions.Select(knownExtension => assemblyName + knownExtension));
86 
87  foreach (var directoryPath in searchDirectoryList)
88  {
89  foreach (var assemblyPartialPath in assemblyPartialPathList)
90  {
91  var assemblyFullPath = Path.Combine(directoryPath, assemblyPartialPath);
92  if (File.Exists(assemblyFullPath))
93  {
94  return LoadAssemblyFromPathInternal(assemblyFullPath);
95  }
96  }
97  }
98  return null;
99  }
100 
101  private string CopySafeShadow(string path)
102  {
103  for(int i = 1; i < 20; i++)
104  {
105  var shadowName = Path.ChangeExtension(path, "shadow" + i);
106  try
107  {
108  File.Copy(path, shadowName, true);
109  return shadowName;
110  }
111  catch (Exception)
112  {
113  }
114  }
115 
116  return null;
117  }
118 
119  private Assembly LoadAssemblyFromPathInternal(string assemblyFullPath)
120  {
121  if (assemblyFullPath == null) throw new ArgumentNullException("assemblyFullPath");
122 
123  string safeShadowPath = null;
124  try
125  {
126  assemblyFullPath = Path.GetFullPath(assemblyFullPath);
127 
128  lock (loadedAssemblies)
129  {
130  Assembly assembly;
131  if (loadedAssemblies.TryGetValue(assemblyFullPath, out assembly))
132  {
133  return assembly;
134  }
135 
136  if (!File.Exists(assemblyFullPath))
137  return null;
138 
139  // Create a shadow copy of the assembly to load
140  safeShadowPath = CopySafeShadow(assemblyFullPath);
141  if (safeShadowPath == null)
142  {
143  log.Error("Cannot create a shadow copy for assembly [{0}]", assemblyFullPath);
144  return null;
145  }
146 
147  // Load the assembly into the current AppDomain
148  // TODO: Is using AppDomain would provide more opportunities for unloading?
149  assembly = Assembly.LoadFile(safeShadowPath);
150  loadedAssemblies.Add(assemblyFullPath, assembly);
151 
152  // Force assembly resolve with proper name
153  // (doing it here, because if done later, loadingInstance will be set to null and it won't work)
154  Assembly.Load(assembly.FullName);
155 
156  // Make sure that all referenced assemblies are loaded here
157  foreach (var assemblyRef in assembly.GetReferencedAssemblies())
158  {
159  Assembly.Load(assemblyRef);
160  }
161 
162  // Make sure that Module initializer are called
163  if (assembly.GetTypes().Length > 0)
164  {
165  foreach (var module in assembly.Modules)
166  {
167  RuntimeHelpers.RunModuleConstructor(module.ModuleHandle);
168  }
169  }
170  return assembly;
171  }
172  }
173  catch (Exception exception)
174  {
175  log.Error("Error while loading assembly reference [{0}]", exception, safeShadowPath ?? assemblyFullPath);
176  var loaderException = exception as ReflectionTypeLoadException;
177  if (loaderException != null)
178  {
179  foreach (var exceptionForType in loaderException.LoaderExceptions)
180  {
181  log.Error("Unable to load type. See exception.", exceptionForType);
182  }
183  }
184  }
185  return null;
186  }
187 
188  static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
189  {
190  // If it is handled by current thread, then we can handle it here.
191  var container = loadingInstance;
192  if (container != null)
193  {
194  var assemblyName = new AssemblyName(args.Name);
195  return container.LoadAssemblyByName(assemblyName.Name);
196  }
197  return null;
198  }
199  }
200 }
SiliconStudio.Core.Diagnostics.LoggerResult LoggerResult
A logger that stores messages locally useful for internal log scenarios.
Definition: LoggerResult.cs:14
Assembly LoadAssemblyFromPath(string assemblyFullPath, ILogger outputLog=null, List< string > lookupDirectoryList=null)
Use the default mode depending on the type of the field/property.
System.IO.File File
Interface for logging.
Definition: ILogger.cs:8