Dynamically loaded assembly does not load in new AppDomain

This is not a duplicate. I examined this related StackOverflow question with no luck: How do I load an assembly in AppDomain with all the recursive links?

I have two console applications. AssemblyLoaderTest.exe and testapp.exe

  • I am trying to use AssemblyLoaderTest.exe to dynamically load testapp.exe and call a method from a class in testapp.exe
  • While the code is working, the testWrite () method in testapp.exe is executed correctly (and outputsuccess.txt is output), however, testapp.exe is loaded into the same AppDomain , which is proven because "CallMethodFromDllInNewAppDomain" always returns false. I am trying to load testapp.exe in a new AppDomain .

My question is: how can I change the code below so that testapp.exe is loaded into the new AppDomain, and as a result of "CallMethodFromDllInNewAppDomain" returns true? Thanks!

The code is below. Both can simply be copied to new console applications in VS and executed / compiled.

Console Application # 1:

using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Security.Policy; namespace AssemblyLoaderTest { class Program { static void Main(string[] args) { List<object> parameters = new List<object>(); parameters.Add("Test from console app"); bool loadedInNewAppDomain = DynamicAssemblyLoader.CallMethodFromDllInNewAppDomain(@"c:\temp\testapp.exe", "testapp.TestClass", "TestWrite", parameters); } } public static class DynamicAssemblyLoader { public static string ExeLoc = ""; public static bool CallMethodFromDllInNewAppDomain(string exePath, string fullyQualifiedClassName, string methodName, List<object> parameters) { ExeLoc = exePath; List<Assembly> assembliesLoadedBefore = AppDomain.CurrentDomain.GetAssemblies().ToList<Assembly>(); int assemblyCountBefore = assembliesLoadedBefore.Count; AppDomainSetup domaininfo = new AppDomainSetup(); Evidence adevidence = AppDomain.CurrentDomain.Evidence; AppDomain domain = AppDomain.CreateDomain("testDomain", adevidence, domaininfo); AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve); domain.CreateInstanceFromAndUnwrap(exePath, fullyQualifiedClassName); List<Assembly> assemblies = domain.GetAssemblies().ToList<Assembly>(); string mainExeName = System.IO.Path.GetFileNameWithoutExtension(exePath); Assembly assembly = assemblies.FirstOrDefault(c => c.FullName.StartsWith(mainExeName)); Type type2 = assembly.GetType(fullyQualifiedClassName); List<Type> parameterTypes = new List<Type>(); foreach (var parameter in parameters) { parameterTypes.Add(parameter.GetType()); } var methodInfo = type2.GetMethod(methodName, parameterTypes.ToArray()); var testClass = Activator.CreateInstance(type2); object returnValue = methodInfo.Invoke(testClass, parameters.ToArray()); List<Assembly> assembliesLoadedAfter = AppDomain.CurrentDomain.GetAssemblies().ToList<Assembly>(); int assemblyCountAfter = assembliesLoadedAfter.Count; if (assemblyCountAfter > assemblyCountBefore) { // Code always comes here return false; } else { // This would prove the assembly was loaded in a NEW domain. Never gets here. return true; } } public static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) { // This is required I've found return System.Reflection.Assembly.LoadFrom(ExeLoc); } } } 

Console application # 2:

 using System; namespace testapp { class Program { static void Main(string[] args) { Console.WriteLine("Hello from console"); } } [Serializable] public class TestClass : MarshalByRefObject { public void TestWrite(string message) { System.IO.File.WriteAllText(@"outputsuccess.txt", message); } } } 
0
source share
1 answer

Use this class. Here are a few notes:

  • This class explicitly sets the current directory of the process and the base application path of the application's isolated domain. This is not entirely necessary, but it will make your life a lot easier.

    • If you do not set the base path of the application to the directory containing the secondary assembly, the runtime will try to resolve any dependencies of the secondary assembly on the same base path of the application as the primary assembly, which probably has no dependencies of the secondary assembly. You can use the AssemblyResolve event to properly resolve the dependencies correctly, but the settings of the base application path are much simpler and less error prone for this.

    • If you do not set Environment.CurrentDirectory , then file operations such as File.WriteAllText("myfile.txt", "blah") will resolve the path to the current directory, which is probably not what the author of the secondary assembly intended. (ASIDE: always allow paths manually for this reason.)

  • I believe that simple reflection operations, such as GetMethod , will not work on the MarshalByRefObject proxy, for example, CreateInstanceFromAndUnwrap returned. Therefore, you need to do a little more to call.

    • If you own the primary and secondary assembly, you can create an interface for the call - put the interface in the general assembly, define the cross-domain call in the interface, implement the interface in the target class, execute domain.CreateInstanceFromAndUnwrap in the target type and pass the result as an interface, which then can be used to call across the domain border.

    • The solution below presents an alternative tool that is less invasive - you do not need to own a secondary assembly for this technique to work. The idea is that the primary domain creates a well-known intermediate object ( InvokerHelper ) in the secondary domain, and this intermediary performs the necessary reflection inside the secondary domain.

Here's the full implementation:

 // Provides a means of invoking an assembly in an isolated appdomain public static class IsolatedInvoker { // main Invoke method public static void Invoke(string assemblyFile, string typeName, string methodName, object[] parameters) { // resolve path assemblyFile = Path.Combine(Environment.CurrentDirectory, assemblyFile); Debug.Assert(assemblyFile != null); // get base path var appBasePath = Path.GetDirectoryName(assemblyFile); Debug.Assert(appBasePath != null); // change current directory var oldDirectory = Environment.CurrentDirectory; Environment.CurrentDirectory = appBasePath; try { // create new app domain var domain = AppDomain.CreateDomain(Guid.NewGuid().ToString(), null, appBasePath, null, false); try { // create instance var invoker = (InvokerHelper) domain.CreateInstanceFromAndUnwrap(Assembly.GetExecutingAssembly().Location, typeof(InvokerHelper).FullName); // invoke method var result = invoker.InvokeHelper(assemblyFile, typeName, methodName, parameters); // process result Debug.WriteLine(result); } finally { // unload app domain AppDomain.Unload(domain); } } finally { // revert current directory Environment.CurrentDirectory = oldDirectory; } } // This helper class is instantiated in an isolated app domain private class InvokerHelper : MarshalByRefObject { // This helper function is executed in an isolated app domain public object InvokeHelper(string assemblyFile, string typeName, string methodName, object[] parameters) { // create an instance of the target object var handle = Activator.CreateInstanceFrom(assemblyFile, typeName); // get the instance of the target object var instance = handle.Unwrap(); // get the type of the target object var type = instance.GetType(); // invoke the method var result = type.InvokeMember(methodName, BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance, null, instance, parameters); // success return result; } } } 
+3
source

Source: https://habr.com/ru/post/1208477/


All Articles