问题描述
我有一个使用脚本引擎编译脚本的代码片段,我将程序集作为字节数组进行检索.
I've got a code-snippet that compiles a script with the script engine and I retreiv the assembly as a byte array.
现在我想在沙箱中加载这个 Assembly
,这就是我所拥有的:
Now I want to load this Assembly
in a Sandbox, this is what I've got:
Assembly _dynamicAssembly;
ScriptEngine _engine;
Session _session;
public string Execute(string code)
{
// Setup sandbox
var e = new Evidence();
e.AddHostEvidence(new Zone(SecurityZone.Internet));
var ps = SecurityManager.GetStandardSandbox(e);
var setup = new AppDomainSetup
{ ApplicationBase = Environment.CurrentDirectory };
var domain =
AppDomain.CreateDomain("Sandbox",
AppDomain.CurrentDomain.Evidence, setup, ps);
AppDomain.CurrentDomain.AssemblyResolve += DomainAssemblyResolve;
// Process code
var submission = _engine.CompileSubmission<object>(code, _session);
submission.Compilation.Emit(memoryStream);
var assembly = memoryStream.ToArray();
_dynamicAssembly = Assembly.Load(assembly);
var loaded = domain.Load(assembly);
// Rest of the code...
}
这是 AssemblyResolve 的事件处理程序:
This is the event handler for AssemblyResolve:
Assembly DomainAssemblyResolve(object sender, ResolveEventArgs args)
{
return _dynamicAssembly;
}
这意味着当我执行 domain.Load(assembly)
时,我将获得 _dynamicAssembly,如果我不订阅该事件并返回那个 Assembly
,我得到一个 FileNotFoundException
.
This means that when I do domain.Load(assembly)
I will get the _dynamicAssembly, if I don't subscribe to that event and return that Assembly
, I get a FileNotFoundException
.
以上编译运行,但问题是在domain-assembly中执行的代码实际上并没有在sandbox中执行.当我获得提交方法并调用其中的工厂并返回此 AppDomain.CurrentDomain.FriendlyName
时,结果是: MyRoslynApplication.vshost.exe
这是 not 沙盒 AppDomain
The above compiles and runs, but the problem is that code that is executed in the domain-assembly is not actually executed in the sandbox. When I get the submission-method and invoke the factory in it and return this AppDomain.CurrentDomain.FriendlyName
the result is: MyRoslynApplication.vshost.exe
which is not the sandbox AppDomain
我加载我的 byte[]
程序集是否错误?
Am I loading my byte[]
-assembly wrong?
推荐答案
如果您想加载在沙箱中运行的类型并从您的主 AppDomain 访问它,您需要使用类似 CreateInstanceFromAndUnwrap.该类型需要是 MarshalByRefObject,以便它可以在调用 AppDomain 中创建透明代理以进行访问.
If you want to load a type that runs in the sandbox and access it from your main AppDomain, you'll need to use a method like CreateInstanceFromAndUnwrap. The type will need to be a MarshalByRefObject so that it can create a transparent proxy in the calling AppDomain for access.
如果主 AppDomain 解析程序集,它将被加载到主 AppDomain(以及沙箱 AppDomain)中,以便您最终加载两个副本.您的主 AppDomain 必须始终通过代理与沙箱保持隔离,以便只能访问 MarshalByRefObject 和可序列化对象.请注意,您引用的类型也不能在要加载到沙箱中的程序集中定义;您需要在第三个公共程序集中定义接口和可能的可序列化类型,然后将其加载到主 AppDomain 和沙箱 AppDomain 中.
If the main AppDomain resolves the assembly, it will be loaded into the main AppDomain (as well as the sandbox AppDomain) so that you end up with two copies loaded. Your main AppDomain must always remain insulated from the sandbox via proxies, so that only MarshalByRefObject and serializable objects can be accessed. Note that the type you're referencing also cannot be defined in the assembly you want to load into the sandbox; you'll want to define interfaces and possibly serializable types in a third common assembly, which will then be loaded into both the main and sandbox AppDomains.
我已经做了一些额外的挖掘,看起来所有将程序集加载到另一个 AppDomain 和生成代理的方法都需要一个程序集名称来解析.我不确定在这种情况下是否可以通过 byte[] 加载;您可能需要将程序集保存到磁盘并加载它.我再挖一点.
I've done some additional digging and it looks like all the methods for loading an assembly into another AppDomain and generating a proxy require an assembly name to resolve. I'm not sure if it's possible to load via a byte[] in this case; you might need to save the assembly to disk and load that. I'll dig a bit more.
我认为你可以做到这一点(这是未经测试的,但似乎是合理的).
I think you can do this (this is untested, but it seems plausible).
这些需要位于主应用程序和沙箱均可访问的接口"程序集中(我将其称为 Services.dll):
These would need to be in an 'interface' assembly accessible to both your main app and the sandbox (I'll refer to it as Services.dll):
public interface IMyService
{
//.... service-specific methods you'll be using
}
public interface IStubLoader
{
Object CreateInstanceFromAndUnwrap(byte[] assemblyBytes, string typeName);
}
接下来是 StubLoader.dll 中的一个类.你不会直接引用这个程序集;这是您将调用第一个 AppDomain.CreateInstanceFromAndUnwrap 的地方,提供它作为程序集名称和 StubLoader 作为类型名称.
Next is a class in a StubLoader.dll. You won't reference this assembly directly; this is where you'll call the first AppDomain.CreateInstanceFromAndUnwrap, providing this as the assembly name and StubLoader as the type name.
public sealed class StubLoader: MarshalByRefObject, IStubLoader
{
public object CreateInstanceFromAndUnwrap(byte[] assemblyBytes, string typeName)
{
var assembly = Assembly.Load(assemblyBytes);
return assembly.CreateInstance(typeName);
}
}
现在,要从您的主 AppDomain 使用它,请执行以下操作:
Now, to use it from your main AppDomain, you do this:
//Create transparent proxy for the stub loader, which will live in the sandbox
var stubLoader = (IStubLoader)sandboxDomain.CreateInstanceFromAndUnwrap("Stubloader.dll", "StubLoader");
//Have the stub loader marshal a proxy to a dynamically loaded assembly (via byte[]) where MyService is the type name implementing MarshalByRefObject and IMyService
var myService = (IMyService)stubLoader.CreateInstanceFromAndUnwrap(assemblyBytes, "MyService");
不幸的是,AppDomain 使用起来并不简单.这是因为它们提供了高度的隔离,因此需要代理以允许跨 AppDomain 边界使用.
Unfortunately, AppDomains are not simple to use. This is because they provide a high degree of insulation and therefore require proxying to allow for usage across AppDomain boundaries.
作为对如何编组不可序列化和非 MarshalByRefObject 类的回应,以下是共享接口 DLL 中可能包含的内容的粗略示例:
In response to how you could marshal a non-serializable and non-MarshalByRefObject class, here is a rough example of what might be in the shared interface DLL:
public interface ISessionWrapper
{
void DoSomethingWithSession();
}
public sealed class SessionWrapper : MarshalByRefObject, ISessionWrapper
{
private readonly Session _session;
public SessionWrapper(Session session)
{
_session = session;
}
public void DoSomethingWithSession()
{
//Do something with the wrapped session...
//This executes inside the sandbox, even though it can be called (via proxy) from outside the sandbox
}
}
现在,无论您的原始服务需要与 Session 一起工作,它都可以通过 ISessionWrapper,它的调用将在幕后编组,以便所有实际代码都在沙箱中在沙箱中的真实 Session 实例上执行.
Now everywhere your original service needed to work with Session, it can instead pass ISessionWrapper, whose calls will be marshalled behind the scenes so that all of the actual code executes in the sandbox on a real Session instance living in the sandbox.
这篇关于将 Roslyn 编译的程序集加载到沙箱 AppDomain 中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!