实现程序的热升级 - 方式二

前言


前一篇我们说到了如何利用应用程序域的相关技术实现热升级的目的。下面我来介绍另一种场景,如下图所示:

主程序仅提供作为MdiContainer的窗体框架,所有的功能都以单独的子窗体形式加载。每个子窗体对应的是一个单独的功能模块(dll文件)。

比如管理公司结构的时候,员工管理模块和部门管理模块就分别以单独的dll文件的形式加载到主窗体中,我们今天要做的就是对这样一个单独的子窗体功能模块进行升级。

 

评估方式一


我们先评估一下利用上一篇所说创建新应用程序域的方式能否实现。

首先,主程序Mdi窗体在默认应用程序域中启动,然后要显示某个功能界面的时候,通过创建应用程序域加载对应的功能模块。OK,这一步能够实现,如下效果:

不过,还没有达到我们想要的效果,我们想实现的是功能模块1这个窗体是作为应用程序框架这个窗体的子窗体显示的。还要继续做。

要达到上述效果,只要设置功能模块1的MdiParent属性为主程序窗体就行了。

不过问题就在这了,主窗体和子窗体分别属于不同的应用程序域!上一篇我们介绍过,不同应用程序域中的成员是密封的,任何程序域中都不能直接访问其他程序域中的成员!只能使用按引用封送或按值封送的技术,才能间接地访问。显然窗体对象不是值类型,如果要按引用封送的话,不管是把主窗体封送进子窗体所在程序域,还是把子窗体封送进入主窗体所在程序域,都必须保证一点,窗体类型必须继承自MarshalByRefObject,查看Form类的继承关系:

public class Form : ContainerControl
public class ContainerControl : ScrollableControl, IContainerControl
public class ScrollableControl : Control, IComponent, IDisposable
public class Control : Component, IDropTarget, ISynchronizeInvoke, IWin32Window, IBindableComponent, IComponent, IDisposable
public class Component : MarshalByRefObject, IComponent, IDisposable

最终“惊喜地”发现,Form是继承自MarshalByRefObject,那么就可以使用按引用封送的技术了?

可是,当我使用MemberwiseClone方法将主窗体对象封成MarshalByRefObject发送到子窗体所在程序域,并设置子窗体的MdiParent属性时,

运行却产生如下异常:

看来即使封送Form类,也只能传送诸如Name, Text之类的简单属性,像ControlCollection这样的并没有做可序列化实现。

 

另辟蹊径


既然使用新应用程序域无法实现,那就只能看在同一个应用程序域中能不能有办法实现。

在上一篇中也说过,无法在程序运行期间更新文件的根本原因,就是运行中的程序“霸占”着文件的句柄,直到卸载程序域或者退出进程的时候才被释放。那么只要在加载完模块后,同时使用某种方式释放句柄应该就可以了!

如果不需要使用AppDomain,那么一般我们加载程序集使用Assembly的相关静态方法,调用Assembly方法生成的对象就处在调用所在的域中,这样子窗体和主窗体对象处于同一个程序域中,也就很方便地设置子窗体的MdiParent属性了。

关于加载程序集的相关主要方法(略去重载方法)如下:

public static Assembly Load(AssemblyName assemblyRef);
public static Assembly Load(byte[] rawAssembly);
public static Assembly Load(string assemblyString);
public static Assembly LoadFile(string path);
public static Assembly LoadFrom(string assemblyFile);

观察这些方法发现,不管是assemblyRef,还是assemblyString, path, assemblyFile这些都跟文件名有关,通过调用测试发现,这些方法加载完文件后并没有释放文件句柄。

唯独方法

public static Assembly Load(byte[] rawAssembly);

加载的是一组字节流!调用这个方法,需要先把文件读入内存字节流,然后再从这个字节流加载,已经跟硬盘上的文件没有关系了,也就是说当文件被读入内存字节流中后,句柄会被释放,这个不就是我们希望的么!

 

具体实现


OK,既然找到了这样一个方法,那么我们建一个解决方案来验证一下。

如下所示:

其中,Modules.xml作为配置文件,用来描述主程序需要加载的功能模块信息

<?xml version="1.0" encoding="utf-8" ?>
<Modules>
  <Module name="Module1" file="Modules\\Module1.dll" interface_name="Module1.Loader" />
</Modules>

功能模块中有个简单的窗体

点击显示后,会弹出该模块的版本号,后面我们以这个消息判断是否升级成功。

 

