【问题标题】:Self-updating WPF application, launcher process load WPF app via reflection自更新 WPF 应用程序,启动器进程通过反射加载 WPF 应用程序
【发布时间】:2018-11-05 03:36:57
【问题描述】:

在一个 Winforms 应用程序中,我们开发了一个自我更新的应用程序启动器,我正在尝试用 WPF 来模仿它,但遇到了一些问题。这与 Winforms 的工作方式:

  • 启动器进程(不参考主应用程序)将检查更新的库并根据需要下载
  • Launcher 然后将从 STA 线程加载程序集 (Assembly.Load),然后通过反射,在该程序集中调用 Init 方法(在启动器充当启动屏幕并显示进度时执行一堆初始化逻辑)
    • Init 完成后,Laucher 将通过加载程序集中的反射调用 Handoff 方法,该方法将使用新的 MainForm 创建一个新的 ApplicationContext,然后调用 Application.Run(newAppContext)。
    • 启动器将关闭其窗口

我试图在 WPF 中模仿相同的方法,但我遇到了问题:

  • 似乎没有我可以将 MainForm 转移到的 ApplicationContext 概念
  • 我不确定如何处理 App.xaml/resources,因为我无法在应用程序 dll 中放置“第二个”App.xaml,因此我不确定如何处理资源/样式的加载李>
  • 当我在通过反射调用的“Handoff”调用中显示一个新的 MainWindow 时,窗口会短暂打开,然后消失

感谢有关如何在 Wpf 应用程序中实现所需行为的任​​何指导。

【问题讨论】:

  • 为什么不直接使用 ClickOnce?任务完成。无论如何,有什么代码要显示吗?
  • 除了 ClickOne,我之前的做法比您建议的更容易,只是制作一个单独的应用程序进行更新。让您的应用程序检查,Process.Start您的更新程序,关闭应用程序,更新程序执行操作,然后重新启动您的应用程序。这相当简单、直接,并且无法覆盖正在运行/正在使用的 .exe 或 .dll。
  • System.Windows.Application.Run(mainWindow),这是一个实例方法
  • 我想问一下,为什么不使用 Process.Start 将其作为普通可执行文件启动并为其提供命令行参数以进行切换?您的另一个选择是使用 AppDomain ExecuteAssembly 函数之一。
  • @JoelLucsy 请参阅:Launcher 将从 STA 线程加载程序集 (Assembly.Load),然后通过反射,在该程序集中调用 Init 方法(在启动器时执行一堆初始化逻辑正在充当启动屏幕并显示进度) - 启动器在初始化时查询加载的程序集并接收状态更新,因此 Process.Start 将不起作用

标签: c# wpf launcher


【解决方案1】:

WPF 应用派生自 System.Windows.Application 的基类。 VS 使用的基本模板创建了 App.xaml 和 App.xaml.cs。这个类有一个静态入口点

public static void Main()

这是我用的:

var type = yourloadedassembly.GetType( "YourNamespace.App" );
type.InvokeMember( "Main", BindingFlags::Public | BindingFlags::Static | BindingFlags::InvokeMethod, null, null, null );

【讨论】:

  • 当我尝试得到:InvalidOperationException: 不能在同一个 AppDomain 中创建多个 System.Windows.Application 实例。
  • 您的更新程序是 WPF 应用程序吗?如果是这样,您可能需要从单独的 AppDomain 运行它。 WPF 喜欢在自己的空间中。
  • 是的,是的 - 我正在尝试,但仍然遇到同样的错误,不确定我做错了什么: var domain = AppDomain.CreateDomain("test", null, new AppDomainSetup { ApplicationBase = Environment.CurrentDirectory}); var asm = domain.Load("MyApp); var types = asm.GetTypes(); ... 获取我的类型 type.InvokeMember("Main", BindingFlags.Public | BindingFlags.Static | BindingFlags.InvokeMethod, null, null,空);
  • 如果我尝试直接在 AppDomain 上调用: domain.DoCallBack(() => type.InvokeMember("Main", BindingFlags.Public | BindingFlags.Static | BindingFlags.InvokeMethod, null, null , 空值));我得到:System.Runtime.Serialization.SerializationException: 'Type 'StartUpManager+c__DisplayClass9_0' in assembly 'Launcher, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' is notmarked as serializable.'
  • AppDomain 非常挑剔。将程序集加载到您自己的域中非常容易。我怀疑当您通过 asm.GetTypes() 获取类型时,您实际上是将该程序集加载到您当前的域中,并且它正试图从那里运行它。我缺乏在 AppDomains 方面的专业知识。我确实知道您调用的任何类都需要可序列化才能跨越域边界,并且我使用 CreateInstanceFromAndUnwrap 来获取该类并在其上调用函数。
【解决方案2】:

对于任何有趣的人来说,解决这个问题其实很简单:

  • 创建 Wpf 应用程序(启动器)
  • 创建一个“应用程序”程序集(可以是一个 dll)来承载您的应用程序特定代码的入口点
  • 让启动器动态加载应用程序程序集 (Assembly.Load)
  • 在您的应用程序集中,有一些静态入口点,可以通过启动器的反射调用。
  • 当启动器通过反射调用入口点方法时,添加您的资源并从应用程序集中新建一个 MainWindow 以分配给 Application.Current.MainWindow:

       Application.Current.Resources = new ResourceDictionary() {Source = new Uri("pack://application:,,,/MyApp.UI.Styling;component/Common.xaml")};
       Application.Current.MainWindow = new MainWindow();
       Application.Current.MainWindow.Show();
    
  • 返回启动器,关闭()启动器窗口

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-22
    • 2016-10-15
    相关资源
    最近更新 更多