How to use a namespace from an Assembly loaded with Assembly.LoadFrom - c#

Based on this question How to get Namespace of an Assembly?:
I can retrieve the namespace(s) from assembly with Assembly.GetTypes() but how can I provide this retrieved namespace file wide like using Namespace in first lines of C# file when loaded with "Add Reference" in project settings?
What is with other C# files? How can I provide this loaded namespace to them?
My approach:
// using NameSpaceFromLib; ??? how to do this with Assembly.LoadFrom
namespace Test
{
public class ClassA : DisposableObject
{
static ClassA()
{
AppDomain currentDomain = AppDomain.CurrentDomain;
// switch between 32 and 64 bit version
currentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomainAssemblyResolve);
}
private const string LibraryName = "LibToLoad";
private static Assembly CurrentDomainAssemblyResolve(object sender, ResolveEventArgs args)
{
if (args.Name.StartsWith(LibraryName, StringComparison.InvariantCultureIgnoreCase))
{
return LoadBitDepthDependentAssembly(LibraryName + ".dll");
}
return null;
}
private static Assembly LoadBitDepthDependentAssembly(string assemblyname)
{
var currentDirectory = Environment.CurrentDirectory;
try
{
var platformDirectory = Environment.Is64BitProcess ? "x64" : "x86";
var newPath = Helper.GetApplicationPath(platformDirectory);
Environment.CurrentDirectory = newPath;
var p = Path.Combine(newPath, assemblyname);
var assembly = Assembly.LoadFrom(p);
return assembly;
}
finally
{
Environment.CurrentDirectory = currentDirectory;
}
}
}
}
Thank you!
PS: I need this to distinguish during runtime if x86 or x64 variant of C++/CLI dll to load

Related

Custom AssemblyLoadContext failing to load Microsoft.AspNetCore.Components

