1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
5 using System.Collections.Generic;
6 using System.Diagnostics;
8 using System.Reflection;
9 using System.Runtime.CompilerServices;
10 using System.Runtime.InteropServices;
11 using System.Threading;
13 namespace System.Runtime.Loader
15 public partial class AssemblyLoadContext
17 private enum InternalState
20 /// The ALC is alive (default)
25 /// The unload process has started, the Unloading event will be called
26 /// once the underlying LoaderAllocator has been finalized
31 private static readonly Dictionary<long, WeakReference<AssemblyLoadContext>> s_allContexts = new Dictionary<long, WeakReference<AssemblyLoadContext>>();
32 private static long s_nextId;
34 // Indicates the state of this ALC (Alive or in Unloading state)
35 private InternalState _state;
37 // Id used by s_allContexts
38 private readonly long _id;
40 // synchronization primitive to protect against usage of this instance while unloading
41 private readonly object _unloadLock;
43 // Contains the reference to VM's representation of the AssemblyLoadContext
44 private readonly IntPtr _nativeAssemblyLoadContext;
46 protected AssemblyLoadContext() : this(false, false, null)
50 protected AssemblyLoadContext(bool isCollectible) : this(false, isCollectible, null)
54 public AssemblyLoadContext(string name, bool isCollectible = false) : this(false, isCollectible, name)
58 private protected AssemblyLoadContext(bool representsTPALoadContext, bool isCollectible, string name)
60 // Initialize the VM side of AssemblyLoadContext if not already done.
61 IsCollectible = isCollectible;
65 // The _unloadLock needs to be assigned after the IsCollectible to ensure proper behavior of the finalizer
66 // even in case the following allocation fails or the thread is aborted between these two lines.
67 _unloadLock = new object();
71 // For non collectible AssemblyLoadContext, the finalizer should never be called and thus the AssemblyLoadContext should not
72 // be on the finalizer queue.
73 GC.SuppressFinalize(this);
76 // If this is a collectible ALC, we are creating a weak handle tracking resurrection otherwise we use a strong handle
77 var thisHandle = GCHandle.Alloc(this, IsCollectible ? GCHandleType.WeakTrackResurrection : GCHandleType.Normal);
78 var thisHandlePtr = GCHandle.ToIntPtr(thisHandle);
79 _nativeAssemblyLoadContext = InitializeAssemblyLoadContext(thisHandlePtr, representsTPALoadContext, isCollectible);
81 // Add this instance to the list of alive ALC
85 s_allContexts.Add(_id, new WeakReference<AssemblyLoadContext>(this, true));
89 ~AssemblyLoadContext()
91 // Use the _unloadLock as a guard to detect the corner case when the constructor of the AssemblyLoadContext was not executed
92 // e.g. due to the JIT failing to JIT it.
93 if (_unloadLock != null)
95 // Only valid for a Collectible ALC. Non-collectible ALCs have the finalizer suppressed.
96 Debug.Assert(IsCollectible);
97 // We get here only in case the explicit Unload was not initiated.
98 Debug.Assert(_state != InternalState.Unloading);
103 private void RaiseUnloadEvent()
105 // Ensure that we raise the Unload event only once
106 Interlocked.Exchange(ref Unloading, null)?.Invoke(this);
109 private void InitiateUnload()
113 // When in Unloading state, we are not supposed to be called on the finalizer
114 // as the native side is holding a strong reference after calling Unload
117 Debug.Assert(_state == InternalState.Alive);
119 var thisStrongHandle = GCHandle.Alloc(this, GCHandleType.Normal);
120 var thisStrongHandlePtr = GCHandle.ToIntPtr(thisStrongHandle);
121 // The underlying code will transform the original weak handle
122 // created by InitializeLoadContext to a strong handle
123 PrepareForAssemblyLoadContextRelease(_nativeAssemblyLoadContext, thisStrongHandlePtr);
125 _state = InternalState.Unloading;
130 s_allContexts.Remove(_id);
134 public IEnumerable<Assembly> Assemblies
138 foreach (Assembly a in GetLoadedAssemblies())
140 AssemblyLoadContext alc = GetLoadContext(a);
150 // Event handler for resolving native libraries.
151 // This event is raised if the native library could not be resolved via
152 // the default resolution logic [including AssemblyLoadContext.LoadUnmanagedDll()]
154 // Inputs: Invoking assembly, and library name to resolve
155 // Returns: A handle to the loaded native library
156 public event Func<Assembly, string, IntPtr> ResolvingUnmanagedDll;
158 // Event handler for resolving managed assemblies.
159 // This event is raised if the managed assembly could not be resolved via
160 // the default resolution logic [including AssemblyLoadContext.Load()]
162 // Inputs: The AssemblyLoadContext and AssemblyName to be loaded
163 // Returns: The Loaded assembly object.
164 public event Func<AssemblyLoadContext, AssemblyName, Assembly> Resolving;
166 public event Action<AssemblyLoadContext> Unloading;
168 // Occurs when an Assembly is loaded
169 public static event AssemblyLoadEventHandler AssemblyLoad;
171 // Occurs when resolution of type fails
172 public static event ResolveEventHandler TypeResolve;
174 // Occurs when resolution of resource fails
175 public static event ResolveEventHandler ResourceResolve;
177 // Occurs when resolution of assembly fails
178 // This event is fired after resolve events of AssemblyLoadContext fails
179 public static event ResolveEventHandler AssemblyResolve;
181 public static AssemblyLoadContext Default => DefaultAssemblyLoadContext.s_loadContext;
183 public bool IsCollectible { get; }
185 public string Name { get; }
187 public override string ToString() => "\"" + Name + "\" " + GetType().ToString() + " #" + _id;
189 public static IEnumerable<AssemblyLoadContext> All
193 AssemblyLoadContext d = AssemblyLoadContext.Default; // Ensure default is initialized
195 List<WeakReference<AssemblyLoadContext>> alcList = null;
198 // To make this thread safe we need a quick snapshot while locked
199 alcList = new List<WeakReference<AssemblyLoadContext>>(s_allContexts.Values);
202 foreach (WeakReference<AssemblyLoadContext> weakAlc in alcList)
204 AssemblyLoadContext alc = null;
206 weakAlc.TryGetTarget(out alc);
216 // Helper to return AssemblyName corresponding to the path of an IL assembly
217 public static AssemblyName GetAssemblyName(string assemblyPath)
219 if (assemblyPath == null)
221 throw new ArgumentNullException(nameof(assemblyPath));
224 return AssemblyName.GetAssemblyName(assemblyPath);
227 // Custom AssemblyLoadContext implementations can override this
228 // method to perform custom processing and use one of the protected
229 // helpers above to load the assembly.
230 protected virtual Assembly Load(AssemblyName assemblyName)
235 [System.Security.DynamicSecurityMethod] // Methods containing StackCrawlMark local var has to be marked DynamicSecurityMethod
236 public Assembly LoadFromAssemblyName(AssemblyName assemblyName)
238 if (assemblyName == null)
239 throw new ArgumentNullException(nameof(assemblyName));
241 // Attempt to load the assembly, using the same ordering as static load, in the current load context.
242 StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
243 return Assembly.Load(assemblyName, ref stackMark, _nativeAssemblyLoadContext);
246 // These methods load assemblies into the current AssemblyLoadContext
247 // They may be used in the implementation of an AssemblyLoadContext derivation
248 public Assembly LoadFromAssemblyPath(string assemblyPath)
250 if (assemblyPath == null)
252 throw new ArgumentNullException(nameof(assemblyPath));
255 if (PathInternal.IsPartiallyQualified(assemblyPath))
257 throw new ArgumentException(SR.Argument_AbsolutePathRequired, nameof(assemblyPath));
264 return InternalLoadFromPath(assemblyPath, null);
268 public Assembly LoadFromNativeImagePath(string nativeImagePath, string assemblyPath)
270 if (nativeImagePath == null)
272 throw new ArgumentNullException(nameof(nativeImagePath));
275 if (PathInternal.IsPartiallyQualified(nativeImagePath))
277 throw new ArgumentException(SR.Argument_AbsolutePathRequired, nameof(nativeImagePath));
280 if (assemblyPath != null && PathInternal.IsPartiallyQualified(assemblyPath))
282 throw new ArgumentException(SR.Argument_AbsolutePathRequired, nameof(assemblyPath));
289 return InternalLoadFromPath(assemblyPath, nativeImagePath);
293 public Assembly LoadFromStream(Stream assembly)
295 return LoadFromStream(assembly, null);
298 public Assembly LoadFromStream(Stream assembly, Stream assemblySymbols)
300 if (assembly == null)
302 throw new ArgumentNullException(nameof(assembly));
305 int iAssemblyStreamLength = (int)assembly.Length;
307 if (iAssemblyStreamLength <= 0)
309 throw new BadImageFormatException(SR.BadImageFormat_BadILFormat);
312 // Allocate the byte[] to hold the assembly
313 byte[] arrAssembly = new byte[iAssemblyStreamLength];
315 // Copy the assembly to the byte array
316 assembly.Read(arrAssembly, 0, iAssemblyStreamLength);
318 // Get the symbol stream in byte[] if provided
319 byte[] arrSymbols = null;
320 if (assemblySymbols != null)
322 var iSymbolLength = (int)assemblySymbols.Length;
323 arrSymbols = new byte[iSymbolLength];
325 assemblySymbols.Read(arrSymbols, 0, iSymbolLength);
332 return InternalLoad(arrAssembly, arrSymbols);
336 // This method provides a way for overriders of LoadUnmanagedDll() to load an unmanaged DLL from a specific path in a
337 // platform-independent way. The DLL is loaded with default load flags.
338 protected IntPtr LoadUnmanagedDllFromPath(string unmanagedDllPath)
340 if (unmanagedDllPath == null)
342 throw new ArgumentNullException(nameof(unmanagedDllPath));
345 if (unmanagedDllPath.Length == 0)
347 throw new ArgumentException(SR.Argument_EmptyPath, nameof(unmanagedDllPath));
350 if (PathInternal.IsPartiallyQualified(unmanagedDllPath))
352 throw new ArgumentException(SR.Argument_AbsolutePathRequired, nameof(unmanagedDllPath));
355 return InternalLoadUnmanagedDllFromPath(unmanagedDllPath);
358 // Custom AssemblyLoadContext implementations can override this
359 // method to perform the load of unmanaged native dll
360 // This function needs to return the HMODULE of the dll it loads
361 protected virtual IntPtr LoadUnmanagedDll(string unmanagedDllName)
363 //defer to default coreclr policy of loading unmanaged dll
371 throw new InvalidOperationException(SR.AssemblyLoadContext_Unload_CannotUnloadIfNotCollectible);
374 GC.SuppressFinalize(this);
378 internal static void OnProcessExit()
382 foreach (var alcAlive in s_allContexts)
384 if (alcAlive.Value.TryGetTarget(out AssemblyLoadContext alc))
386 alc.RaiseUnloadEvent();
392 private void VerifyIsAlive()
394 if (_state != InternalState.Alive)
396 throw new InvalidOperationException(SR.AssemblyLoadContext_Verify_NotUnloading);
401 internal sealed class DefaultAssemblyLoadContext : AssemblyLoadContext
403 internal static readonly AssemblyLoadContext s_loadContext = new DefaultAssemblyLoadContext();
405 internal DefaultAssemblyLoadContext() : base(true, false, "Default")
410 internal sealed class IndividualAssemblyLoadContext : AssemblyLoadContext
412 internal IndividualAssemblyLoadContext(string name) : base(false, false, name)