将字节数组程序集加载到新的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实现最大兼容性
也许使用应用程序域上的回调切换到新创建的应用程序域的上下文可以成功加载?像这样的。。。
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中返回程序集,则会出现"缺少程序集"异常(序列化问题?),尽管它在其他域中加载良好。