Edit: I have uploaded the source code for the issue to GitHub if you would like to download: https://github.com/bryanenroute/assemblyloadcontext-issue
I have a .NET Core 3.0 console application that references a .NET Standard 2.0 class library with a single interface (IModule). I also have a ASP.NET Core 3.0 application that references the same .NET Standard 2.0 class library and implements the interface (Module : IModule).
I am trying to load the ASP.NET Core assembly from the .NET Core console application using a custom AssemblyLoadContext and a common class library interface (IModule)... a simple plugin system.
Unfortunately, the ASP.NET Core module/plugin fails in the ALC override function for Load(AssemblyName) with the following exception:
Could not load file or assembly 'Microsoft.AspNetCore.Components, Version=3.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'. The system cannot find the file specified.
When I try with a different project type (e.g. .NET Core Console Application or .NET Standard 2.0 Class Library), the module/plugin loads as intended.
Here's the Console app code:
using NetStandardCommon;
using System;
using System.IO;
namespace NetCoreConsoleApp
{
class Program
{
static void Main(string[] args)
{
LoadNetCoreModule();
LoadAspNetCoreModule();
}
static void LoadNetCoreModule()
{
//Works!
FileInfo asm = new FileInfo(#"..\..\..\..\NetCoreModule\bin\debug\netcoreapp3.0\NetCoreModule.dll");
var moduleDirectory = asm.DirectoryName;
ModuleAssemblyLoadContext context = new ModuleAssemblyLoadContext(asm.Name, moduleDirectory, typeof(IModule));
context.Scan();
foreach (var module in context.GetImplementations<IModule>())
{
module.Start();
}
}
static void LoadAspNetCoreModule()
{
//Fails!
FileInfo asm = new FileInfo(#"..\..\..\..\AspNetCoreApp\bin\debug\netcoreapp3.0\AspNetCoreApp.dll");
var moduleDirectory = asm.DirectoryName;
ModuleAssemblyLoadContext context = new ModuleAssemblyLoadContext(asm.Name, moduleDirectory, typeof(IModule));
context.Scan();
foreach (var module in context.GetImplementations<IModule>())
{
module.Start();
}
}
}
}
Here's the ModuleAssemblyLoadContext code:
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Loader;
using System.Linq;
namespace NetCoreConsoleApp
{
public class ModuleAssemblyLoadContext : AssemblyLoadContext
{
private List<Assembly> _loaded;
private Dictionary<string, Assembly> _shared;
private string _path;
private AssemblyDependencyResolver _resolver;
public ModuleAssemblyLoadContext(string name, string path, params Type[] sharedTypes) : base(name)
{
_path = path;
_resolver = new AssemblyDependencyResolver(_path);
_loaded = new List<Assembly>();
_shared = new Dictionary<string, Assembly>();
if (sharedTypes != null)
{
foreach (Type sharedType in sharedTypes)
{
_shared[Path.GetFileName(sharedType.Assembly.Location)] = sharedType.Assembly;
}
}
}
public void Scan()
{
foreach (string dll in Directory.EnumerateFiles(_path, "*.dll"))
{
var file = Path.GetFileName(dll);
if (_shared.ContainsKey(file))
{
continue;
}
var asm = this.LoadFromAssemblyPath(dll);
_loaded.Add(asm);
}
}
public IEnumerable<T> GetImplementations<T>()
{
return _loaded
.SelectMany(a => a.GetTypes())
.Where(t => typeof(T).IsAssignableFrom(t))
.Select(t => Activator.CreateInstance(t))
.Cast<T>();
}
protected override Assembly Load(AssemblyName assemblyName)
{
string filename = $"{assemblyName.Name}.dll";
if (_shared.ContainsKey(filename))
{
return _shared[filename];
}
string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
if (assemblyPath != null)
{
return LoadFromAssemblyPath(assemblyPath);
}
return null;
}
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
{
string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
if (libraryPath != null)
{
return LoadUnmanagedDllFromPath(libraryPath);
}
return IntPtr.Zero;
}
}
}
I tried modifying the ALC Load function to load the assemblies directly from the shared folder (C:\Program Files (x86)\dotnet\shared\Microsoft.AspNetCore.App\3.0.0) which lets the execution continue a bit farther, but it ultimately fails with the following exception:
An attempt was made to load a program with an incorrect format. (0x8007000B)
Here's the revised Load function:
protected override Assembly Load(AssemblyName assemblyName)
{
string filename = $"{assemblyName.Name}.dll";
if (_shared.ContainsKey(filename))
{
return _shared[filename];
}
try
{
if (File.Exists(#"C:\Program Files (x86)\dotnet\shared\Microsoft.AspNetCore.App\3.0.0\" + filename))
{
return Assembly.LoadFrom(#"C:\Program Files (x86)\dotnet\shared\Microsoft.AspNetCore.App\3.0.0\" + filename);
}
}
catch (Exception ex)
{
//Message displayed is 'An attempt was made to load a program with an incorrect format. (0x8007000B)'
Console.WriteLine(ex.Message);
}
return Assembly.Load(assemblyName);
}
I'm excited about the possibilities of loading/unloading assemblies for a .net Core plugin system, but I'm struggling to get over this hurdle. What am I missing?
I had this issue about a month ago with loading a Assembly to my SQL server. Are you using virtual drives to store your Assembly? I found out that our share drive actual drive path was a E drive and not a P drive which is what is mapped on my computer. I was virtually connected to it, so I had to give the real Drive path which started with E instead of P. Also, your program might be mapping it to the wrong drive as well. I would check that, and if that doesn't help I have about 3-4 more things to try as far as this particular issue in concerned.
I believe the library might also need to be built with the target framework set to .NetCore 3.0 (netcoreapp3.0).

Unable to load NuGet dll with platform specific dlls in netcoreapp

I am unable to load System.Data.Client dll from it's nuget package using the ICompilationAssemblyResolver I have available in a netcoreapp. The bulk of the Assembly resolving is borrowed from here, and works great for the most part. It looks like so:
internal sealed class AssemblyResolver : IDisposable
{
private readonly ICompilationAssemblyResolver assemblyResolver;
private readonly DependencyContext dependencyContext;
private readonly AssemblyLoadContext loadContext;
public AssemblyResolver(string path)
{
this.Assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(path);
this.dependencyContext = DependencyContext.Load(this.Assembly);
this.assemblyResolver = new CompositeCompilationAssemblyResolver(new ICompilationAssemblyResolver[]
{
new AppBaseCompilationAssemblyResolver(Path.GetDirectoryName(path)),
new ReferenceAssemblyPathResolver(),
new PackageCompilationAssemblyResolver()
});
this.loadContext = AssemblyLoadContext.GetLoadContext(this.Assembly);
this.loadContext.Resolving += OnResolving;
}
public Assembly Assembly { get; }
public void Dispose()
{
this.loadContext.Resolving -= this.OnResolving;
}
private Assembly OnResolving(AssemblyLoadContext context, AssemblyName name)
{
bool NamesMatch(RuntimeLibrary runtime)
{
return string.Equals(runtime.Name, name.Name, StringComparison.OrdinalIgnoreCase);
}
RuntimeLibrary library =
this.dependencyContext.RuntimeLibraries.FirstOrDefault(NamesMatch);
if (library != null)
{
var wrapper = new CompilationLibrary(
library.Type,
library.Name,
library.Version,
library.Hash,
library.RuntimeAssemblyGroups.SelectMany(g => g.AssetPaths),
library.Dependencies,
library.Serviceable);
var assemblies = new List<string>();
this.assemblyResolver.TryResolveAssemblyPaths(wrapper, assemblies);
if (assemblies.Count > 0)
{
return this.loadContext.LoadFromAssemblyPath(assemblies[0]);
}
}
return null;
}
}
However I am unable to load a dll that references System.Data.Client # 4.3.1 as at runtime I get the error message:
Exception has occurred: CLR/System.IO.FileNotFoundException
An unhandled exception of type 'System.IO.FileNotFoundException'
occurred in System.Private.CoreLib.ni.dll: 'Could not load file or
assembly 'System.Data.SqlClient, Version=4.1.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a'. The system cannot find the file specified.'
I am not sure why it is trying to load 4.1.0 when I have specified 4.3.0 but I think that is a bit of a red herring. I suspect that the PackageCompilationAssemblyResolver only looks under the lib folder, and the package in question does not have one for netstandard. It does however have one for specific runtimes:
Armed with this information I have created an incredibly crude AssemblyLoader that looks under the runtimes folder for a nuget package and I am able to load the dll and run my program as I expect.
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.DotNet.PlatformAbstractions;
using Microsoft.Extensions.DependencyModel;
using Microsoft.Extensions.DependencyModel.Resolution;
namespace Loader
{
public class CrudeCompilationAssemblyResolver : ICompilationAssemblyResolver
{
private readonly string[] _nugetPackageDirectories;
public CrudeCompilationAssemblyResolver()
{
var basePath = Environment.GetEnvironmentVariable("HOME");
var defaultPath = Path.Combine(basePath, ".nuget", "packages");
_nugetPackageDirectories = new [] { defaultPath };
}
public bool TryResolveAssemblyPaths(CompilationLibrary library, List<string> assemblies)
{
if (_nugetPackageDirectories == null || _nugetPackageDirectories.Length == 0 || !string.Equals(library.Type, "package", StringComparison.OrdinalIgnoreCase))
{
return false;
}
foreach (var directory in _nugetPackageDirectories)
{
string packagePath;
var fullPath = Path.Combine(directory, library.Name, library.Version, "runtimes", "unix", "lib", "netstandard1.3", $"{library.Name}.dll");
if (File.Exists(fullPath))
{
assemblies.AddRange(new[] { fullPath });
return true;
}
}
return false;
}
}
}
My question is: Is there a better/officially sanctioned way of loading this troublesome assembly from a nuget package? Or do I need to make my crude loader a lot less crude?
Full repo is here: CustomAssemblyResolver

Creating an AppDomain and calling a method from an assembly in a subfolder

I have an example application that has a number of endpoints (c# classes) which use an interface which defines some methods. These endpoints are in their own class libraries.
In an assembly called "EndPoints"
namespace EndPoints
{
public interface IEndPoint
{
void Initialize(XmlDocument message);
bool Validate();
void Execute();
}
}
In an assembly called "EndPoints.EndPoint1"
namespace EndPoints
{
public class EndPoint1 : IEndPoint
{
private XmlDocument _message;
public void Initialize(XmlDocument message)
{
_message = message;
Console.WriteLine("Initialize EndPoint1");
}
public bool Validate()
{
Console.WriteLine("Validate EndPoint1");
return true;
}
public void Execute()
{
Console.WriteLine("Execute EndPoint1");
}
}
}
The application will "choose" an endpoint to use and then find the appropriate class, create an instance of it and then call the methods in turn.
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
// Generate "random" endpoint name
string endPointName = GetEndPointName();
// Get the class name from the namespaced class
string className = GetClassName(endPointName);
// Dummy xmldocument that used to pass into the end point
XmlDocument dummyXmlDocument = new XmlDocument();
// Load appropriate endpoint assembly because the application has no reference to it so the assembly would not have been loaded yet
LoadEndPointAssembly(endPointName);
// search currently loaded assemblies for that class
var classTypes = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(s => s.GetTypes())
.Where(p => p.FullName == endPointName)
.ToList();
// cycle through any found types (should be 1 only)
for (int i = 0; i < classTypes.Count; i++)
{
var classType = classTypes[i];
IEndPoint classInstance = Activator.CreateInstance(classType) as IEndPoint;
classInstance.Initialize(dummyXmlDocument);
if (classInstance.Validate())
{
classInstance.Execute();
}
}
}
private static void LoadEndPointAssembly(string endPointName)
{
using (StreamReader reader = new StreamReader(endPointName + ".dll", System.Text.Encoding.GetEncoding(1252), false))
{
byte[] b = new byte[reader.BaseStream.Length];
reader.BaseStream.Read(b, 0, System.Convert.ToInt32(reader.BaseStream.Length));
reader.Close();
AppDomain.CurrentDomain.Load(b);
}
}
private static string GetEndPointName()
{
// Code to create "random" endpoint class name
Random rand = new Random();
int randomEndPoint = rand.Next(1, 4);
return string.Format("EndPoints.EndPoint{0}", randomEndPoint);
}
private static string GetClassName(string namespacedClassName)
{
string className = null;
string[] components = namespacedClassName.Split('.');
if (components.Length > 0)
{
className = components[components.Length - 1];
}
return className;
}
}
}
I want to change the application to achieve the following;
- each endpoint assembly (and any config files and/or other assemblies that it uses) are contained in a subfolder below the application folder. the subfolder name would be the name of the endpoint class e.g. "EndPoint1"
- each endpoint runs in its own appdomain.
However, so far I've been unable to achieve this. I keep getting an exception stating its failed to load the appropriate assembly even though when I create the appdomain I specify the subfolder to be used by setting the ApplicationBase and PrivateBinPath properties of the AppDomainSetup; e.g.
AppDomain appDomain = null;
AppDomain root = AppDomain.CurrentDomain;
AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationBase = root.SetupInformation.ApplicationBase + className + #"\";
setup.PrivateBinPath = root.SetupInformation.ApplicationBase + className + #"\";
appDomain = AppDomain.CreateDomain(className, null, setup);
I've then been trying to use the Load method on the newly created appDomain to load the assembly. That's when I get the error.
Please does anyone have any thoughts about how I can load the appropriate assembly and call the methods defined in the interface? Many thanks.
I would do it in the following way. Firstly you need a class derived from MarshalByRef. It will be responsible for loading EndPoints and executing them in separate application domains. Here, I assume that it is defined in ConsoleApplication1 but it can be moved somewhere else:
public class EndPointLoader : MarshalByRefObject
{
public void Load(string path, string endPointName)
{
Assembly.LoadFrom(path);
var classTypes = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(s => s.GetTypes())
.Where(p => p.FullName == endPointName)
.ToList();
for (int i = 0; i < classTypes.Count; i++)
{
....
}
}
}
Here is a code that uses this class. You can put in in your LoadEndPointAssembly method.
var appDomain = AppDomain.CreateDomain(endPointName);
var loader = (EndPointLoader)appDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(EndPointLoader).FullName);
loader.Load(assemblyPath, endPointName);

Plugin Application and WPF issues?

I am working on a project where allowing 3rd-party plugins is required. I have worked with plugins before and I never had a problem.
I'm sure my problem is because WPF doesn't like me using Assembly.LoadFile(file) & Activator.CreateInstance(t)!
The error I encounter is:
The component 'Servus.Forms.MainWindow' does not have a resource identified by the URI '/Servus;component/forms/mainwindow.xaml'.
which shows in my MainForm constructor at:
InitializeComponent();
If I load the plugins after loading the MainForm it loads without issues, however when opening any other forms(there are many in my application) I experience the same issue as about but with the relevant error for that particular form.
I have also tried to load the plugins in there own AppDomain like this:
PluginDomain temp = new PluginDomain();
PluginBase tempPlug = temp.GetPlugin(file);
With the following classes:
public class PluginDomain
{
public AppDomain CurrentDomain { get; set; }
public ServusAssemblyLoader CurrentAssemblyLoader { get; set; }
private readonly Random _rand = new Random();
public PluginDomain()
{
}
public PluginBase GetPlugin(string assemblyName)
{
try
{
string appBase = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
var ads = new AppDomainSetup { ApplicationBase = appBase, PrivateBinPath = appBase, ShadowCopyFiles = "true" };
CurrentDomain = AppDomain.CreateDomain("ServusDomain_Plugin_" + _rand.Next(0, 100000), null, ads);
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
CurrentAssemblyLoader = (ServusAssemblyLoader)
CurrentDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(ServusAssemblyLoader).FullName);
AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomain_AssemblyResolve;
return CurrentAssemblyLoader.Load(assemblyName);
}
catch (Exception e)
{
CConsole.WriteLine("Error: " + e.Message);
}
finally
{
CurrentAssemblyLoader = null;
AppDomain.Unload(CurrentDomain);
}
return null;
}
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
string[] parts = args.Name.Split(',');
string file = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\" + parts[0].Trim() + ".dll";
return Assembly.LoadFrom(file);
}
}
public class ServusAssemblyLoader : MarshalByRefObject, IAssemblyLoader
{
public PluginBase Load(string file)
{
Assembly asm = Assembly.LoadFrom(file);
foreach (Type t in asm.GetTypes())
{
if (t.IsSubclassOf(typeof(PluginBase)))
{
return (PluginBase)Activator.CreateInstance(t);
}
}
return null;
}
}
public interface IAssemblyLoader
{
PluginBase Load(string file);
}
This returns an TransparentProxy object like this:
{System.Runtime.Remoting.Proxies.__TransparentProxy}
However I am unsure how to use this as I was expecting it to return a PluginBase Object.
I have read that many people have also have this issue, they have answers that say to use a new AppDomain, but as you can see this doesn't help me right now.
I hope I have provided you enough information, can anyone help?
It turns out I had a few things wrong in my PluginDomain Class.
Fix #1:
Replace:
return (PluginBase)Activator.CreateInstance(t);
With:
(PluginBase)asm.CreateInstance(t.ToString());
Fix #2:
Remove:
AppDomain.Unload(CurrentDomain);
Fix #3: (Purely for debugging)
Replace:
return CurrentAssemblyLoader.Load(assemblyName);
With:
PluginBase obj = CurrentAssemblyLoader.Load(assemblyName);
return obj;
EDIT:
It should be noted that the new AppDomain wont be able to access objects in the old one; so my problem is only half fixed.

