9

スクリプト エンジンでスクリプトをコンパイルするコード スニペットがあり、アセンブリをバイト配列として取得します。

これをサンドボックスにロードしたいのですがAssembly、これが私が持っているものです:

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)すると_dynamicAssemblyを取得することを意味します。そのイベントにサブスクライブせずにそれを返すとAssemblyFileNotFoundException.

上記はコンパイルおよび実行されますが、問題は、ドメイン アセンブリで実行されるコードが実際にはサンドボックスで実行されないことです。送信メソッドを取得し、その中でファクトリを呼び出してこれを返すとAppDomain.CurrentDomain.FriendlyName、結果は次のようになります。MyRoslynApplication.vshost.exeこれはサンドボックスではありませんAppDomain

byte[]アセンブリを間違ってロードしていますか?

4

2 に答える 2

11

サンドボックスで実行される型を読み込み、メインの AppDomain からアクセスする場合は、 CreateInstanceFromAndUnwrapなどのメソッドを使用する必要があります。型は、アクセスのために呼び出し元の AppDomain で透過プロキシを作成できるように、MarshalByRefObject である必要があります。

メインの AppDomain がアセンブリを解決すると、それがメインの AppDomain (およびサンドボックスの AppDomain) に読み込まれるため、最終的に 2 つのコピーが読み込まれます。MarshalByRefObject とシリアライズ可能なオブジェクトのみにアクセスできるように、メインの AppDomain は常にプロキシを介してサンドボックスから隔離されたままにしておく必要があります。参照しているタイプは、サンドボックスにロードするアセンブリでも定義できないことに注意してください。インターフェイスと、場合によってはシリアル化可能な型を 3 番目の共通アセンブリで定義し、メインとサンドボックスの両方の AppDomain にロードする必要があります。


さらに掘り下げたところ、アセンブリを別の AppDomain にロードしてプロキシを生成するすべてのメソッドで、アセンブリ名を解決する必要があるようです。この場合、byte[] 経由でロードできるかどうかはわかりません。アセンブリをディスクに保存してロードする必要がある場合があります。もう少し掘ります。


私はあなたがこれを行うことができると思います (これはテストされていませんが、もっともらしいようです)。

これらは、メイン アプリとサンドボックスの両方にアクセスできる「インターフェイス」アセンブリにある必要があります (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 の境界を越えて使用できるようにするためにプロキシが必要になるためです。


シリアル化不可で MarshalByRefObject 以外のクラスをマーシャリングする方法に応じて、共有インターフェイス 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 インスタンスでサンドボックス内で実行されます。

于 2011-11-29T15:15:20.470 に答える