将字节数组程序集加载到新的AppDomain会引发FileNotFound异常

时间:2014-09-29 18:27:13

标签: c# .net-assembly appdomain

我正在尝试执行以下操作:

  • 下载包含我需要执行的程序集的字节数组。
  • 在新的应用程序域中加载此程序集中的对象并在对象上执行方法

以下是我的代码尝试将程序集加载到新的应用程序域:

    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);

代码库在最后一行消失,并带有以下消息:

  

其他信息:无法加载文件或程序集   'Alertera.AgentProxy,Version = 1.0.0.0,Culture = neutral,   PublicKeyToken = null'或其依赖项之一。系统不能   找到指定的文件。

没有代码会遇到AppOnAssemblyResolve函数。 有趣的是它已经正确地读取了组件的名称。此外,Alertera.AgentProxy程序集没有任何外部依赖项,除了在System和Newtonsoft.Json上。但是,Newtsoft.Json已作为资源嵌入其中,因此不需要单独加载。

任何指针?使用.NET 2实现最大兼容性

2 个答案:

答案 0 :(得分:2)

也许使用应用域上的回调切换到新创建的应用域的上下文将允许您成功加载?像这样......

    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;
        }
    }

编辑:

这是一个更完整的示例,其中显示了如何加载程序集并将信息传递回调用应用程序域,还卸载了为加载程序集而创建的应用程序域。

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;
    }            
}    

答案 1 :(得分:0)

我这样做是这样的:

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

用法:

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

如果您认为加载应用程序域是很麻烦的,请等到您必须一次性丢弃应用程序域。

我是这样做的:

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

如果您执行with the generic idisposable method from SO,它将失败,因为该操作不可序列化...

请注意,麻烦的根源在于,在新的应用程序域中,Assembly.Load代码无法从旧的应用程序域访问字节数组,而是获取一个空的字节数组,因此具有属性Serializable的类。 ..或分别,如果要在ApplicationDomainBridge中返回程序集,尽管在其他域中加载得很好,但是会收到“程序集丢失”异常(序列化问题?)。