Skip to content

dotnet 桌面端基于 AppHost 的配置式自动切换更新后的应用程序路径

Updated: at 08:22,Created: at 00:34

在桌面应用端开发的时候,应用更新有很多实现方式,本文来告诉大家一个基于 dotnet core 或 .NET 5 的 AppHost 方式的配置式软件更新方法。这个方法的特点是入口的 Exe 文件可以固定不动,每次更新的时候只需要更新某个配置文件的路径,即可实现在应用启动的时候,读取配置文件的路径加载某个版本的软件跑起来。而且本文的方法不需要额外其他一个独立的启动进程,而是入口文件进程就是最终的 dotnet 进程,可以做到固定到任务栏等不会在自动更新的时候丢失

配置式的软件更新指的是有一个入口进程,在入口进程里面读取配置文件的内容,根据配置文件启动不同的版本的实际运行的软件。配置式的优势在于热更新方便,静默更新方便,支持软件在运行的过程中,后台下载更新的版本,在下载完成之后,通过修改配置文件而在下次启动的时候自动更新软件。通过配置式的更新,可以玩出如增量更新,二进制差分等。更进一步还支持自动回滚技术,在新版本打开失败之后,可以快速自动回滚到旧版本,只需要保持一个旧版本不删除即可

在 dotnet core 之前,咱使用 .NET Framework 也可以做到配置式的软件更新。有两个实现方法,第一个方法是入口进程是一个独立的进程,而实际运行的软件又是另一个进程,使用两个文件的方式实现,入口进程对应的文件固定路径,而实际运行的进程放在版本号文件夹里面。第二个做法是通过修改 app.config 文件的文件寻址方法修改加载的 Dll 路径,让 Dll 作为实际的入口

以上的第一个方法存在的问题是两个进程会影响启动性能,同时两个进程会影响用户固定到任务栏动作。因为固定到任务栏的是某个特定版本的软件,而在自动更新之后将会让任务栏固定的软件路径失效

在 dotnet core 上,咱的 dotnet 程序可以被 Native 的应用跑起来 CLR 引擎,而在没有运行 CLR 引擎之前,这个 Native 程序自身可以独立运行。详细请看 dotnet core 应用是如何跑起来的 通过AppHost理解运行过程

因为这个 Native 程序可以自主运行,因此这个 Native 程序特别适合用来做应用程序的入口。当然了,默认的 dotnet core 应用也就使用了这个 Native 的程序作为默认的入口,只是这个默认入口硬编码了 App 的路径而已。咱可以自己去实现这个 Native 程序,只需要找到合适的路径,包括咱的应用软件路径以及 CLR 引擎路径,将 CLR 引擎运行,然后加载咱的应用就可以了。如何编写一个 dotnet 入口程序,细节请看 dotnet core 应用是如何跑起来的 通过自己写一个 dotnet host 理解运行过程

这样做的一个优势是,使用 Native 作为入口程序,不需要开启两个进程。因为 dotnet 应用将会在这个 Native 程序里面跑起来,只是 Native 应用将会根据配置加载不同的版本的 dotnet 应用而已。相对来说这个方式的启动速度会比较快,而且不会破坏用户将应用程序固定到标题栏,也不需要去更新快捷方式和注册表的路径。多次更新时入口程序的路径没有变更,有变更的是 dotnet 应用程序,不同的版本的 dotnet 应用程序放在不同的版本号文件夹里面,同时更改配置文件里面的路径即可

本文提供的方法实现了以下功能。可以将任意路径的某个文件当成配置文件,从配置文件里面读取 CLR 引擎路径,加载 CLR 引擎。从配置文件里面读取业务端 dotnet 入口 DLL 文件进行加载,运行 dotnet 程序。可以自定义自己的 dotnet 框架文件夹,也就是 CLR 引擎所在的文件夹。可以自己定义业务端的 dotnet 入口文件所在文件夹。从而实现了可以在不更改应用程序,只修改配置文件下,通过修改配置文件实现修改实际被加载的 dotnet 程序的路径而实现自动更新的功能。同时可以让团队内多个不同的软件共用相同的特别的自定义 dotnet 框架,可以将自己的 dotnet 框架文件夹存放到自定义的文件夹里面,从而自己管理 CLR 引擎版本。可以让入口程序文件在多个版本更新时,保持入口程序文件不变。可以很好支持后台静默更新

