从另一个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

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

问题描述

场景:

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

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

我是owner of both assembly(应用程序和其他来源)

现在,我想将LOADED.exe [及其图像dll和...之类的资源)作为Byte array加载到内存并执行它,然后从硬盘上删除LOADED.exe及其资源./p>

在第一步中,我尝试just load the LOADED.exe file to memory and execute it(因此在此步骤中使用了simple EXE without any resource).

A)

好吧,我发现WinForm程序使用这种方式此处:

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

我在LUNCHER.exe里面的一个按钮下尝试了一下,然后运行...处理异常的结果:

不能在同一AppDomain中创建多个System.Windows.Application实例.

好!


B)

然后,我搜索了一个解决方案,有人说必须在new [different] AppDomain中执行它.

例如,这是一个答案:动态加载的程序集-设置&交流

我通过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);
    }
}

现在我的错误在这一行:

好!


C)

我再次搜索并找到了这个(动态加载的程序集不在新的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;
        }
    }
}

然后我通过以下代码在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[] {});
}

但是我遇到了与以前的方式**B**相同的错误:

"System.TypeLoadException"类型的未处理异常发生在 Luncher.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 (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[] {});
}

运行时代码的最后一行错误:

类型的未处理异常 mscorlib.dll中发生了" System.Reflection.TargetInvocationException "

其他信息:目标服务器已抛出异常 调用.


最后:

我很困惑!

  • 以上所有方法中我都犯什么错误?
  • 为什么所有方法都以错误结尾!!!

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

解决方案

  1. 创建共享程序集.这将同时加载到两个AppDomain("Launcher"域,"Loaded"域)中,并作为我们"Loaded" AppDomain的入口点:

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

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

    在您的Launcher项目中为ChildDomainLoader添加项目参考. Loaded项目无需修改.

  2. 向共享程序集中添加一些代码.我们需要一个可以称为跨域的MarshalByRefObject并加载我们的子程序集.我们称之为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. 使用它.从启动器应用程序中调用Runner.RunInOtherDomain.

    var assemblyPath = "path to your loaded.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天全站免登陆