从另一个 WPF 应用程序加载 WPF 应用程序程序集,得到错误:无法在同一个 AppDomain 中创建多个 System.Windows.Application 实例 [英] Load a WPF application Assembly from another WPF app, get error: Cannot create more than one System.Windows.Application instance in the same AppDomain

查看:20
本文介绍了从另一个 WPF 应用程序加载 WPF 应用程序程序集,得到错误:无法在同一个 AppDomain 中创建多个 System.Windows.Application 实例的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

场景:

LUNCHER.exe:一个 WPF 应用程序 >> Build 32bit, .Net 4.5.1, location= D:\

LOADED.exe:另一个 WPF 应用程序 >> Build 32bit, .Net 4.5.1, location= D:\

我是两个程序集的所有者(应用程序和thair 源)

现在,我想将 LOADED.exe [及其资源,例如图像 dll 和...) 作为 Byte array 加载到内存中并执行它,然后从硬盘中删除 LOADED.exe 及其资源.

在第一步中,我试图只是将 LOADED.exe 文件加载到内存并执行它(所以我使用了一个 没有任何资源的简单 EXE步骤).

A)

好的,我为 WinForm 程序找到了这种方式

我通过LUNCHER.exe中另一个按钮下的以下代码进行了尝试:

private void Button_Click_1(object sender, RoutedEventArgs e){尝试{var filePath = string.Format("{0}{1}", Utility.ExePath, PART_PATH);AppDomain newappdomain = getAppDomainForAssembly(filePath, "LOADED.exe.domain");对象加载exe_object = getInstanceFromAppDomain(ref newappdomain, filePath);//如果您知道要调用的方法名称...executeMethod(loadedexe_object.GetType(), "methodname", ref loadedexe_object, null);//或获取入口点...executeMethod(loadedexe_object.GetType(),_asm_resolve(filePath).EntryPoint.Name, 引用加载的exe_object, null);}捕获(异常前){var 类型 = "";if (ex 是 ArgumentNullException){type = "ArgumentNullException";}else if (ex is NotSupportedException){type = "NotSupportedException";}else if (ex 是 AppDomainUnloadedException){type = "AppDomainUnloadedException";}else if (ex is TypeLoadException){type = "TypeLoadException";}else if (ex is MissingMethodException){type = "MissingMethodException";}else if (ex is MethodAccessException){type = "MethodAccessException";}else if (ex is BadImageFormatException){type = "BadImageFormatException";}else if (ex is FileLoadException){type = "FileLoadException";}MessageBox.Show(type + "\n\r\n\r" + ex.Message + "\n\r\n\r" + ex.InnerException + "\n\r\n\r" + ex.Source);}}private AppDomain getAppDomainForAssembly(string assemblypath, string appdomainname){//this._assembly_file = AssemblyFile;string _assembly_file_name = System.IO.Path.GetFileName(assemblypath);字符串_rootpath = System.IO.Path.GetDirectoryName(assemblypath);//this._assembly_class_name = AssemblyClassNameToInstance;AppDomainSetup _app_domain_info = new AppDomainSetup();_app_domain_info.ApplicationBase = _rootpath;_app_domain_info.PrivateBinPath = _rootpath;_app_domain_info.PrivateBinPathProbe = _rootpath;_app_domain_info.ConfigurationFile = _rootpath + @"LOADED.exe.config";//这里放正确的.assembly.config文件的路径AppDomain _app_domain = AppDomain.CreateDomain(appdomainname, null, _app_domain_info);返回_app_domain;}受保护的 System.Reflection.Assembly _asm_resolve(string assemblyFile){返回 System.Reflection.Assembly.LoadFrom(assemblyFile);}私有对象 getInstanceFromAppDomain(ref AppDomain appDomain,字符串assemblyPath,字符串className = null){if (string.IsNullOrEmpty(className)){System.Reflection.Assembly assembly = _asm_resolve(assemblyPath);System.Reflection.MethodInfo method = assembly.EntryPoint;//现在我的错误在这一行>>返回 appDomain.CreateInstanceFromAndUnwrap(assemblyPath, method.Name);}别的{返回 appDomain.CreateInstanceFromAndUnwrap(assemblyPath, className);}}

现在我的错误在这一行:

好的!

<小时>

C)

我再次搜索并找到了这个(动态加载的程序集未加载到新的 AppDomain 中):

//提供一种在隔离的应用程序域中调用程序集的方法公共静态类 IsolatedInvoker{//主调用方法public static void Invoke(string assemblyFile, string typeName, string methodName, object[] parameters){//解析路径assemblyFile = Path.Combine(Environment.CurrentDirectory, assemblyFile);Debug.Assert(assemblyFile != null);//获取基本路径var appBasePath = Path.GetDirectoryName(assemblyFile);Debug.Assert(appBasePath != null);//改变当前目录var oldDirectory = Environment.CurrentDirectory;Environment.CurrentDirectory = appBasePath;尝试{//创建新的应用程序域var domain = AppDomain.CreateDomain(Guid.NewGuid().ToString(), null, appBasePath, null, false);尝试{//创建实例var invoker = (InvokerHelper) domain.CreateInstanceFromAndUnwrap(Assembly.GetExecutingAssembly().Location, typeof(InvokerHelper).FullName);//调用方法var result = invoker.InvokeHelper(assemblyFile, typeName, methodName, parameters);//处理结果Debug.WriteLine(result);}最后{//卸载应用程序域AppDomain.Unload(域);}}最后{//恢复当前目录Environment.CurrentDirectory = oldDirectory;}}//这个助手类在一个隔离的应用程序域中实例化私有类 InvokerHelper : MarshalByRefObject{//此辅助函数在隔离的应用程序域中执行public object InvokeHelper(string assemblyFile, string typeName, string methodName, object[] parameters){//创建目标对象的实例var handle = Activator.CreateInstanceFrom(assemblyFile, typeName);//获取目标对象的实例var instance = handle.Unwrap();//获取目标对象的类型var type = instance.GetType();//调用方法var result = type.InvokeMember(methodName, BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance, null, instance, parameters);//成功返回结果;}}}

然后我通过LUNCHER.exe中另一个按钮下的以下代码调用它:

private void Button_Click_2(object sender, RoutedEventArgs e){var filePath = string.Format("{0}{1}", Utility.ExePath, PART_PATH);IndependentInvoker.Invoke(filePath, "Main", "Main", new object[] {});}

但是我得到了和以前一样的错误 **B**:

<块引用>

'System.TypeLoadException' 类型的未处理异常发生在午餐.exe

附加信息:无法从程序集中加载类型Main"已加载,版本=1.0.0.0,文化=中性,PublicKeyToken=null'.

<小时>

D)