以下是实现的细节。为了支持配置式的方式运行,咱需要让自己编写的 Native 程序支持读取配置文件,在配置文件里面里面获取业务端的应用路径,以及 CLR 引擎路径。我在本文的例子里面,入口进程的文件存放布局如下。在入口进程所在的文件夹里面存放了 App.txt 文件,这个文件里面就包含了配置业务端应用的路径以及 CLR 引擎的路径。本文例子里面在入口进程运行的时候,将会去读取 App.txt 文件,从配置里面读取这两个路径,然后分别进行加载。本文的例子代码里面固定了 App.txt 文件的路径一定是如上文描述的文件布局,但实际上大家可以根据自己的需求修改此路径和文件名

我的 App.txt 的设计是这个文件里面分为两行,第一行的内容就是业务端应用的入口 DLL 文件的路径,第二行就是 CLR 引擎的路径。以上的 业务端应用的入口 DLL 文件的路径指的是咱的业务端 C# 代码里面的 Main 函数所在 DLL 路径。而 CLR 引擎路径指的是 CoreClr.dll 所在的文件夹路径,根据 dotnet core 应用是如何跑起来的 通过AppHost理解运行过程 可以了解,只需要在 Native 应用里面加载 CoreClr.dll 库,就可以跑起来 dotnet 应用

我在例子应用里面实现的功能是读 App.txt 文件的内容,支持在 App.txt 里面存放相对路径和绝对路径,然后进行加载运行。这个例子的代码放在 github 还请大家自行下载使用。大概只需要加上图标和程序清单,以及设置为窗口启动,那么就可以放在你的项目里面跑起来。但依然需要小心的是,这仅是一个例子的代码而已,还请大家理解这个例子里面的实现逻辑再进行商业化发布

这个 Native 入口程序的设计非常简单,在以上例子里面没有加上在某个版本运行失败的时候自动回滚版本等逻辑,这些就看大家的需求,自行添加了。如果想要让自动更新程序不需要有高的权限就能执行,那么请更改入口程序读取的配置文件的路径,如更改到 AppData 文件夹里面。而且将下载的 dotnet 程序也放到 AppData 文件夹下面。那么此时的自动更新逻辑,只需要将文件下载存放到 AppData 文件夹里面,然后更改 AppData 文件夹里面的 App.txt 配置文件的路径,完全不需要用到需要权限的应用程序文件夹,因此也就可以使用用户权限在后台更新

为什么我会考虑将 CLR 引擎和业务端的应用分为不同的文件夹?其实根据以上的配置文件的写法,可以将 CLR 引擎所在的文件夹和业务端的应用写相同的文件夹。但是在考虑到一个团队里面如果有很多个软件,那么多个软件之前共用相同的 CLR 引擎,也许可以提升用户端的性能,如减少一点内存的占用。那为什么不使用默认的公共的 CLR 引擎文件夹路径?原因是担心这个路径太多人知道,被奇怪的软件玩坏了。仅仅只是这个原因而已

使用此方法存在的坑点在于,如果需要自动更新,更新入口程序的应用清单,那么依然需要更改此入口 Native 程序文件。好在更新清单,对于大部分团队来说,次数不是很多。而且咱还有一个黑科技,是在 Windows 下,在 Exe 文件被执行的过程中,是可以移动或命名文件的,此时不会存在问题,因此自动更新的时候,可以通过先重命名再替换文件的方式实现更新入口文件。详细请看 Windows 上的应用程序在运行期间可以给自己改名(可以做 OTA 自我更新) - walterlv


知识共享许可协议

原文链接: http://blog.lindexi.com/post/dotnet-%E6%A1%8C%E9%9D%A2%E7%AB%AF%E5%9F%BA%E4%BA%8E-AppHost-%E7%9A%84%E9%85%8D%E7%BD%AE%E5%BC%8F%E8%87%AA%E5%8A%A8%E5%88%87%E6%8D%A2%E6%9B%B4%E6%96%B0%E5%90%8E%E7%9A%84%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F%E8%B7%AF%E5%BE%84

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。 欢迎转载、使用、重新发布,但务必保留文章署名 林德熙 (包含链接: https://blog.lindexi.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我 联系