从应用程序的资源加载.NET程序集并从内存运行它,但不终止主/宿主应用程序

Ele*_*ios 10 .net c# vb.net reflection .net-assembly

介绍


我使用的共享下一个C#代码示例大卫赫弗南 "为从应用程序的资源加载.NET程序集,并从内存中运行它:

Assembly a = Assembly.Load(bytes);
MethodInfo method = a.EntryPoint;
if (method != null)
    method.Invoke(a.CreateInstance(method.Name), null);
Run Code Online (Sandbox Code Playgroud)

在这里,我只是在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
Run Code Online (Sandbox Code Playgroud)

问题


问题是如果执行的程序集有一个应用程序退出指令,那么它也会终止我的主/主机应用程序.例如:

从这个源代码编译的ConsoleApplication1.exe:

Module Module1
    Sub Main()
        Environment.Exit(0)
    End Sub
End Module
Run Code Online (Sandbox Code Playgroud)

当我将ConsoleApplication1.exe添加到应用程序资源,然后我加载它并使用该Assembly.Load方法运行它时,它也会因为调用而终止我的应用程序Environment.Exit.


如何在不修改已执行程序集的源代码的情况下防止这种情况?

也许我可以做一些事情,比如将一种退出事件处理程序关联到执行的程序集以正确处理/忽略它?在这一点上我有什么选择?

PS:对我来说无论给定的解决方案是用C#还是VB.NET编写的.

请注意两件事,第一件是我的意图是以自动/抽象的方式解决这个问题,我的意思是最后的结果应该只需要调用"Execute"方法传递资源和参数而不用担心其余的部分; 其次,我希望执行的程序集同步运行,而不是异步运行......如果这可能对可能的解决方案很重要.

squ*_*l25 7

更新:我的第一个解决方案不适用于OP所要求的程序资源中包含的程序集; 而是从磁盘加载它.从字节数组加载的解决方案将遵循(进行中).请注意,以下几点适用于这两种解决方案:

  • 由于该Environment.Exit()方法由于缺少权限而抛出异常,因此在遇到该方法后,该方法的执行将不会继续.

  • 你会需要你的主要方法需要的所有权限,但你可以找到那些很快就在智能感知键入"权限",或者通过检查SecurityExceptionTargetSite属性(是的一个实例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...");
}
Run Code Online (Sandbox Code Playgroud)

解决方案2:当程序集是字节数组时

警告:癌症解决方案如下.

在更改我的解决方案以加载字节数组时,OP和我发现了一个奇怪的异常文件未找到异常:即使你传入一个字节数组Assembly.Load(),domain.ExecuteAssemblyByName()仍然会在磁盘上搜索程序集,原因有些奇怪.显然我们不是唯一有问题的人:加载字节数组装配.

首先,我们有一个Helper班级:

public class Helper : MarshalByRefObject
{
    public void LoadAssembly(Byte[] data)
    {
        var a = Assembly.Load(data);
        a.EntryPoint.Invoke(null, null);
    }
}
Run Code Online (Sandbox Code Playgroud)

如您所见,使用加载程序集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);
}
Run Code Online (Sandbox Code Playgroud)

请注意,在第二个解决方案中,SecurityException变为a TargetInvocationException,其InnerException属性为a SecurityException.不幸的是,这意味着您无法使用e.TargetSite哪种方法来查看异常.

结论/要记住的事情

这个解决方案并不完美.以某种方式通过方法的IL并人为地删除调用将会好得多Environment.Exit().