我也在 LUNCHER.EXE 中的另一个按钮下测试了这种方式:

private void Button_Click_3(object sender, RoutedEventArgs e){var filePath = @"D:\LOADED.exe";var dll = File.ReadAllBytes(filePath);var assembly = Assembly.Load(dll);var app = typeof(应用程序);var field = app.GetField("_resourceAssembly", BindingFlags.NonPublic | BindingFlags.Static);field.SetValue(null, assembly);//修复urihelpervar helper = typeof (BaseUriHelper);var property = helper.GetProperty("ResourceAssembly", BindingFlags.NonPublic | BindingFlags.Static);property.SetValue(null, assembly, null);//---- 现在我的错误在这一行 >>assembly.EntryPoint.Invoke(null, new object[] {});}

运行时最后一行代码出错:

<块引用>

未处理的异常类型mscorlib.dll 中出现System.Reflection.TargetInvocationException"

附加信息:异常已被一个目标抛出调用.

<小时>

最后:

我很困惑!

  • 我在上述所有方法中的错误是什么?
  • 为什么所有的方式都以错误结束!!!

请帮助我提供简单的描述和一些代码,因为我在这种情况下是初学者(加载程序集,创建 AppDomain 和...)但我需要将 WPF 应用程序加载到内存和显示它的窗口,然后当它在内存中运行时从硬盘中删除它的文件.

解决方案

  1. 创建一个共享程序集.这将被加载到两个 AppDomains(启动器"域、加载"域)并作为我们加载"应用程序域的入口点:

    添加新项目 > 类库 > 名称:ChildDomainLoader

    向新项目添加以下引用:System.XamlWindowsBasePresentationFramework

    在您的 Launcher 项目中为 ChildDomainLoader 添加项目引用.Loaded 项目不必修改.

  2. 向共享程序集添加一些代码.我们需要一个可以跨域调用并加载我们的子程序集的 MarshalByRefObject.我们称之为Runner:

    使用系统;使用 System.IO;使用 System.Linq;使用 System.Reflection;使用 System.Windows;命名空间 ChildDomainLoader{公共类跑步者:MarshalByRefObject{公共静态 AppDomain RunInOtherDomain(string assemblyPath){var ownType = typeof (Runner);string ownAssemblyName = ownType.Assembly.FullName;//创建一个新的 AppDomain 并在其中加载我们的程序集.var childDomain = AppDomain.CreateDomain(Guid.NewGuid().ToString());childDomain.Load(ownAssemblyName);//在其他 AppDomain 中调用 Run().var runner = (Runner) childDomain.CreateInstanceAndUnwrap(ownAssemblyName, ownType.FullName);runner.Run(assemblyPath);返回子域;}公共无效运行(字符串程序集路径){//我们将程序集加载为字节数组.var otherAssemblyBytes = File.ReadAllBytes(assemblyPath);var assembly = AppDomain.CurrentDomain.Load(otherAssemblyBytes);AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>{throw new NotImplementedException("如果你依赖其他程序集,可能需要在这里做一些工作.");};//将程序集设置为 ResourceAssembly,否则 WPF 会混淆.Application.ResourceAssembly = 程序集;//搜索 App 类.var app = 程序集.GetExportedTypes().Single(t => typeof(Application).IsAssignableFrom(t));//调用它的 Main 方法.MethodInfo main = app.GetMethod("Main", BindingFlags.Static | BindingFlags.Public);main.Invoke(null, null);}}}

  3. 使用它.从您的 Launcher 应用程序调用 Runner.RunInOtherDomain.

    var assemblyPath = "你加载的.exe 的路径";ChildDomainLoader.Runner.RunInOtherDomain(assemblyPath);File.Delete(assemblyPath);

The Scenario:

LUNCHER.exe: a WPF Application >> Build 32bit, .Net 4.5.1, location= D:\

LOADED.exe: another WPF Application >> Build 32bit, .Net 4.5.1, location= D:\

I'm owner of both assembly (both application and thair sources)

Now, i want load the LOADED.exe [and its resources such as images dlls and...) as a Byte array to the memory and execute it, then remove LOADED.exe and its resources from hard disk.

In the first step i'm trying to just load the LOADED.exe file to memory and execute it (so i used a simple EXE without any resource in this step).

A)

Ok, i found this way for WinForm programes here:

var filePath = @"D:\LOADED.EXE";

if (File.Exists(filePath))
{
    try
    {

        // prepare to load the application into memory (using Assembly.Load)

        // read the bytes from the application exe file
        var fs = new FileStream(filePath, FileMode.Open);
        var br = new BinaryReader(fs);
        byte[] bin = br.ReadBytes(Convert.ToInt32(fs.Length));
        fs.Close();
        br.Close();

        // load the bytes into Assembly
        var asm = Assembly.Load(bin);
        // search for the Entry Point
        var method = asm.EntryPoint;
        if (method != null)
        {
            // create an istance of the Startup form Main method
            object o = asm.CreateInstance(method.Name);

            // invoke the application starting point
            Application.Current.ShutdownMode = System.Windows.ShutdownMode.OnExplicitShutdown;
            method.Invoke(o, null);
        }
        else
        {
            //show message: Impossible to launch the application 
        }
    }
    catch(Exception ex)
    {
        MessageBox.Show(ex.Message + "\n\r\n\r" + ex.InnerException + "\n\r\n\r" + "\n\r\n\r" + ex.Source);
        // exception throws .. something to do?
    }
}

I tried it inside LUNCHER.exe under a button then RUN... The result of handled exception:

Cannot create more than one System.Windows.Application instance in the same AppDomain.

OK!


B)

Then, i searched for a solution and some body has been said you must execute it in a new [different] AppDomain.

For example here is an answer: Dynamically Loaded Assembly - Settings & Communication

I tried it by the following codes under another button in the LUNCHER.exe:

private void Button_Click_1(object sender, RoutedEventArgs e)
{
    try
    {
        var filePath = string.Format("{0}{1}", Utility.ExePath, PART_PATH);
        AppDomain newappdomain = getAppDomainForAssembly(filePath, "LOADED.exe.domain");
        object loadedexe_object = getInstanceFromAppDomain(ref newappdomain, filePath);

        //If you know the method name to call...
        executeMethod(loadedexe_object.GetType(), "methodname", ref loadedexe_object, null);

        //or get entry point...
        executeMethod(loadedexe_object.GetType(),
            _asm_resolve(filePath).EntryPoint.Name, ref loadedexe_object, null);
    }
    catch (Exception ex)
    {
        var type = "";

        if (ex is ArgumentNullException)
        {
            type = "ArgumentNullException";
        }
        else if (ex is NotSupportedException)
        {
            type = "NotSupportedException";
        }
        else if (ex is AppDomainUnloadedException)
        {
            type = "AppDomainUnloadedException";
        }
        else if (ex is TypeLoadException)
        {
            type = "TypeLoadException";
        }
        else if (ex is MissingMethodException)
        {
            type = "MissingMethodException";
        }
        else if (ex is MethodAccessException)
        {
            type = "MethodAccessException";
        }
        else if (ex is BadImageFormatException)
        {
            type = "BadImageFormatException";
        }
        else if (ex is FileLoadException)
        {
            type = "FileLoadException";
        }

        MessageBox.Show(type + "\n\r\n\r" + ex.Message + "\n\r\n\r" + ex.InnerException + "\n\r\n\r" + ex.Source);
    }
}

private AppDomain getAppDomainForAssembly(string assemblypath, string appdomainname)
{
    //this._assembly_file = AssemblyFile;

    string _assembly_file_name = System.IO.Path.GetFileName(assemblypath);
    string _rootpath = System.IO.Path.GetDirectoryName(assemblypath);

    //this._assembly_class_name = AssemblyClassNameToInstance;
    AppDomainSetup _app_domain_info = new AppDomainSetup();
    _app_domain_info.ApplicationBase = _rootpath;
    _app_domain_info.PrivateBinPath = _rootpath;
    _app_domain_info.PrivateBinPathProbe = _rootpath;
    _app_domain_info.ConfigurationFile = _rootpath + @"LOADED.exe.config";  //Here put the path to the correct .assembly .config file

    AppDomain _app_domain = AppDomain.CreateDomain(appdomainname, null, _app_domain_info);

    return _app_domain;
}

protected System.Reflection.Assembly _asm_resolve(string assemblyFile)
{
    return System.Reflection.Assembly.LoadFrom(assemblyFile);
}

private object getInstanceFromAppDomain(ref AppDomain appDomain,
    string assemblyPath, string className = null)
{
    if (string.IsNullOrEmpty(className))
    {
        System.Reflection.Assembly assembly = _asm_resolve(assemblyPath);
        System.Reflection.MethodInfo method = assembly.EntryPoint;

        // Now my ERROR is in this line>>
        return appDomain.CreateInstanceFromAndUnwrap(assemblyPath, method.Name); 
    }
    else
    {
        return appDomain.CreateInstanceFromAndUnwrap(assemblyPath, className);
    }
}

Now my error is in this line:

OK!


C)

I searched again and found this one (Dynamically loaded Assembly not loading in new AppDomain):

// Provides a means of invoking an assembly in an isolated appdomain
public static class IsolatedInvoker
{
    // main Invoke method
    public static void Invoke(string assemblyFile, string typeName, string methodName, object[] parameters)
    {
        // resolve path
        assemblyFile = Path.Combine(Environment.CurrentDirectory, assemblyFile);
        Debug.Assert(assemblyFile != null);

        // get base path
        var appBasePath = Path.GetDirectoryName(assemblyFile);
        Debug.Assert(appBasePath != null);

        // change current directory
        var oldDirectory = Environment.CurrentDirectory;
        Environment.CurrentDirectory = appBasePath;
        try
        {
            // create new app domain
            var domain = AppDomain.CreateDomain(Guid.NewGuid().ToString(), null, appBasePath, null, false);
            try
            {
                // create instance
                var invoker = (InvokerHelper) domain.CreateInstanceFromAndUnwrap(Assembly.GetExecutingAssembly().Location, typeof(InvokerHelper).FullName);

                // invoke method
                var result = invoker.InvokeHelper(assemblyFile, typeName, methodName, parameters);

                // process result
                Debug.WriteLine(result);
            }
            finally
            {
                // unload app domain
                AppDomain.Unload(domain);
            }
        }
        finally
        {
            // revert current directory
            Environment.CurrentDirectory = oldDirectory;
        }
    }

    // This helper class is instantiated in an isolated app domain
    private class InvokerHelper : MarshalByRefObject
    {
        // This helper function is executed in an isolated app domain
        public object InvokeHelper(string assemblyFile, string typeName, string methodName, object[] parameters)
        {
            // create an instance of the target object
            var handle = Activator.CreateInstanceFrom(assemblyFile, typeName);

            // get the instance of the target object
            var instance = handle.Unwrap();

            // get the type of the target object
            var type = instance.GetType();

            // invoke the method
            var result = type.InvokeMember(methodName, BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance, null, instance, parameters);

            // success
            return result;
        }
    }
}

Then i called it by the following codes under another button in the LUNCHER.exe:

private void Button_Click_2(object sender, RoutedEventArgs e)
{
    var filePath = string.Format("{0}{1}", Utility.ExePath, PART_PATH);
    IsolatedInvoker.Invoke(filePath, "Main", "Main", new object[] {});
}

But i get same error like previous way **B**:

An unhandled exception of type 'System.TypeLoadException' occurred in Luncher.exe

Additional information: Could not load type 'Main' from assembly Loaded, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.


D)

