【问题标题】:Loading byte array assembly into new AppDomain throws a FileNotFound exception将字节数组程序集加载到新的 AppDomain 中会引发 FileNotFound 异常
【发布时间】:2014-09-29 18:27:12
【问题描述】:

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

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

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

    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,版本=1.0.0.0,文化=中性, PublicKeyToken=null' 或其依赖项之一。系统无法 找到指定的文件。

没有任何代码会触发 AppOnAssemblyResolve 函数。 有趣的是它正确地读取了程序集的名称。此外,Alertera.AgentProxy 程序集没有任何外部依赖项,除了 System 和 Newtonsoft.Json。不过Newtsoft.Json已经作为资源嵌入到里面了,所以不需要单独加载。

有什么建议吗?使用 .NET 2 实现最大兼容性

【问题讨论】:

  • 我认为这是失败的,因为Load 正在将程序集加载到当前域(没有程序集解析处理程序),根据文档:“此方法应仅用于加载将程序集加载到当前应用程序域中。提供此方法是为了方便无法调用静态 Assembly.Load 方法的互操作调用者。要将程序集加载到其他应用程序域中,请使用 CreateInstanceAndUnwrap 等方法。"
  • 我看到了那些 cmets.. 我想我不知道该怎么做。我正在调用 CreateInstanceAndUnwrap,但在程序集加载到应用程序域之后。 CreateInstanceAndUnwrap 没有办法加载字节数组。您能否分享代码示例以将程序集正确加载到新的应用程序域中?
  • 对此一无所知,但 Fuslogvw.exe 有帮助吗?
  • 当您说“Newtsoft.Json 已作为资源嵌入其中”时,您是如何做到的?您是在运行时加载它,还是在 Alerteara 汇编中有对 Newtsoft 的强烈引用?
  • @AladinHdabe stackoverflow.com/questions/189549/… - 看看 Lars 的回复,我就是这样做的

标签: c# .net-assembly appdomain


【解决方案1】:

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

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

【讨论】:

  • 终于解决了这个问题(最终将程序集加载到当前域中)。我不得不调整你的代码以使用 AssemblyResolve 委托而不是直接的 Assembly.Load,但除此之外,它就像一个魅力。谢谢!
  • 嗨,你能分享一下“调整”版本吗?这个解决方案对我不起作用。
  • @MajkeloDev,当你说“它不起作用”时,你能更具体一点吗?有错误吗?
  • 我正在从外部存储加载 dll,当我将您的解决方案与新的 AppDomain 一起使用时,它仍然会在 Assembly.Load 上抛出“FileNotFoundException”。
  • @MajkeloDev,无法重现这一点,但我更新了一个更完整的示例。我的猜测是您的程序集需要一些不可用的依赖项,但我无法实现。如果这就是您所需要的,也许仅反射会有所帮助,或者就像@Igorek 所做的那样,您可以处理containerAppDomain 上的AssemblyResolve 事件以帮助加载可能需要的任何依赖项。
【解决方案2】:

我是这样做的:

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

因为如果你这样做with the generic idisposable method from SO,它会失败,因为动作不可序列化......

请注意,问题的根源在于,在新的 appdomain 中,Assembly.Load 代码无法访问旧 appdomain 中的字节数组,而是得到一个空字节数组,因此是一个具有 Serializable 属性的类。 .. 或者,如果你想在 ApplicationDomainBridge 中返回程序集,你会得到一个“缺少程序集”异常(序列化问题?),尽管它在另一个域中加载得很好。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多