从应用程序的资源和 运行 内存中加载 .NET 程序集,但不终止 main/host 应用程序
Load a .NET assembly from the application's resources and run It from memory, but without terminating the main/host application
简介
我正在使用 David Heffernan' 为 loading a .NET assembly from the application's resources and run It from memory 共享的下一个 C# 代码示例:
Assembly a = Assembly.Load(bytes);
MethodInfo method = a.EntryPoint;
if (method != null)
method.Invoke(a.CreateInstance(method.Name), null);
在这里我只是分享一个我也在用的VB.NET的改编:
Public Shared Sub Execute(ByVal resource As Byte(), ByVal parameters As Object())
Dim ass As Assembly = Assembly.Load(resource)
Dim method As MethodInfo = ass.EntryPoint
If (method IsNot Nothing) Then
Dim instance As Object = ass.CreateInstance(method.Name)
method.Invoke(instance, parameters)
If (instance IsNot Nothing) AndAlso (instance.GetType().GetInterfaces.Contains(GetType(IDisposable))) Then
DirectCast(instance, IDisposable).Dispose()
End If
instance = Nothing
method = Nothing
ass = Nothing
Else
Throw New EntryPointNotFoundException("Entrypoint not found in the specified resource. Are you sure it is a .NET assembly?")
End If
End Sub
问题
问题是,如果执行的程序集有应用程序退出指令,那么它也会终止我的 main/host 应用程序。例如:
ConsoleApplication1.exe 从源代码编译:
Module Module1
Sub Main()
Environment.Exit(0)
End Sub
End Module
当我将 ConsoleApplication1.exe 添加到应用程序资源,然后使用 Assembly.Load
方法加载它并 运行 它时,它也终止了我的应用程序,因为调用 Environment.Exit
。
问题
如何在不修改已执行程序集的源代码的情况下防止这种情况发生?
也许我可以做一些事情,比如将一种退出事件处理程序关联到已执行的程序集,以正确 handle/ignore 它?此时我有什么选择?
PS:对我来说,无论给定的解决方案是用 C# 还是 VB.NET.
编写的
请注意两点,第一是我的意图是通过automated/abstracted方式解决这个问题,我的意思是最终结果应该只需要调用传递资源的"Execute"方法和争论,不用担心其余的;其次,我希望执行的程序集是 运行 同步的,而不是异步的...以防万一可能对可能的解决方案很重要。
更新:我的第一个解决方案不适用于 OP 要求的程序资源中包含的程序集;相反,它从磁盘加载它。从字节数组加载的解决方案将在后面(进行中)。请注意,以下几点适用于两种解决方案:
由于Environment.Exit()
方法因为权限不足抛出异常,所以遇到方法后不会继续执行。
您将需要 Main 方法所需的所有权限,但您只需在智能感知中输入 "Permission" 或检查 SecurityException
的TargetSite
属性(它是MethodBase
的一个实例,会告诉你哪个方法失败了)。
如果您的 Main 中的另一个方法需要 UnmanagedCode
权限,那么您就不走运了,至少使用这个解决方案是这样。
请注意,我发现 UnmanagedCode
权限是 Environment.Exit()
需要的权限 完全是通过反复试验。
解决方案 1:当程序集在磁盘上时
好的,这是我目前的发现,请耐心等待。我们将创建一个沙盒 AppDomain:
AppDomainSetup adSetup = new AppDomainSetup();
adSetup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
// This is where the main executable resides. For more info on this, see "Remarks" in
// https://msdn.microsoft.com/en-us/library/system.appdomainsetup.applicationbase(v=vs.110).aspx#Anchor_1
PermissionSet permission = new PermissionSet(PermissionState.None);
// Permissions of the AppDomain/what it can do
permission.AddPermission(new SecurityPermission(SecurityPermissionFlag.AllFlags & ~SecurityPermissionFlag.UnmanagedCode));
// All SecurityPermission flags EXCEPT UnmanagedCode, which is required by Environment.Exit()
// BUT the assembly needs SecurityPermissionFlag.Execution to be run;
// otherwise you'll get an exception.
permission.AddPermission(new FileIOPermission(PermissionState.Unrestricted));
permission.AddPermission(new UIPermission(PermissionState.Unrestricted));
// the above two are for Console.WriteLine() to run, which is what I had in the Main method
var assembly = Assembly.LoadFile(exePath); // path to ConsoleApplication1.exe
var domain = AppDomain.CreateDomain("SomeGenericName", null, adSetup, permission, null); // sandboxed AppDomain
try
{
domain.ExecuteAssemblyByName(assembly.GetName(), new string[] { });
}
// The SecurityException is thrown by Environment.Exit() not being able to run
catch (SecurityException e) when (e.TargetSite == typeof(Environment).GetMethod("Exit"))
{
Console.WriteLine("Tried to run Exit");
}
catch (SecurityException e)
{
// Some other action in your method needs SecurityPermissionFlag.UnmanagedCode to run,
// or the PermissionSet is missing some other permission
}
catch
{
Console.WriteLine("Something else failed in ConsoleApplication1.exe's main...");
}
方案二:当汇编为字节数组时
警告:接下来是致癌溶液。
当更改我的解决方案以加载字节数组时,OP 和我发现了一个奇怪的异常文件未找到异常:即使您将字节数组传递给 Assembly.Load()
,domain.ExecuteAssemblyByName()
出于某种奇怪的原因仍在磁盘中搜索程序集。 显然我们不是唯一有问题的人:Loading Byte Array Assembly。
首先,我们有一个 Helper
class:
public class Helper : MarshalByRefObject
{
public void LoadAssembly(Byte[] data)
{
var a = Assembly.Load(data);
a.EntryPoint.Invoke(null, null);
}
}
如您所见,使用 Assembly.Load()
加载程序集并调用它的入口点。这是我们将加载到 AppDomain
:
中的代码
AppDomainSetup adSetup = new AppDomainSetup();
adSetup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
// This is where the main executable resides. For more info on this, see "Remarks" in
// https://msdn.microsoft.com/en-us/library/system.appdomainsetup.applicationbase(v=vs.110).aspx#Anchor_1
PermissionSet permission = new PermissionSet(PermissionState.None);
// Permissions of the AppDomain/what it can do
permission.AddPermission(new SecurityPermission(SecurityPermissionFlag.AllFlags & ~SecurityPermissionFlag.UnmanagedCode));
// All SecurityPermission flags EXCEPT UnmanagedCode, which is required by Environment.Exit()
// BUT the assembly needs SecurityPermissionFlag.Execution to be run;
// otherwise you'll get an exception.
permission.AddPermission(new FileIOPermission(PermissionState.Unrestricted));
permission.AddPermission(new UIPermission(PermissionState.Unrestricted));
// the above two are for Console.WriteLine() to run, which is what I had in the Main method
var domain = AppDomain.CreateDomain("SomeGenericName", null, adSetup, permission, null); // sandboxed AppDomain
try
{
Helper helper = (Helper)domain.CreateInstanceAndUnwrap(typeof(Helper).Assembly.FullName, typeof(Helper).FullName);
// create an instance of Helper in the new AppDomain
helper.LoadAssembly(bytes); // bytes is the in-memory assembly
}
catch (TargetInvocationException e) when (e.InnerException.GetType() == typeof(SecurityException))
{
Console.WriteLine("some kind of permissions issue here");
}
catch (Exception e)
{
Console.WriteLine("Something else failed in ConsoleApplication1.exe's main... " + e.Message);
}
请注意,在第二种解决方案中,SecurityException
变成了 TargetInvocationException
,而 InnerException
属性 变成了 SecurityException
。不幸的是,这意味着您不能使用 e.TargetSite
查看哪个方法引发了异常。
Conclusion/Things谨记
这个解决方案并不完美。以某种方式通过方法的 IL 并人为地删除对 Environment.Exit()
.
的调用会好得多
我可以成功地将程序集加载到另一个 AppDomain 中并调用它的入口点。
Environment.Exit always shuts down the hosting process.
解决方法是,从加载的控制台应用程序的 Main 中 return 一个 int。零表示成功,其他数字表示错误。
而不是这个:
Module Module1
Sub Main()
// your code
Environment.Exit(0)
End Sub
End Module
写:(我希望这是有效的 VB.NET :-))
Module Module1
Function Main() As Integer
// your code
Return 0 // 0 == no error
End Function
End Module
演示 - C#
class Program
{
static void Main(string[] args)
{
Launcher.Start(@"C:\Users\path\to\your\console\app.exe");
}
}
public class Launcher : MarshalByRefObject
{
public static void Start(string pathToAssembly)
{
TextWriter originalConsoleOutput = Console.Out;
StringWriter writer = new StringWriter();
Console.SetOut(writer);
AppDomain appDomain = AppDomain.CreateDomain("Loading Domain");
Launcher program = (Launcher)appDomain.CreateInstanceAndUnwrap(
typeof(Launcher).Assembly.FullName,
typeof(Launcher).FullName);
program.Execute(pathToAssembly);
AppDomain.Unload(appDomain);
Console.SetOut(originalConsoleOutput);
string result = writer.ToString();
Console.WriteLine(result);
}
/// <summary>
/// This gets executed in the temporary appdomain.
/// No error handling to simplify demo.
/// </summary>
public void Execute(string pathToAssembly)
{
// load the bytes and run Main() using reflection
// working with bytes is useful if the assembly doesn't come from disk
byte[] bytes = File.ReadAllBytes(pathToAssembly); //"Program.exe"
Assembly assembly = Assembly.Load(bytes);
MethodInfo main = assembly.EntryPoint;
main.Invoke(null, new object[] { null });
}
}
Also to note:
Also, note that if you use LoadFrom you'll likely get a FileNotFound
exception because the Assembly resolver will attempt to find the
assembly you're loading in the GAC or the current application's bin
folder. Use LoadFile to load an arbitrary assembly file instead--but
note that if you do this you'll need to load any dependencies
yourself.
更多 AppDomain
代码可以帮助找到解决方案。
代码可以在 LoadUnload
找到
LoadUnload 项目中包含的小型应用程序包含 AppDomain
您可以在您的解决方案中采用的代码。
只有一种方法可以做到这一点。您必须 dynamically instrument 程序集将要执行的所有代码。这归结为拦截系统调用。没有简单的方法可以做到这一点。请注意,这不需要修改源代码。
为什么 .NET 安全系统不能这样做?虽然系统可以为您提供安全权限,您可以使用它来控制对 Environment.Exit
的调用,但这并不能真正解决问题。程序集仍然可以调用非托管代码。其他答案已经指出,这可以通过创建一个 AppDomain 并撤销 SecurityPermissionFlag.UnmanagedCode
来完成。确实,这可行,但您在注释中指出您允许程序集调用非托管代码。
就是这样,如果你想运行同一个process.You中的代码也可以运行另一个进程中的代码,但是你必须进行进程间通信。
简介
我正在使用 David Heffernan' 为 loading a .NET assembly from the application's resources and run It from memory 共享的下一个 C# 代码示例:
Assembly a = Assembly.Load(bytes);
MethodInfo method = a.EntryPoint;
if (method != null)
method.Invoke(a.CreateInstance(method.Name), null);
在这里我只是分享一个我也在用的VB.NET的改编:
Public Shared Sub Execute(ByVal resource As Byte(), ByVal parameters As Object())
Dim ass As Assembly = Assembly.Load(resource)
Dim method As MethodInfo = ass.EntryPoint
If (method IsNot Nothing) Then
Dim instance As Object = ass.CreateInstance(method.Name)
method.Invoke(instance, parameters)
If (instance IsNot Nothing) AndAlso (instance.GetType().GetInterfaces.Contains(GetType(IDisposable))) Then
DirectCast(instance, IDisposable).Dispose()
End If
instance = Nothing
method = Nothing
ass = Nothing
Else
Throw New EntryPointNotFoundException("Entrypoint not found in the specified resource. Are you sure it is a .NET assembly?")
End If
End Sub
问题
问题是,如果执行的程序集有应用程序退出指令,那么它也会终止我的 main/host 应用程序。例如:
ConsoleApplication1.exe 从源代码编译:
Module Module1
Sub Main()
Environment.Exit(0)
End Sub
End Module
当我将 ConsoleApplication1.exe 添加到应用程序资源,然后使用 Assembly.Load
方法加载它并 运行 它时,它也终止了我的应用程序,因为调用 Environment.Exit
。
问题
如何在不修改已执行程序集的源代码的情况下防止这种情况发生?
也许我可以做一些事情,比如将一种退出事件处理程序关联到已执行的程序集,以正确 handle/ignore 它?此时我有什么选择?
PS:对我来说,无论给定的解决方案是用 C# 还是 VB.NET.
编写的请注意两点,第一是我的意图是通过automated/abstracted方式解决这个问题,我的意思是最终结果应该只需要调用传递资源的"Execute"方法和争论,不用担心其余的;其次,我希望执行的程序集是 运行 同步的,而不是异步的...以防万一可能对可能的解决方案很重要。
更新:我的第一个解决方案不适用于 OP 要求的程序资源中包含的程序集;相反,它从磁盘加载它。从字节数组加载的解决方案将在后面(进行中)。请注意,以下几点适用于两种解决方案:
由于
Environment.Exit()
方法因为权限不足抛出异常,所以遇到方法后不会继续执行。您将需要 Main 方法所需的所有权限,但您只需在智能感知中输入 "Permission" 或检查
SecurityException
的TargetSite
属性(它是MethodBase
的一个实例,会告诉你哪个方法失败了)。如果您的 Main 中的另一个方法需要
UnmanagedCode
权限,那么您就不走运了,至少使用这个解决方案是这样。请注意,我发现
UnmanagedCode
权限是Environment.Exit()
需要的权限 完全是通过反复试验。
解决方案 1:当程序集在磁盘上时
好的,这是我目前的发现,请耐心等待。我们将创建一个沙盒 AppDomain:
AppDomainSetup adSetup = new AppDomainSetup();
adSetup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
// This is where the main executable resides. For more info on this, see "Remarks" in
// https://msdn.microsoft.com/en-us/library/system.appdomainsetup.applicationbase(v=vs.110).aspx#Anchor_1
PermissionSet permission = new PermissionSet(PermissionState.None);
// Permissions of the AppDomain/what it can do
permission.AddPermission(new SecurityPermission(SecurityPermissionFlag.AllFlags & ~SecurityPermissionFlag.UnmanagedCode));
// All SecurityPermission flags EXCEPT UnmanagedCode, which is required by Environment.Exit()
// BUT the assembly needs SecurityPermissionFlag.Execution to be run;
// otherwise you'll get an exception.
permission.AddPermission(new FileIOPermission(PermissionState.Unrestricted));
permission.AddPermission(new UIPermission(PermissionState.Unrestricted));
// the above two are for Console.WriteLine() to run, which is what I had in the Main method
var assembly = Assembly.LoadFile(exePath); // path to ConsoleApplication1.exe
var domain = AppDomain.CreateDomain("SomeGenericName", null, adSetup, permission, null); // sandboxed AppDomain
try
{
domain.ExecuteAssemblyByName(assembly.GetName(), new string[] { });
}
// The SecurityException is thrown by Environment.Exit() not being able to run
catch (SecurityException e) when (e.TargetSite == typeof(Environment).GetMethod("Exit"))
{
Console.WriteLine("Tried to run Exit");
}
catch (SecurityException e)
{
// Some other action in your method needs SecurityPermissionFlag.UnmanagedCode to run,
// or the PermissionSet is missing some other permission
}
catch
{
Console.WriteLine("Something else failed in ConsoleApplication1.exe's main...");
}
方案二:当汇编为字节数组时
警告:接下来是致癌溶液。
当更改我的解决方案以加载字节数组时,OP 和我发现了一个奇怪的异常文件未找到异常:即使您将字节数组传递给 Assembly.Load()
,domain.ExecuteAssemblyByName()
出于某种奇怪的原因仍在磁盘中搜索程序集。 显然我们不是唯一有问题的人:Loading Byte Array Assembly。
首先,我们有一个 Helper
class:
public class Helper : MarshalByRefObject
{
public void LoadAssembly(Byte[] data)
{
var a = Assembly.Load(data);
a.EntryPoint.Invoke(null, null);
}
}
如您所见,使用 Assembly.Load()
加载程序集并调用它的入口点。这是我们将加载到 AppDomain
:
AppDomainSetup adSetup = new AppDomainSetup();
adSetup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
// This is where the main executable resides. For more info on this, see "Remarks" in
// https://msdn.microsoft.com/en-us/library/system.appdomainsetup.applicationbase(v=vs.110).aspx#Anchor_1
PermissionSet permission = new PermissionSet(PermissionState.None);
// Permissions of the AppDomain/what it can do
permission.AddPermission(new SecurityPermission(SecurityPermissionFlag.AllFlags & ~SecurityPermissionFlag.UnmanagedCode));
// All SecurityPermission flags EXCEPT UnmanagedCode, which is required by Environment.Exit()
// BUT the assembly needs SecurityPermissionFlag.Execution to be run;
// otherwise you'll get an exception.
permission.AddPermission(new FileIOPermission(PermissionState.Unrestricted));
permission.AddPermission(new UIPermission(PermissionState.Unrestricted));
// the above two are for Console.WriteLine() to run, which is what I had in the Main method
var domain = AppDomain.CreateDomain("SomeGenericName", null, adSetup, permission, null); // sandboxed AppDomain
try
{
Helper helper = (Helper)domain.CreateInstanceAndUnwrap(typeof(Helper).Assembly.FullName, typeof(Helper).FullName);
// create an instance of Helper in the new AppDomain
helper.LoadAssembly(bytes); // bytes is the in-memory assembly
}
catch (TargetInvocationException e) when (e.InnerException.GetType() == typeof(SecurityException))
{
Console.WriteLine("some kind of permissions issue here");
}
catch (Exception e)
{
Console.WriteLine("Something else failed in ConsoleApplication1.exe's main... " + e.Message);
}
请注意,在第二种解决方案中,SecurityException
变成了 TargetInvocationException
,而 InnerException
属性 变成了 SecurityException
。不幸的是,这意味着您不能使用 e.TargetSite
查看哪个方法引发了异常。
Conclusion/Things谨记
这个解决方案并不完美。以某种方式通过方法的 IL 并人为地删除对 Environment.Exit()
.
我可以成功地将程序集加载到另一个 AppDomain 中并调用它的入口点。
Environment.Exit always shuts down the hosting process.
解决方法是,从加载的控制台应用程序的 Main 中 return 一个 int。零表示成功,其他数字表示错误。
而不是这个:
Module Module1
Sub Main()
// your code
Environment.Exit(0)
End Sub
End Module
写:(我希望这是有效的 VB.NET :-))
Module Module1
Function Main() As Integer
// your code
Return 0 // 0 == no error
End Function
End Module
演示 - C#
class Program
{
static void Main(string[] args)
{
Launcher.Start(@"C:\Users\path\to\your\console\app.exe");
}
}
public class Launcher : MarshalByRefObject
{
public static void Start(string pathToAssembly)
{
TextWriter originalConsoleOutput = Console.Out;
StringWriter writer = new StringWriter();
Console.SetOut(writer);
AppDomain appDomain = AppDomain.CreateDomain("Loading Domain");
Launcher program = (Launcher)appDomain.CreateInstanceAndUnwrap(
typeof(Launcher).Assembly.FullName,
typeof(Launcher).FullName);
program.Execute(pathToAssembly);
AppDomain.Unload(appDomain);
Console.SetOut(originalConsoleOutput);
string result = writer.ToString();
Console.WriteLine(result);
}
/// <summary>
/// This gets executed in the temporary appdomain.
/// No error handling to simplify demo.
/// </summary>
public void Execute(string pathToAssembly)
{
// load the bytes and run Main() using reflection
// working with bytes is useful if the assembly doesn't come from disk
byte[] bytes = File.ReadAllBytes(pathToAssembly); //"Program.exe"
Assembly assembly = Assembly.Load(bytes);
MethodInfo main = assembly.EntryPoint;
main.Invoke(null, new object[] { null });
}
}
Also to note:
Also, note that if you use LoadFrom you'll likely get a FileNotFound exception because the Assembly resolver will attempt to find the assembly you're loading in the GAC or the current application's bin folder. Use LoadFile to load an arbitrary assembly file instead--but note that if you do this you'll need to load any dependencies yourself.
更多 AppDomain
代码可以帮助找到解决方案。
代码可以在 LoadUnload
LoadUnload 项目中包含的小型应用程序包含 AppDomain
您可以在您的解决方案中采用的代码。
只有一种方法可以做到这一点。您必须 dynamically instrument 程序集将要执行的所有代码。这归结为拦截系统调用。没有简单的方法可以做到这一点。请注意,这不需要修改源代码。
为什么 .NET 安全系统不能这样做?虽然系统可以为您提供安全权限,您可以使用它来控制对 Environment.Exit
的调用,但这并不能真正解决问题。程序集仍然可以调用非托管代码。其他答案已经指出,这可以通过创建一个 AppDomain 并撤销 SecurityPermissionFlag.UnmanagedCode
来完成。确实,这可行,但您在注释中指出您允许程序集调用非托管代码。
就是这样,如果你想运行同一个process.You中的代码也可以运行另一个进程中的代码,但是你必须进行进程间通信。