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

本文关键字:AppDomain 异常 FileNotFound 字节数 字节 数组 程序集 加载 | 更新日期: 2023-09-27 18:25:02

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

  • 下载一个字节数组,其中包含我需要执行的程序集
  • 将此程序集中的对象加载到新的应用程序域中,并对该对象执行方法

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

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

代码库在最后一行死亡,并显示以下消息:

附加信息:无法加载文件或程序集'Alerter.AgentProxy,版本=1.0.0.0,区域性=中性,PublicKeyToken=null'或其依赖项之一。系统无法找到指定的文件。

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

有指针吗?使用.NET 2实现最大兼容性

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

也许使用应用程序域上的回调切换到新创建的应用程序域的上下文可以成功加载?像这样的。。。

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

我这样做:

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

如果您认为加载appdomain很麻烦,请等待,直到您必须以一次性方式卸载appdomain。

我是这样做的:

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

因为如果使用SO中的泛型idispoable方法进行操作,它将失败,因为该操作不可序列化。。。

请注意,问题的根源是,在新的appdomain中,Assembly.Load代码无法从旧的appdomain访问字节数组,而是获得了一个空的字节数组,因此是一个具有Serializable属性的类。。。或者,如果您想在ApplicationDomainBridge中返回程序集,则会出现"缺少程序集"异常(序列化问题?),尽管它在其他域中加载良好。