Also i tested this way under another button in the LUNCHER.EXE:

private void Button_Click_3(object sender, RoutedEventArgs e)
{
    var filePath = @"D:\LOADED.exe";
    var dll = File.ReadAllBytes(filePath);
    var assembly = Assembly.Load(dll);

    var app = typeof (Application);

    var field = app.GetField("_resourceAssembly", BindingFlags.NonPublic | BindingFlags.Static);
    field.SetValue(null, assembly);

    //fix urihelper
    var helper = typeof (BaseUriHelper);
    var property = helper.GetProperty("ResourceAssembly", BindingFlags.NonPublic | BindingFlags.Static);
    property.SetValue(null, assembly, null);

    //---- Now my ERROR is in this line >>
    assembly.EntryPoint.Invoke(null, new object[] {});
}

And error in last line of code in Run Time:

An unhandled exception of type 'System.Reflection.TargetInvocationException' occurred in mscorlib.dll

Additional information: Exception has been thrown by the target of an invocation.


Finally:

I'm confused!

  • what is my mistake in all above methods?
  • why all the ways end with an ERROR!!!

Please help me with simple descriptions and some codes because I'm beginner in this scenarios (loading assembly, create AppDomain and...) but i need to load a WPF application to the memory and show its window and then delete it's file from H.D.D while it is running in the memory.

