问题描述
这不是重复的 - 我已经回顾了这个相关的 StackOverflow 问题,但没有运气:如何将所有引用递归地加载到 AppDomain 中?
This is not a duplicate - I have reviewed this related StackOverflow question with no luck: How to Load an Assembly to AppDomain with all references recursively?
我有两个控制台应用程序.AssemblyLoaderTest.exe 和 testapp.exe
I have two console applications. AssemblyLoaderTest.exe and testapp.exe
- 我正在尝试使用 AssemblyLoaderTest.exe 动态加载 testapp.exe 并从 testapp.exe 中的类调用方法
- 到目前为止代码有效 - testapp.exe 中的方法TestWrite()"被正确执行(并且输出成功.txt 被写入),然而,testapp.exe 被加载到相同的 AppDomain,这被证明是因为CallMethodFromDllInNewAppDomain"总是返回 false.我正在尝试在 新 AppDomain 中加载 testapp.exe.
- I am trying to use AssemblyLoaderTest.exe to dynamically load testapp.exe and call a method from a class within testapp.exe
- So far the code works - the method "TestWrite()" in testapp.exe is executed correctly (and outputsuccess.txt is written), however, testapp.exe is loaded in the same AppDomain, which is proven because "CallMethodFromDllInNewAppDomain" always returns false. I am trying to load testapp.exe in a new AppDomain.
我的问题:如何修改以下代码,以便将 testapp.exe 加载到新的 AppDomain 中,结果CallMethodFromDllInNewAppDomain"返回 true?谢谢!
My question: how can I modify the below code so that testapp.exe is loaded in a new AppDomain, and as a result, "CallMethodFromDllInNewAppDomain" returns true? Thank you!
代码如下.两者都可以简单地复制到 VS 中的新控制台应用程序中并执行/编译.
Code below. Both can be simply copied into new Console applications in VS and executed/compiled.
控制台应用程序 #1:
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: emp estapp.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:
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);
}
}
}
推荐答案
使用这个类.以下是一些注意事项:
Use this class. Here are some notes:
该类明确设置进程的当前目录和隔离应用程序域的应用程序基路径.这并非完全必要,但它会让您的生活变得更轻松.
This class explicitly sets the current directory of the process and the app base path of the isolated app domain. This isn't entirely necessary, but it will make your life a whole lot easier.
如果您没有将应用程序基路径设置为包含辅助程序集的目录,则运行时将尝试针对与主程序集相同的应用程序基本路径解析辅助程序集的任何依赖项,这可能没有辅助程序集的依赖项.您可以使用
AssemblyResolve
事件手动正确解析依赖项,但设置应用程序基本路径是一种更简单且不易出错的方法.
If you don't set the app base path to the directory containing the secondary assembly, then the runtime will attempt to resolve any dependencies of the secondary assembly against the same app base path as the primary assembly, which probably doesn't have the secondary assembly's dependencies. You could use the
AssemblyResolve
event to manually resolve the dependencies correctly, but settings the app base path is a much simpler and less error-prone way to do this.
如果不设置Environment.CurrentDirectory
,那么File.WriteAllText("myfile.txt", "blah")
等文件操作会针对当前目录解析路径,这可能不是辅助程序集作者的意图.(旁白:因此始终手动解析路径.)
If you don't set Environment.CurrentDirectory
, then file operations such as File.WriteAllText("myfile.txt", "blah")
will resolve paths against the current directory, which is probably not what the secondary assembly's author intended. (ASIDE: Always resolve paths manually for this reason.)
我相信像 GetMethod
这样的简单反射操作不适用于由 CreateInstanceFromAndUnwrap
返回的 MarshalByRefObject 代理.所以你需要做更多的事情来调用.
I believe simple reflection operations like GetMethod
won't work on a MarshalByRefObject proxy such as returned by CreateInstanceFromAndUnwrap
. So you need to do a little more to invoke.
如果您是主程序集和辅助程序集的所有者,您可以为调用创建一个接口——将接口放在共享程序集中,在接口中定义跨域调用,实现接口在目标类中,对目标类型执行
domain.CreateInstanceFromAndUnwrap
并将结果转换为接口,然后您可以使用该接口跨域边界调用.
If you are the owner of both the primary and secondary assemblies, you could create an interface for the invocation -- put the interface in a shared assembly, define the cross-domain call in the interface, implement the interface in the target class, do a
domain.CreateInstanceFromAndUnwrap
on the target type and cast the result as the interface, which you can then use to call across the domain boundary.
下面的解决方案提供了一种侵入性较小的替代方法——您不必拥有辅助组件即可使该技术发挥作用.这个想法是主域在辅助域中创建一个众所周知的中间对象 (InvokerHelper
),并且该中介从辅助域内部执行必要的反射.
The solution below provides an alternative means that is less invasive -- you don't have to own the secondary assembly for this technique to work. The idea is that the primary domain creates a well-known intermediary object (InvokerHelper
) in the secondary domain, and that intermediary performs the necessary reflection from inside the secondary domain.
这是一个完整的实现:
// 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;
}
}
}
这篇关于动态加载的程序集未加载到新的 AppDomain 中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!