我有一个使用脚本引擎编译脚本的代码片段,我将程序集检索为字节数组。
现在我想在沙盒中加载这个程序集
,这就是我得到的:
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 的事件处理程序:
Assembly DomainAssemblyResolve(object sender, ResolveEventArgs args)
{
return _dynamicAssembly;
}
这意味着当我执行 domain.Load(assembly)
时,如果我不订阅该事件并返回该 Assembly
,我将获得 _dynamicAssembly,我获取 FileNotFoundException
。
上面编译运行了,但问题是在domain-assembly中执行的代码实际上并没有在沙箱中执行。当我获得提交方法并调用其中的工厂并返回此 AppDomain.CurrentDomain.FriendlyName
结果是:MyRoslynApplication.vshost.exe
不是沙盒AppDomain
我是否加载了我的 byte[]
-assembly 错误?
最佳答案
如果您想加载在沙箱中运行的类型并从您的主 AppDomain 访问它,您需要使用类似 CreateInstanceFromAndUnwrap 的方法。 .该类型需要是 MarshalByRefObject,以便它可以在调用 AppDomain 中创建透明代理以进行访问。
如果主 AppDomain 解析程序集,它将被加载到主 AppDomain(以及沙盒 AppDomain)中,这样您最终会加载两个副本。您的主 AppDomain 必须始终通过代理与沙箱保持隔离,以便只能访问 MarshalByRefObject 和可序列化的对象。请注意,您引用的类型也不能在要加载到沙箱中的程序集中定义;您需要在第三个公共(public)程序集中定义接口(interface)和可能的可序列化类型,然后将其加载到主 AppDomain 和沙箱 AppDomain 中。
我做了一些额外的挖掘,看起来所有将程序集加载到另一个 AppDomain 和生成代理的方法都需要一个程序集名称来解析。我不确定在这种情况下是否可以通过 byte[] 加载;您可能需要将程序集保存到磁盘并加载它。我再深入一点。
我认为你可以做到这一点(这是未经测试的,但似乎有道理)。
这些需要位于您的主应用程序和沙箱均可访问的“接口(interface)”程序集中(我将其称为 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 作为类型名称。
public sealed class StubLoader: MarshalByRefObject, IStubLoader
{
public object CreateInstanceFromAndUnwrap(byte[] assemblyBytes, string typeName)
{
var assembly = Assembly.Load(assemblyBytes);
return assembly.CreateInstance(typeName);
}
}
现在,要从您的主 AppDomain 使用它,您可以这样做:
//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");
不幸的是,AppDomains 并不易于使用。这是因为它们提供了高度隔离,因此需要代理以允许跨 AppDomain 边界使用。
为了响应如何编码(marshal)不可序列化和非 MarshalByRefObject 类,这里是共享接口(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 实例上执行。
关于c# - 将 Roslyn 编译的程序集加载到沙箱 AppDomain,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8312974/