解决方案

  1. Create a shared assembly. This will be loaded into both AppDomains ("Launcher" Domain, "Loaded" Domain) and serve as an entry point of our "Loaded" AppDomain:

    Add a new project > Class library > Name: ChildDomainLoader

    Add the following references to the new project: System.Xaml, WindowsBase, PresentationFramework

    Add a project reference for ChildDomainLoader in your Launcher project. The Loaded project doesn't have to be modified.

  2. Add some code to the shared assembly. We need a MarshalByRefObject that can be called cross-domain and loads our child assembly. Let's call it Runner:

    using System;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Windows;
    
    namespace ChildDomainLoader
    {
        public class Runner : MarshalByRefObject
        {
            public static AppDomain RunInOtherDomain(string assemblyPath)
            {
                var ownType = typeof (Runner);
                string ownAssemblyName = ownType.Assembly.FullName;
    
                // Create a new AppDomain and load our assembly in there.
                var childDomain = AppDomain.CreateDomain(Guid.NewGuid().ToString());
                childDomain.Load(ownAssemblyName);
    
                // Call Run() in other AppDomain.
                var runner = (Runner) childDomain.CreateInstanceAndUnwrap(ownAssemblyName, ownType.FullName);
                runner.Run(assemblyPath);
    
                return childDomain;
            }
    
            public void Run(string assemblyPath)
            {
                // We load the assembly as byte array.
                var otherAssemblyBytes = File.ReadAllBytes(assemblyPath);
                var assembly = AppDomain.CurrentDomain.Load(otherAssemblyBytes);
    
                AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
                {
                    throw new NotImplementedException("Probably need to do some work here if you depend on other assemblies.");
                };
    
                // Set the assembly as ResourceAssembly, as WPF will be confused otherwise.
                Application.ResourceAssembly = assembly;
    
                // Search for the App class.
                var app = assembly
                    .GetExportedTypes()
                    .Single(t => typeof(Application).IsAssignableFrom(t));
    
                // Invoke its Main method.
                MethodInfo main = app.GetMethod("Main", BindingFlags.Static | BindingFlags.Public);
                main.Invoke(null, null);
            }
        }
    }
    

  3. Use it. Call Runner.RunInOtherDomain from your Launcher application.

    var assemblyPath = "path to your loaded.exe";
    ChildDomainLoader.Runner.RunInOtherDomain(assemblyPath);
    File.Delete(assemblyPath);
    

这篇关于从另一个 WPF 应用程序加载 WPF 应用程序程序集,得到错误:无法在同一个 AppDomain 中创建多个 System.Windows.Application 实例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