简体   繁体   中英

Loading byte array assembly into new AppDomain throws a FileNotFound exception

I'm trying to do the following:

  • Download a byte array that contains an assembly that I need to execute.
  • Load an object from this assembly in a new app domain and execute method on the object

Here is my code that attempts to load the assembly into new app domain:

    public object Execute(byte[] agentCode)
    {
        var app = AppDomain.CreateDomain("MonitoringProxy", AppDomain.CurrentDomain.Evidence, new AppDomainSetup {ApplicationBase = AppDomain.CurrentDomain.BaseDirectory}, new PermissionSet(PermissionState.Unrestricted));
        app.AssemblyResolve += AppOnAssemblyResolve;
        var assembly = app.Load(agentCode);

The codebase dies on the last line with the following message:

Additional information: Could not load file or assembly 'Alertera.AgentProxy, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.

No code ever hits the AppOnAssemblyResolve function. What's interesting is that it has read the name of the assembly properly. Furthermore, Alertera.AgentProxy assembly does not have any external dependencies, except on System and Newtonsoft.Json. However, Newtsoft.Json has been imbedded into it as a resource, so it does not need to be loaded separately.

Any pointers? Using .NET 2 for maxmimum compatibility

Maybe using the callback on the app domain to switch to the context of the newly created app domain will allow you to load successfully? Something like this...

    public object Execute(byte[] assemblyBytes)
    {
        AppDomain domainWithAsm = AsmLoad.Execute(assemblyBytes);
        ....
    }

    [Serializable]
    public class AsmLoad
    {
        public byte[] AsmData;

        public void LoadAsm() 
        {
            Assembly.Load(AsmData);
            Console.WriteLine("Loaded into: " + AppDomain.CurrentDomain.FriendlyName);
        }

        public static AppDomain Execute(byte[] assemblyBytes)
        {
            AsmLoad asmLoad = new AsmLoad() { AsmData = assemblyBytes };
            var app = AppDomain.CreateDomain("MonitoringProxy", AppDomain.CurrentDomain.Evidence, new AppDomainSetup { ApplicationBase = AppDomain.CurrentDomain.BaseDirectory }, new PermissionSet(PermissionState.Unrestricted));
            app.DoCallBack(new CrossAppDomainDelegate(asmLoad.LoadAsm));
            return app;
        }
    }

EDIT:

Here is a more complete example, which shows how to load an assembly and pass information back to the calling app domain, and also unloads the app domain created to load the assembly.

class Program
{
    static void Main(string[] args)
    {
        var assemblyBytes = File.ReadAllBytes(@"C:\dev\Newtonsoft.Json.dll");

        // load an unload the same assembly 5 times
        for (int i = 0; i < 5; i++)
        {
            var assemblyContainer = AssemblyContainer.LoadAssembly(assemblyBytes, true);
            var assemblyName = assemblyContainer.AssemblyName;

            assemblyContainer.Unload();
        }

        Console.ReadKey();
    }
}    

[Serializable]
public class AssemblyContainer
{
    public byte[] AssemblyData { get; set; }
    public bool ReflectionOnly { get; set; }
    private AppDomain Container { get; set; }
    public AssemblyName AssemblyName { get; set; }

    /// <summary>
    /// Unload the domain containing the assembly
    /// </summary>
    public void Unload()
    {
        AppDomain.Unload(Container);
    }

    /// <summary>
    /// Load the assembly
    /// </summary>
    /// <remarks>This will be executed</remarks>
    public void LoadAssembly()
    {                
        var assembly = ReflectionOnly ? Assembly.ReflectionOnlyLoad(AssemblyData) : Assembly.Load(AssemblyData);
        AssemblyName = assembly.GetName();

        // set data to pick up from the main app domain
        Container.SetData("AssemblyData", AssemblyName);
    }

    /// <summary>
    /// Load the assembly into another domain
    /// </summary>
    /// <param name="assemblyBytes"></param>
    /// <param name="reflectionOnly"></param>
    /// <returns></returns>
    public static AssemblyContainer LoadAssembly(byte[] assemblyBytes, bool reflectionOnly = false)
    {
        var containerAppDomain = AppDomain.CreateDomain(
            "AssemblyContainer",
            AppDomain.CurrentDomain.Evidence,
            new AppDomainSetup
            {
                ApplicationBase = AppDomain.CurrentDomain.BaseDirectory
            },
            new PermissionSet(PermissionState.Unrestricted));

        AssemblyContainer assemblyContainer = new AssemblyContainer()
        {
            AssemblyData = assemblyBytes,
            ReflectionOnly = reflectionOnly,
            Container = containerAppDomain
        };

        containerAppDomain.DoCallBack(new CrossAppDomainDelegate(assemblyContainer.LoadAssembly));

        // collect data from the other app domain
        assemblyContainer.AssemblyName = (AssemblyName)containerAppDomain.GetData("AssemblyData");
        return assemblyContainer;
    }            
}    

I do it like this:

Public Class ApplicationDomainBridge
    Inherits MarshalByRefObject

    Public ReturnValue As Object
    Public ErrorObject As System.Exception
End Class


<Serializable>
Public Class AsmHelper
    ' Inherits MarshalByRefObject

    Private Class ApplicationDomainBridge
        Inherits MarshalByRefObject

        Public ErrorObject As System.Exception
        Public Eval As ReportTester.Parameters.AbstractEvaluator
    End Class



    Private AsmData As Byte()
    Private CallbackResult As ApplicationDomainBridge


    Sub New()
        Me.CallbackResult = New ApplicationDomainBridge
    End Sub



    Public Sub LoadAsmAndExecuteEval()
        Try
            ' System.Console.WriteLine("Executing in: " & AppDomain.CurrentDomain.FriendlyName)
            ' Throw New System.InvalidCastException("Test")

            Dim assembly As System.Reflection.Assembly = System.Reflection.Assembly.Load(AsmData)

            ' Here we do something
            ' The object you return must have 
            ' Inherits MarshalByRefObject

            Dim programType As System.Type = assembly.GetType("RsEval")
            Dim eval As ReportTester.Parameters.AbstractEvaluator = DirectCast(System.Activator.CreateInstance(programType), ReportTester.Parameters.AbstractEvaluator)

            Me.CallbackResult.Eval = eval
        Catch ex As Exception
            Me.CallbackResult.ErrorObject = ex
        End Try

    End Sub


    Public Shared Function ExecuteInAppTemporaryAppDomain(temporaryAppDomain As AppDomain, ByVal assemblyBytes As Byte()) As ReportTester.Parameters.AbstractEvaluator
        Dim loadExecute As AsmHelper = New AsmHelper() With {
        .AsmData = assemblyBytes
    }

        temporaryAppDomain.DoCallBack(New CrossAppDomainDelegate(AddressOf loadExecute.LoadAsmAndExecuteEval))
        loadExecute.AsmData = Nothing

        Dim retValue As ReportTester.Parameters.AbstractEvaluator = Nothing

        If loadExecute.CallbackResult.ErrorObject Is Nothing Then
            retValue = loadExecute.CallbackResult.Eval
            loadExecute.CallbackResult = Nothing
            loadExecute = Nothing
        End If

        If loadExecute IsNot Nothing AndAlso loadExecute.CallbackResult IsNot Nothing AndAlso loadExecute.CallbackResult.ErrorObject IsNot Nothing Then
            Throw loadExecute.CallbackResult.ErrorObject
        End If

        Return retValue
    End Function


End Class

Usage:

Dim assemblyBytes As Byte() = System.IO.File.ReadAllBytes(results.PathToAssembly)
Dim temporaryAppDomain As AppDomain = CreateAppDomain()
Dim evaluator As ReportTester.Parameters.AbstractEvaluator = AsmHelper.ExecuteInAppTemporaryAppDomain(temporaryAppDomain, assemblyBytes)
    evaluator.Domain = temporaryAppDomain

And it you thought loading an appdomain is hacky, wait until you must unload the appdomain in a disposable.

I did that like this:

Protected Overridable Sub Dispose(disposing As Boolean)
    If Not disposedValue Then
        If disposing Then
            ' TODO: verwalteten Zustand (verwaltete Objekte) entsorgen.

            If Me.LoadContext IsNot Nothing Then
                Me.LoadContext.Unload()
                Me.LoadContext = Nothing
            End If

            If Me.Stream IsNot Nothing Then
                Me.Stream.Dispose()
                Me.Stream = Nothing
            End If

            'If Parameters.m_parameterValues IsNot Nothing Then
            '    Parameters.m_parameterValues.Clear()
            '    Parameters.m_parameterValues = Nothing
            'End If

            If Me.Domain IsNot Nothing Then
                ' https://stackoverflow.com/questions/7793074/unload-an-appdomain-while-using-idisposable
                Dim thread As System.Threading.Thread = New System.Threading.Thread(
                  Sub(obj As System.AppDomain)
                      Try
                          ' System.Threading.Thread.Sleep(1000)
                          System.AppDomain.Unload(obj)
                      Catch ex As System.Threading.ThreadAbortException
                          ' System.Console.WriteLine(ex.Message)
                          System.GC.Collect()
                          System.GC.WaitForPendingFinalizers()
                      End Try
                  End Sub
                )
                thread.IsBackground = True
                thread.Start(Me.Domain)
                Me.Domain = Nothing
            End If

            System.GC.Collect()
            System.GC.WaitForPendingFinalizers()
        End If

        ' TODO: nicht verwaltete Ressourcen (nicht verwaltete Objekte) freigeben und Finalize() weiter unten überschreiben.
        ' TODO: grosse Felder auf Null setzen.
    End If
    disposedValue = True
End Sub

Because if you do it with the generic idisposable method from SO , it will fail because the action is not serializable...

Note that the origin of the trouble is, that in the new appdomain, Assembly.Load code can't access the byte array from the old appdomain, and instead gets an empty byte-array, thus a class with attribute Serializable... Or respectively, if you want to return the assembly in ApplicationDomainBridge, you get a "missing assembly" exception (serialization issue?), although it loaded fine in the other domain.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM