How do I use AppDomain.CreateDomain with AssemblyResolve? - c#

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….

Related

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

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

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

How to find uses of a method in a second assembly?

I am trying to map out the dependency matrix for a collection of assemblies including what dependency methods are used where. The basic DLL dependency matrix was easy but I am finding it difficult to get the method mapping. The tool I have been using is jbevain MethodBaseRocks.cs.
The dependencies of the assembly I want to parse are being loaded according to AppDomain.CurrentDomain.GetAssemblies() but I am getting FileNotFoundException and ReflectionTypeLoadExceptions.
Is there a correct way to load referenced assemblies?
I have tried LoadFile, LoadFrom and ReflectionOnlyLoadFrom all with the same result.
How do I get the types for methods that use a references Type?
I can step around the ReflectionTypeLoadExceptions error with the answer from here but these methods are the exact ones I want to map
TestLibraryA.dll
namespace TestLibraryA
{
public class TestClassA
{
public int DoStuff(int a, int b)
{
return a + b;
}
}
}
TestLibaryB.dll
using TestLibraryA;
namespace TestLibraryB
{
public class TestClassB
{
public int DoStuffAgain()
{
TestClassA obj = new TestClassA();
int ans = obj.DoStuff(3, 5);
return ans;
}
public TestClassA DoOtherStuff()
{
TestClassA result = new TestClassA();
return result;
}
}
}
Parser Code Application
public List<string> GetMethods()
{
List<string> result = new List<string> { };
Assembly dependencyAssembly = Assembly.LoadFile("TestLibraryA.dll");
Assembly targetAssembly = Assembly.LoadFile("TestLibaryB.dll");
Type[] types = targetAssembly.GetTypes();
// ReflectionTypeLoadExceptions thrown if a dependency type is used
// NB Not demo'ed in this example
foreach(var type in types)
{
foreach(var method in type.GetMethods())
{
// With the above DoStuffAgain() method is returned but DoOtherStuff() is not
var instructions = MethodBodyReader.GetInstructions(method);
// FileNotFoundException thrown saying TestLibraryA.dll not loaded
// the line throwing the error is
// MethodBodyReader(method)
// this.body = method.GetMethodBody();
foreach (var instruction in instructions)
{
MethodInfo methodInfo = instruction.Operand as MethodInfo;
if (methodInfo != null)
{
result.Add(methodInfo.DeclaringType.FullName + "." + methodInfo.Name);
}
}
}
}
return result;
}
To avoid the exception you need to add the code below before you load TestLibaryB
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
if (args.Name == "TestLibaryA...")
{
return Assembly.LoadFrom("TestLibaryA's Path");
}
return null;
}

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);

Error deserializing object having a field of type declared in the other assembly loaded on AssemblyResolve

I have an application which embedes (via BuildAction: Embedded Resource) referenced assembly (called ClassLibrary1) inside itself and loads it on AppDomain.CurrentDomain.AssemblyResolve event.
Main assembly defines a class Class1:
public class Class1
{
public Class2 MyField { get; set; }
}
It has a property of type Class2 defined in ClassLibrary1.
Definition of Class2:
public class Class2
{
public int A { get; set; }
}
In the main method I`m creating a new XmlSerializer(typeof(Class1)):
static void Main()
{
SubscribeAssemblyResolver();
MainMethod();
}
private static void MainMethod()
{
XmlSerializer xs2 = new XmlSerializer(typeof(Class1));
Class1 cl = new Class1();
}
While executing a programm I get the following error:
Unable to generate a temporary class (result=1).
error CS0012: The type 'ClassLibrary1.Class2' is defined in an assembly that is not referenced. You must add a reference to assembly 'ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c06f123f2868e8c8'.
error CS0266: Cannot implicitly convert type 'object' to 'ClassLibrary1.Class2'. An explicit conversion exists (are you missing a cast?)
Any ideas?
The rest of the code:
private static void SubscribeAssemblyResolver()
{
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}
static Dictionary<String, Assembly> _assemblies = new Dictionary<String, Assembly>(StringComparer.OrdinalIgnoreCase);
static System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
return ResolveAssembly(args.Name);
}
private static Assembly ResolveAssembly(string argsName)
{
Assembly dll;
var name = "WindowsFormsApplication1.Libs." + new AssemblyName(argsName).Name + ".dll";
if (!_assemblies.TryGetValue(name, out dll))
{
Assembly res = typeof(Program).Assembly;
using (var input = res.GetManifestResourceStream(name))
{
if (input == null)
{
//TODO: log
return null;
}
Byte[] assemblyData = new Byte[input.Length];
input.Read(assemblyData, 0, assemblyData.Length);
if (null == (dll = Assembly.Load(assemblyData)))
{
//TODO: log
return null;
}
//TODO: log
_assemblies[name] = dll;
return dll;
}
}
return dll;
}
UPDATE: Created a BUG on the microsoft Connect site. You can also download a sample visual stuido 2010 solution (just expand Details fieldgroup) from there to reproduce it.
I've solved similar problem by saving assembly in temporary folder
public static byte[] ReadFully(Stream input)
{
var buffer = new byte[16 * 1024];
using (var ms = new MemoryStream())
{
int read;
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, read);
}
return ms.ToArray();
}
}
public App()
{
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
var assemblyName = new AssemblyName(args.Name);
if (assemblyName.Name != "Omikad.Core")
return null;
var resourceName = "Terem." + assemblyName.Name + ".dll";
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
{
if (stream == null)
return null;
var assemblyData = ReadFully(stream);
var tmp = Path.Combine(Path.GetTempPath(), "Omikad.Core.dll");
File.WriteAllBytes(tmp, assemblyData);
return Assembly.LoadFrom(tmp);
}
};
}
Try to add atribute:
[XmlInclude(typeof(Class2))]
public class Class1
{
public Class2 MyField { get; set; }
}
As for now I`ve ended up with two somewhat bad solutions:
While you can`t instanciate XmlSerializer for the type Class1, you still can instanciate it for the type Class2 from the main assembly. That does mean that if you move Class1 to ClassLibrary1 or Class2 to the main assembly - it will deserialize without errors. It works, but it is not possible to use this solution everywhere, plus it is ideologically wrong.
Use ILMerge to merge those assemblies into one. But it only works for non-wpf stuff, plus you should manage the situation with the assemblies attributes (there could be conflicts).
And one very bad idea:
Generate ClassLibrary1.XmlSerializer.dll with sgen.exe.
Also embed it into the main assembly.
Explicitly load it to the XmlSerializer cache calling one of it`s internal methods via reflection.
Although I had to use solution number one for now, I`m not satisfied with it, because it is too constraining.
I'd try the XmlSerializer(Type, Type[]) constructor and provide Class2 as an additional type using the second parameter. I've few experience with the XmlSerializer, but for DataContractSerializer this does the trick.

Categories