How do I use AppDomain.CreateDomain with AssemblyResolve?

I want to load my assemblies from WCF by using memory. Everything is working good WHEN:
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
Assembly[] assBefore = AppDomain.CurrentDomain.GetAssemblies();
foreach (byte[] binary in deCompressBinaries)
loadedAssembly = AppDomain.CurrentDomain.Load(binary);
But I want to use AppDomain.CreateDomain, not the current domain:
protected void LoadApplication()
{
this.ApplicationHost = AppDomain.CreateDomain("TestService", null, new AppDomainSetup
{
ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase
});
ApplicationHost.AssemblyResolve += new ResolveEventHandler(OnAssemblyResolve);
foreach (AssemblyName asmbly in System.Reflection.Assembly.GetExecutingAssembly().GetReferencedAssemblies())
{
ApplicationHost.Load(asmbly);
}
List<byte[]> deCompressBinaries = new List<byte[]>();
foreach (var item in AppPackage.Item.AssemblyPackage)
deCompressBinaries.Add(item.Buffer);
var decompressvalues = DeCompress(deCompressBinaries);
deCompressBinaries.Clear();
deCompressBinaries = decompressvalues.ToList();
foreach (byte[] binary in deCompressBinaries)
ApplicationHost.Load(binary);
Assembly[] assAfter = AppDomain.CurrentDomain.GetAssemblies();
}
Assembly OnAssemblyResolve(object sender, ResolveEventArgs args)
{
return Assembly.Load(args.Name);
}
I have two class libraries, ClassLibrary1 and ClassLibrary2 using the below:
namespace ClassLibrary2
{
public class Class1 : MarshalByRefObject
{
public Class1()
{
}
public int GetSum(int a , int b)
{
try
{
ClassLibrary1.Class1 ctx = new ClassLibrary1.Class1();
return ctx.Sum(a, b);
}
catch
{
return -1;
}
}
public int GetMultiply(int a, int b)
{
return a * b;
}
}
}
Classlibrary2 depends on ClassLibrary1. So I am using assemblyresolver. But I get an error on ApplicationHost.Load(binary);:
Error: Could not load file or assembly 'ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
Also it is NOT FIRING ASSEMBLYRESOLVER. My cursor is not going to the Assemblyresolver method. How do I use AppDomain.CreateDomain with the resolve method?
I personally don't like loading assemblies from a byte array. I think it is better to save your assemblies to a temporary folder and then load them from that folder. Take a look at this article: Application Domains is hard….

Categories