动态加载的程序集未在新的AppDomain中加载

时间:2014-03-25 16:48:12

标签: c# .net .net-assembly appdomain system.reflection

这不重复 - 我已经审查了这个相关的StackOverflow问题而没有运气:How to Load an Assembly to AppDomain with all references recursively?

我有两个控制台应用程序。 AssemblyLoaderTest.exe和testapp.exe

  1. 我正在尝试使用AssemblyLoaderTest.exe动态加载testapp.exe并从testapp.exe中的类调用方法
  2. 到目前为止代码工作 - 方法" TestWrite()"在testapp.exe中正确执行(并写入outputsuccess.txt),然而 testapp.exe加载在同一个AppDomain 中,这被证明是因为" CallMethodFromDllInNewAppDomain"总是返回false。我正在尝试在新AppDomain 中加载testapp.exe。
  3. 我的问题:如何修改以下代码,以便在新的AppDomain中加载testapp.exe,结果," CallMethodFromDllInNewAppDomain"返回true?谢谢!

    以下代码。两者都可以简单地复制到VS中的新控制台应用程序中并执行/编译。

    控制台应用程序#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);
            }
        }
    }
    

    控制台应用程序#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);
            }
        }
    
    }
    

1 个答案:

答案 0 :(得分:3)

使用此课程。以下是一些注意事项:

  1. 此类显式设置进程的当前目录和隔离的应用程序域的应用程序基本路径。这不是完全必要的,但它会让你的生活变得更加轻松。

    1. 如果您未将应用程序基本路径设置为包含辅助程序集的目录,则运行时将尝试解析辅助程序集与主程序集相同的应用程序基本路径的所有依赖关系,这可能没有辅助程序集的依赖关系。您可以使用AssemblyResolve事件正确手动解析依赖关系,但设置应用程序基本路径是一种更简单且不易出错的方法。

    2. 如果您未设置Environment.CurrentDirectory,则File.WriteAllText("myfile.txt", "blah")等文件操作将解析当前目录的路径,这可能不是辅助程序集的路径作者打算。 (ASIDE:因此总是手动解析路径。)

  2. 我认为像GetMethod这样的简单反射操作不会对MarshalByRefObject代理工作,例如由CreateInstanceFromAndUnwrap返回。所以你需要多做一些来调用。

    1. 如果您是主要和次要程序集的所有者,则可以为调用创建接口 - 将接口放在共享程序集中,在接口中定义跨域调用,实现接口在目标类中,对目标类型执行domain.CreateInstanceFromAndUnwrap并将结果转换为接口,然后可以使用该接口跨域边界调用。

    2. 下面的解决方案提供了一种侵入性较小的替代方法 - 您不必拥有辅助装置以使此技术起作用。我们的想法是主域在辅助域中创建一个众所周知的中间对象(InvokerHelper),该中间人从辅助域内部执行必要的反射

  3. 这是一个完整的实施:

    // 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;
            }
        }
    }