主程序启动后,首先读取Modules.xml文件,在工具栏中生成对应的按钮,表示已发现对应功能模块

当点击按钮时加载并显示子窗体:

复制代码
void ctl_Click(object sender, EventArgs e)
{
    string name = ((ToolStripButton)sender).Text;

    Form frm = ModuleManager.LoadModule(name);
    frm.MdiParent = this;
    frm.Show();
}
复制代码
复制代码
public static Form LoadModule(string moduleName)
{
    if (!_modules.ContainsKey(moduleName))
        return null;

    ModuleInfo mInfo = _modules[moduleName];
    if (mInfo.Frm_Module != null && !mInfo.Frm_Module.IsDisposed)
        return mInfo.Frm_Module;

    byte[] bFile = File.ReadAllBytes(mInfo.File);
    Assembly amy = Assembly.Load(bFile);
    ILoader loader = (ILoader)amy.CreateInstance(mInfo.Interface_Name);
    Form frm = loader.LoadModule();
    mInfo.Frm_Module = frm;
    return frm;
}
复制代码

通过File.ReadAllBytes方法将dll文件读入字节流,这时候文件句柄就已经被释放了,也就可以在运行中进行升级操作。

其中,ModuleInfo保存Modules.xml中读取的模块信息。

复制代码
class ModuleInfo
{
    string name;
    /// <summary>
    /// 模块唯一标识,也作为应用程序域的名称
    /// </summary>
    public string Name
    {
        get { return name; }
    }

    string file;
    /// <summary>
    /// 模块对应的文件名
    /// </summary>
    public string File
    {
        get { return file; }
        set { file = value; }
    }

    string interface_name;
    /// <summary>
    /// 用于程序框架加载模块的入口
    /// </summary>
    public string Interface_Name
    {
        get { return interface_name; }
        set { interface_name = value; }
    }

    /// <summary>
    /// 功能模块窗体
    /// </summary>
    public Form Frm_Module;

    public ModuleInfo(string name, string file, string interface_name)
    {
        this.name = name;
        this.file = file;
        this.interface_name = interface_name;
    }
}
复制代码

逻辑很简单,我们直接运行看一下

如何判断这时候磁盘上的文件和正在运行的子窗体没有关系呢,为了便于演示,我在功能模块1的窗体上再加一个按钮用于删除相应的dll文件。

再次运行,点击删除

删除成功了!说明没有问题,句柄已经被释放,这时候我们重新生成一下Module1,把版本修改为1.0.0.1

[assembly: AssemblyVersion("1.0.0.1")]
[assembly: AssemblyFileVersion("1.0.0.1")]

关闭子窗体,(只是关闭子窗体,并不关闭主窗体),再次点击Module1按钮

加载成功,并且版本已经更改为最新的1.0.0.1!

至此,整个程序升级的工作完成。

 

完整的解决方案 点击下载

:我在blog中使用的代码都是为了演示使用的精简的代码,并不适合拿来直接使用,只是希望大家能理解解决的方法,再以自己理解的方式实现。


转自:http://www.cnblogs.com/houkui/p/4256224.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
部署是指在系统运行过程中对软件进行更新或修改而无需停机重启。这个概念常见于服务器端应用程序的开发和部署过程中。下面是一些使用部署的想法: 1. 动态代码加载:通过将代码以模块的形式加载到应用程序中,可以在不停止应用程序的情况下更新特定模块的代码。这样可以实现模块级别的部署,而不会影响整个应用程序。 2. 插件系统:设计一个支持插件的系统架构,通过在运行时动态加载和卸载插件,实现对系统功能的灵活扩展和升级。这样,当添加或更新插件时,不需要停止整个系统。 3. 动态配置更新:将应用程序的配置信息存储在外部配置文件中,并实现在运行时动态加载和更新配置。当配置文件发生变化时,应用程序可以自动重新加载配置,以生效新的设置,而无需停止和重启。 4. 补丁技术:使用补丁技术可以在不停止应用程序的情况下修复和更新已经发布的软件版本。补丁技术通过对已有的字节码进行动态修改,实现对已发布版本的补丁安装。 5. 容器化技术:使用容器化技术如Docker,可以将应用程序打包成镜像,并在运行时动态部署和更新。通过管理容器的生命周期,可以实现部署的效果,而不需要停止和重新构建整个应用程序。 以上是一些使用部署的想法,具体的实现方式会根据应用程序的需求和技术栈而有所不同。不同技术栈和开发框架都有自己特定的部署方案,需要根据实际情况选择合适的方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值