从另一个 WPF 应用程序加载 WPF 应用程序程序集,出现错误:无法在同一个 AppDomain 中创建多个 System.Windows.Application 实例

Posted

技术标签:

【中文标题】从另一个 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 【发布时间】:2014-12-06 04:06:42 【问题描述】:

情景:

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 及其资源。

在第一步中我尝试just load the LOADED.exe file to memory and execute it(所以我在这一步中使用了simple EXE without any resource)。

A)

好的,我为 WinForm 程序找到了这种方式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?
    

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

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

好的!


B)

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

例如这里是一个答案:Dynamically Loaded Assembly - Settings & Communication

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

private void Button_Click_1(object sender, RoutedEventArgs e)

    try
    
        var filePath = string.Format("01", 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)

我再次搜索,找到了这个(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;
        
    

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

private void Button_Click_2(object sender, RoutedEventArgs e)

    var filePath = string.Format("01", Utility.ExePath, PART_PATH);
    IsolatedInvoker.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 (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

附加信息:异常已被 调用。


最后:

我很困惑!

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

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

【问题讨论】:

我建议缩小您的问题范围(目前它看起来更像是应用程序开发的请求)并指定导致问题的代码的基本部分。最好的问候, 另外,您可以考虑使用 Process.Start() 方法从启动器应用程序启动另一个应用程序,如下所述:msdn.microsoft.com/en-us/library/… 我知道 process.start 但我想在运行后删除 exe 文件!!!这个问题有一些样本,很长,但我的要求很简单,答案不会很大! 【参考方案1】:

    创建一个共享程序集。这将被加载到两个 AppDomain(“Launcher”域,“Loaded”域)中,并作为我们“Loaded”AppDomain 的入口点:

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

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

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

    向共享程序集添加一些代码。 我们需要一个可以跨域调用并加载我们的子程序集的 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);
            
        
    
    

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

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

【讨论】:

非常感谢您:D ...您的指示效果很好!当然,在我关闭LOADED.exe 后文件会被删除,因为 LUNCHER 和 LOADER 等待结束 LOADED 继续。 所以我在 RunInOtherDomain & Run 方法中添加了一个 bool 参数来检测我们是否也必须删除程序集,然后我在加载程序集后将此代码添加到 RUN:if (forceDeleteAssembly && File.Exists(assemblyPath)) File.Delete(assemblyPath); 现在一切正常 :)

以上是关于从另一个 WPF 应用程序加载 WPF 应用程序程序集,出现错误:无法在同一个 AppDomain 中创建多个 System.Windows.Application 实例的主要内容,如果未能解决你的问题,请参考以下文章

如何从另一个应用程序关闭 WPF 系统托盘应用程序?

WPF怎么能跨进程通信

从另一个项目的可执行文件启动一个项目的可执行文件

从另一个窗口访问和更改 WPF 中的控件

WPF 应用程序消息循环和 PostThreadMessage

如何将调色板图像从另一个线程生成到 WPF UI 线程?