So I have a program that I want to load an assembly and in the Load method, some assemblies do not seem to be loaded and my methods are not returned. I want to load the assembly, retrieve all test methods, and unload it because I want to then reload it a second time later on in the program.
Also, unload doesn't seem to free the assembly. I try building the referenced DLL after I unload and I get that "...being used by another process. The file is locked by "MyProject""
public class TestAssemblyLoadContext : AssemblyLoadContext
{
private AssemblyDependencyResolver _resolver;
public TestAssemblyLoadContext(string pluginPath) : base(isCollectible: true)
{
_resolver = new AssemblyDependencyResolver(pluginPath);
}
protected override Assembly Load(AssemblyName name)
{
string assemblyPath = _resolver.ResolveAssemblyToPath(name);
if (assemblyPath != null)
{
Console.WriteLine($"Loading assembly {assemblyPath} into the TestAssemblyLoadContext");
return LoadFromAssemblyPath(assemblyPath);
}
return null;
}
}
And I call it here and unload it. I want to call this method twice to compare methods in different git branches and figure out some info in the methods.
private static IOrderedEnumerable<MethodInfo> GetMethods(string assemblyPath)
{
var alc = new TestAssemblyLoadContext(assemblyPath);
Assembly assembly = alc.LoadFromAssemblyPath(assemblyPath);
var methods = assembly.GetTypes()
.SelectMany(t => t.GetMethods())
.Where(m => m.GetCustomAttributes(typeof(TestAttribute), false).Length > 0 || m.GetCustomAttributes(typeof(TestCaseAttribute), false).Length > 0)
.OrderBy(tm => tm.DeclaringType.FullName[..tm.DeclaringType.FullName.LastIndexOf(".")]);
// Assembly is still locked and can't build as an example
alc.Unload();
return methods;
}
Related
I have type A implementing interface IA from assembly aA.dll. It creates K instances of type B from aB.dll. I want to get A from aA.dll to use type B from bB.dll which is same in its name and version to aB.dll yet with minor code differences. So I try:
public class CollectibleAssemblyLoadContext
: AssemblyLoadContext
{
public CollectibleAssemblyLoadContext() : base(isCollectible: true)
{ }
protected override Assembly Load(AssemblyName assemblyName)
{
string path = "";
if (AssemblyNameToPath.TryGetValue(assemblyName.FullName, out path))
{
return Assembly.LoadFile(path);
}
return null;
}
}
Yet when I try to create a A as IA using:
public static object GetRaw<T>() where T : class
{
AssemblyLoadContext context = new CollectibleAssemblyLoadContext();
var type = typeof(T);
Assembly assembly = context.LoadFromAssemblyName(type.Assembly.GetName());
Type programType = assembly.GetType(type.FullName);
object result = Activator.CreateInstance(programType);
return result;
}
Generally X is what I get while V is what I want from this picture:
Type B is used from preloaded in general context aB.dll. How to make it load if from bB.dll? How to make sure AssemblyLoadContext would use Load to get all the assemblies from scratch, not only one?
A small demo project we tried to test it with, yet it fails to load more than one assembly deep no matter what...
To my knowledge different versions of DLLs / dependencies are not allowed in the same AppDomain. So a type can only resolve to one DLL in the same ApPDomain. Spinning up a new AppDomain might be what you can try.
Using C# source generators, is there a way to get more information about types in referenced assemblies. To be more precise: Is there a way to find out which type implements an interface that resides in a referenced project?
For example:
Assembly 1
-BaseClass with interface
Assembly 2 (uses the source generator and refers to assembly 1)
-Implements BaseClass of Assembly1
Thanks in advance.
Yes, there is a way. I've done that for one of my source generator projects. Because I don't assume which adjustments you'd need, I'll just drop the essential code here and then highlight some of the things which might be relevant to you:
internal interface IImplementationTypeSetCache
{
IImmutableSet<INamedTypeSymbol> All { get; }
IImmutableSet<INamedTypeSymbol> ForAssembly(IAssemblySymbol assembly);
}
internal class ImplementationTypeSetCache : IImplementationTypeSetCache
{
private readonly GeneratorExecutionContext _context;
private readonly WellKnownTypes _wellKnownTypes;
private readonly Lazy<IImmutableSet<INamedTypeSymbol>> _all;
private IImmutableDictionary<IAssemblySymbol, IImmutableSet<INamedTypeSymbol>> _assemblyCache =
ImmutableDictionary<IAssemblySymbol, IImmutableSet<INamedTypeSymbol>>.Empty;
private readonly string _currentAssemblyName;
internal ImplementationTypeSetCache(
GeneratorExecutionContext context,
WellKnownTypes wellKnownTypes)
{
_context = context;
_wellKnownTypes = wellKnownTypes;
_currentAssemblyName = context.Compilation.AssemblyName ?? "";
_all = new Lazy<IImmutableSet<INamedTypeSymbol>>(
() => context
.Compilation
.SourceModule
.ReferencedAssemblySymbols
.Prepend(_context.Compilation.Assembly)
.SelectMany(ForAssembly)
.ToImmutableHashSet<INamedTypeSymbol>(SymbolEqualityComparer.Default));
}
public IImmutableSet<INamedTypeSymbol> All => _all.Value;
public IImmutableSet<INamedTypeSymbol> ForAssembly(IAssemblySymbol assembly)
{
if (_assemblyCache.TryGetValue(assembly, out var set)) return set;
var freshSet = GetImplementationsFrom(assembly);
_assemblyCache = _assemblyCache.Add(assembly, freshSet);
return freshSet;
}
private IImmutableSet<INamedTypeSymbol> GetImplementationsFrom(IAssemblySymbol assemblySymbol)
{
var internalsAreVisible =
SymbolEqualityComparer.Default.Equals(_context.Compilation.Assembly, assemblySymbol)
||assemblySymbol
.GetAttributes()
.Any(ad =>
SymbolEqualityComparer.Default.Equals(ad.AttributeClass, _wellKnownTypes.InternalsVisibleToAttribute)
&& ad.ConstructorArguments.Length == 1
&& ad.ConstructorArguments[0].Value is string assemblyName
&& Equals(assemblyName, _currentAssemblyName));
return GetAllNamespaces(assemblySymbol.GlobalNamespace)
.SelectMany(ns => ns.GetTypeMembers())
.SelectMany(t => t.AllNestedTypesAndSelf())
.Where(nts => nts is
{
IsAbstract: false,
IsStatic: false,
IsImplicitClass: false,
IsScriptClass: false,
TypeKind: TypeKind.Class or TypeKind.Struct or TypeKind.Structure,
DeclaredAccessibility: Accessibility.Public or Accessibility.Internal or Accessibility.ProtectedOrInternal
})
.Where(nts =>
!nts.Name.StartsWith("<")
&& (nts.IsAccessiblePublicly()
|| internalsAreVisible && nts.IsAccessibleInternally()))
.ToImmutableHashSet<INamedTypeSymbol>(SymbolEqualityComparer.Default);
}
private static IEnumerable<INamespaceSymbol> GetAllNamespaces(INamespaceSymbol root)
{
yield return root;
foreach(var child in root.GetNamespaceMembers())
foreach(var next in GetAllNamespaces(child))
yield return next;
}
}
_wellKnownTypes.InternalsVisibleToAttribute is just the INamedTypeSymbol instance representing the .Net InternalsVisibleToAttribute. Further more an extension method is in use here:
internal static IEnumerable<INamedTypeSymbol> AllNestedTypesAndSelf(this INamedTypeSymbol type)
{
yield return type;
foreach (var typeMember in type.GetTypeMembers())
{
foreach (var nestedType in typeMember.AllNestedTypesAndSelf())
{
yield return nestedType;
}
}
}
I use this code to iterate over all implementation types (at least what my project considers as such) from the current assembly and all (!) referenced assemblies (my assemblies, third-party assemblies and .Net assemblies). So you might consider making some adjustments and therefore I want to highlight some points.
First, you'll get referenced assemblies by:
context
.Compilation
.SourceModule
.ReferencedAssemblySymbols
Like mentioned before these are really all referenced assemblies. So, you might want to filter them in order to prevent redundancies.
Next, consider accessibility. Does your target assembly disclose the internals to your source generator project via InternalsVisibleToAttribute?
Last, you might need to adjust the filter logic of the types, because it is specific to my project. For example, you might want to include abstract classes as well.
The rest of the code is basically the logic for iterating over assemblies down to namespaces down to types down to nested types.
Finally, you'll just need to check which of the iterated types implement the interface.
That should be it. Have fun.
Now that I consider my work done (but feel free to ask follow-up questions), I hope that I deserve a mini-advertisement:
This snippets that I pasted here are part of my dependency injection container project MrMeeseeks.DIE (documentation). Feedback is very welcome.
I need to create new AppDomain and pass executing assembly to it, without any access to the assembly file itself.
I have tried to use a binary serializer to transfer the loaded assembly, but it can't be done with the assembly existing only in memory.
The problem is that new AppDomain throws assembly load exception because there is no such a file in current directory.
Update:
Even if I found a way to get the actual assembly byte array, saved it to disk and forced it to be loaded into new AppDomain - there is a casting error - I can't cast it from proxy type to actual class or interface.
Example project:
https://drive.google.com/open?id=16z9nr8W5D8HjkBABgOCe5MIZKJSpFOul
Update 2:
The example below is working when executed from assembly on the hard drive - there is no need to search the assembly file, and assembly FullName is enough in CreateInstanceFromAndUnwrap method. All errors occur when the assembly containing this code is loaded from byte array.
Usage:
public sealed class LoadDomain: IDisposable
{
private AppDomain _domain;
private IFacade _value;
public IFacade Value
{
get
{
return _value;
}
}
public void Dispose()
{
if (_domain != null)
{
AppDomain.Unload(_domain);
_domain = null;
}
}
public void Load(byte[] assemblyBytes,string assemblyPath,string assemmblyDir)
{
var domainSetup = new AppDomainSetup()
{
ShadowCopyDirectories = "true",
ShadowCopyFiles = "true",
ApplicationName = Assembly.GetExecutingAssembly().ManifestModule.ScopeName,
DynamicBase = assemmblyDir,
LoaderOptimization = LoaderOptimization.MultiDomainHost,
};
_domain = AppDomain.CreateDomain("Isolated:" + Guid.NewGuid(), null, domainSetup);
// _domain.Load(assemblyBytes); not working
// _domain.AssemblyResolve+=.. not working
Type type = typeof(Facade);
_value = (IFacade)_domain.CreateInstanceFromAndUnwrap(assemblyPath, type.FullName);
//assemlby path working, but casting error : InvalidCastException
//assembly FullName not working: FileNotFoundException
}
}
public class Facade : MarshalByRefObject, IFacade
{
public void DoSomething()
{
MessageBox.Show("new domain");
}
}
am try implement plugin system for my application. The idea is that in a folder stores Users assembly. When my application starts i want get object list from users assemblies.
public void InitPlugins()
{
var userAssemblies = Directory.GetFiles(PATH,"*.dll");
foreach(var file in userAssemblies)
{
Assembly customAssembly = Assembly.Load(file);
//How can I find all object implements IPlugin in this assembly?
}
}
That should work.
foreach (Type type in customAssembly )
{
if (type.GetInterface("IPlugin") == typeof(IPlugin))
{
IPlugin plugin = Activator.CreateInstance(type) as IPlugin;
}
}
I am stuck for 3rd day with this problem and I have no freaking idea why this doesn't work. I just want to load external .dll to read some info using reflection and delete the file after all. The problem is that read files are locked. The most strange thing is that only two files are locked while i read 5 of them succesfully. I've tried ShadowCopy with no result. I got no clue right now.
This is my appdomain class:
public class AppDomainExpander
{
private Type[] _types;
public Type[] Types
{
get { return _types; }
set { _types = value; }
}
public void Create(string domainName, string path)
{
AppDomainSetup aps = new AppDomainSetup();
aps.ShadowCopyFiles = "true";
AppDomain dmn = AppDomain.CreateDomain(domainName);
string typename = typeof(DomainCommunicator).FullName;
string assemblyName = typeof(DomainCommunicator).Assembly.FullName;
var inner = (DomainCommunicator)dmn.CreateInstanceAndUnwrap(assemblyName, typename);
inner.Create();
Assembly assembly = Assembly.LoadFrom(path);
Types = assembly.GetTypes();
AppDomain.Unload(dmn); //it's strange that the code even work because i try to unload domain before i get Types[]
}
public class DomainCommunicator : MarshalByRefObject
{
public void Create()
{
AppDomain.CurrentDomain.DomainUnload += new EventHandler(OnDomainUnload);
}
void OnDomainUnload(object sender, EventArgs e)
{
AppDomain.CurrentDomain.DomainUnload -= new EventHandler(OnDomainUnload);
}
}
}
And this is how i try to use it:
var expander = new AppDomainExpander();
expander.Create("MyDomain", file.Path);
foreach (var type in expander.Types)
The types are loaded to your main AppDomain which does not have the ShadowCopy feature enabled. This is why the files are locked.
You will need to load the assembly in the DomainCommunicator.Create method instead. Note though that you cannot keep the Types property. This will lead to types leaking from the child AppDomain to the main one and the file locking problems that you currently face.
I just noticed that only interfaces are locked. What is more when I load two classes and then two interfaces it's OK. But when I add interface and implementing class at same time it locks