3 回答
TA贡献1966条经验 获得超4个赞
这里的主要问题是,您可以在执行主appdomain中的代码时实例化插件。
您需要做的是创建一个代理类型,该类型在已加载的程序集中定义,但在新的appdomain中实例化。在两个应用程序域中都未加载类型的程序集的情况下,您无法跨应用程序域边界传递类型。 例如,如果您想像上面那样枚举类型并打印到控制台,则应从在新应用程序域中执行的代码执行,而不要从在当前应用程序域中执行的代码执行。
所以,让我们创造我们的插件的代理,这样会存在于你的主要组件,并负责执行所有插件相关的代码:
// Mark as MarshalByRefObject allows method calls to be proxied across app-domain boundaries
public class PluginRunner : MarshalByRefObject
{
// make sure that we're loading the assembly into the correct app domain.
public void LoadAssembly(byte[] byteArr)
{
Assembly.Load(byteArr);
}
// be careful here, only types from currently loaded assemblies can be passed as parameters / return value.
// also, all parameters / return values from this object must be marked [Serializable]
public string CreateAndExecutePluginResult(string assemblyQualifiedTypeName)
{
var domain = AppDomain.CurrentDomain;
// we use this overload of GetType which allows us to pass in a custom AssemblyResolve function
// this allows us to get a Type reference without searching the disk for an assembly.
var pluginType = Type.GetType(
assemblyQualifiedTypeName,
(name) => domain.GetAssemblies().Where(a => a.FullName == name.FullName).FirstOrDefault(),
null,
true);
dynamic plugin = Activator.CreateInstance(pluginType);
// do whatever you want here with the instantiated plugin
string result = plugin.RunTest();
// remember, you can only return types which are already loaded in the primary app domain and can be serialized.
return result;
}
}
在这里,我将在上面的评论中重申一些要点:
您必须继承自MarshalByRefObject,这意味着可以使用远程处理跨应用程序域边界代理对此对象的调用。
将数据传递到代理类或从代理类传递数据时,必须对数据进行标记,[Serializable]并且还必须采用当前加载的程序集中的类型。如果您需要插件将某些特定对象返回给您,则说PluginResultModel您应该在由两个程序集/应用程序域加载的共享程序集中定义此类。
必须将程序集合格的类型名传递给CreateAndExecutePluginResult其当前状态,但是可以通过自己迭代程序集和类型并删除对的调用来消除此要求Type.GetType。
接下来,您需要创建域并运行代理:
static void Main(string[] args)
{
var bytes = File.ReadAllBytes(@"...filepath...");
var domain = AppDomain.CreateDomain("plugintest", null, null, null, false);
var proxy = (PluginRunner)domain.CreateInstanceAndUnwrap(typeof(PluginRunner).Assembly.FullName, typeof(PluginRunner).FullName);
proxy.LoadAssembly(bytes);
proxy.CreateAndExecutePluginResult("TestPlugin.Class1, TestPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
}
再说一遍,因为它非常重要,而且我很长一段时间都不了解:当您在此代理类上执行方法时,例如proxy.LoadAssembly实际上是被序列化为字符串并传递给新应用程序要执行的域。这不是正常的函数调用,您需要非常小心传入/传出这些方法的内容。
TA贡献1779条经验 获得超6个赞
此调用导致System.IO.FileNotFoundException,但我不知道为什么。我怀疑会发生异常,因为再次在磁盘上搜索了程序集。但是,为什么程序要再次加载它?
这里的关键是了解加载程序上下文,有关MSDN的一篇很好的文章:
将加载程序上下文视为保存程序集的应用程序域中的逻辑存储区。根据程序集的加载方式,它们属于三种加载程序上下文之一。
加载上下文
LoadFrom上下文
两种情况都没有
从装byte[]配件将装配件放置在这两个上下文中。
至于这两个上下文,除非应用程序预订AssemblyResolve事件,否则不能绑定该上下文中的程序集。通常应避免这种情况。
在下面的代码中,我们使用AssemblyResolve事件在Load上下文中加载程序集,从而使我们能够绑定到该程序集。
如何使用从字节数组加载的程序集在新的appdomain中创建实例?
请注意,这仅仅是概念的证明,它是探索装载程序上下文的基本要素。建议的方法是使用@caesay描述的代理,并在本文中由Suzanne Cook进一步评论。
这是一个不保留对实例的引用的实现(类似于即发即弃)。
首先,我们的插件:
Test.cs
namespace Plugins
{
public class Test
{
public Test()
{
Console.WriteLine($"Hello from {AppDomain.CurrentDomain.FriendlyName}.");
}
}
}
接下来,在一个新ConsoleApp的插件加载器中:
PluginLoader.cs
[Serializable]
class PluginLoader
{
private readonly byte[] _myBytes;
private readonly AppDomain _newDomain;
public PluginLoader(byte[] rawAssembly)
{
_myBytes = rawAssembly;
_newDomain = AppDomain.CreateDomain("New Domain");
_newDomain.AssemblyResolve += new ResolveEventHandler(MyResolver);
}
public void Test()
{
_newDomain.CreateInstance("plugin", "Plugins.Test");
}
private Assembly MyResolver(object sender, ResolveEventArgs args)
{
AppDomain domain = (AppDomain)sender;
Assembly asm = domain.Load(_myBytes);
return asm;
}
}
Program.cs
class Program
{
static void Main(string[] args)
{
byte[] rawAssembly = File.ReadAllBytes(@"D:\Projects\AppDomainTest\plugin.dll");
PluginLoader plugin = new PluginLoader(rawAssembly);
// Output:
// Hello from New Domain
plugin.Test();
// Output:
// Assembly: mscorlib
// Assembly: ConsoleApp
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
Console.WriteLine($"Assembly: {asm.GetName().Name}");
}
Console.ReadKey();
}
}
CreateInstance("plugin", "Plugins.Test")尽管不了解插件程序集,但显示的输出已成功从默认应用程序域中成功调用。
- 3 回答
- 0 关注
- 192 浏览
添加回答
举报