自动更新程序



  .NET框架组件和Windows有一些有趣的API,它们能够建立通过网络自动更新的应用程序。像Windows一样把应用程序编写为自动更新有很多好处,包括方便了用户,减轻网络管理员的维护工作量。自动更新需要注意一些因素,例如发现、安全和文件更替。本文使用了BITS API和一些.NET框架组件的特性,使应用程序可以像Windows一样自动更新。

  我喜欢Windows更新特性。我的计算机开启后,85%的时间连接着Internet,象很多人一样,我并没有那么多时间使用网络。Windows XP利用未使用的带宽来比较网络上可用的最新服务包和补丁与本机安装的补丁包,如果发现需要更新,就在后台下载它们,下载完成后它提示有新的内容需要安装。

  如果我有选择,客户端的每个应用程序都应该允许自动更新。如果你要应用程序自动更新,必须编写代码来处理发现、下载、安全和替换。

  为了处理实际的下载,我将使用Windows的后台智能传输服务(Background Intelligent Transfer Service,BITS)特性。我将使用.NET框架组件的特性来解决自动更新应用程序的安全和更新问题。

  困难

  为了查找远程服务器上的更新,应用程序必须有查询网络的途径,这需要网络编程、简单的应用程序与服务器通讯的协议。这将在后面的"发现"节中讲到。

  下一步是下载。下载看起来不需要考虑联网的问题,但要考虑下载用户请求的文件,以及在没有用户同意时下载大文件。友好的自动更新应用程序将使用剩余的带宽下载更新。这听起来简单,但却是一个技术难题,幸运的是已经有了解决方法。

  安全也许是最关键的考虑因素。考虑一下Windows更新特性,它的主要目的是获取安全补丁,想象一下如果Windows更新本身不能验证是否安装了安全的代码。很明显任何从Internet下载并执行代码的应用程序必须有最高的安全级。因此我将讨论怎样使自动更新的应用程序更安全。

  最后的考虑因素是使用新版应用程序更换原应用程序的过程。这个问题比较有趣,因为它要求代码运行时将自己从系统删除,有多种办法可以实现该功能。

  BITS基础

  BITS是一个新的Windows文件传输特性,它通过HTTP异步从远程服务器下载文件。BITS可以使用专门的空闲带宽管理多个用户的多个下载。尽管BITS的使用不限于自动更新应用程序,但是它是Windows更新使用的低层API。由于它对于任何应用程序都是可用的,因而用于做许多实际的工作,包括建立自动更新的应用程序。
以下是基本的想法。应用程序请求BITS管理文件的下载。BITS将工作添加到它的队列并将它与应用程序运行的用户环境关联。一旦用户登录,BITS就使用空闲带宽通过网络慢慢下载文件。实际上BITS技术的代码名称是Drizzle,它描述BITS做什么。

  这是怎么实现的呢?这项技术相当复杂。首先,BITS的实现方式与维护按优先级(前台、高、正常、低)队列组织的工作集的Windows服务一样。相同优先级的工作按时间片给定五分钟带宽。队列中一旦没有工作了,就检查下一优先级队列的工作。

  前台队列中的工作使用尽可能大的网络带宽,由于这个原因前台优先级只用于响应用户请求的代码。其它的优先级--高、正常和低--都是后台优先级,它们只利用空闲的网络带宽。

  为了获得后台特性,BITS监视网络数据包,并不处理自己的包。剩余的包用于计算带宽的活动负载。BITS利用活动负载信息与连接速度和一些静态信息来决定是否继续下载文件,或者为了提高活动用户的流量停止。由于这个原因,用户不会遭遇带宽问题。

  对于BITS来说一旦注意到就停止工作的能力非常重要。在很多情况下BITS在下载了文件的一部分后就要放弃网络,甚至连接也一起丢失了。文件下载的部分被保存了,但是当BITS再次使用网络时它从断点开始。恢复的能力是有效果的。

  BITS用于从HTTP服务器传输文件。服务器必须与HTTP 1.1兼容,或者至少支持在GET方法中包含Range头,这是因为BITS需要请求文件的一部分。此外,下载的内容必须是静态内容,例如标记文件、代码文件、位图或声音。包含Range头的请求在请求动态内容如CGI、ISAPI或ASP.NET时不做任何操作。

  目前BITS有两种版本:1.0和1.5。BITS 1.0随Windows XP发布,有以下特性:可中断的文件下载、下载优先级、可选择的工作完成通知和错误情况、可选择使用对话框或者其它UI元素进行过程通知。BITS 1.5 与Windows .NET Server一起发布,除了有BITS 1.0的特性外,还有可中断的文件上载以及使用Basic、 Digest、 NTLM、 Negotiate(Kerberos) 或Passport认证连接,它与Windows 2000以上版本兼容

==============

BITS、 COM和可管理代码

  BITS API是作为COM对象实现的,到目前为止还没有.NET框架组件版本的API,幸运的是BITS API很直接并易于使用。本文的例程用C#编写,如果使用C++,BITS代码要使用下面的代码开始:

IBackgroundCopyManager* pBCM = NULL;

hr = CoCreateInstance(__uuidof(BackgroundCopyManager), NULL,
CLSCTX_LOCAL_SERVER, __uuidof(IBackgroundCopyManager),
(void**) &pBCM);
if (SUCCEEDED(hr)) {
// 使用pBCM接口指针
}

  C#使用new关键字等效地建立BackgroundCopyManager对象,接着通过计算获取一个IBackgroundCopyManager接口的引用,而不采用调用CoCreateInstance或者 QueryInterface等方法。下面的代码获取了IBackgroundCopyManager接口:

IBackgroundCopyManager bcm = null;
//建立BITS对象
bcm = (IBackgroundCopyManager)new BackgroundCopyManager();

  该代码很简单,可能使人误解,因为还需要做很多工作要做,要将可管理的BackgroundCopyManager和IBackgroundCopyManager类型与下层COM对象和接口分别对应地联系起来。.NET框架组件通过RCW管理与COM对象的交互操作。要使用RCW关联一个可管理的类型,你必须使用属性。图1中的代码显示了怎样声明BackgroundCopyManager类和IBackgroundCopyManager接口,这样它们就描述了BITS COM对象。

[GuidAttribute("4991D34B-80A1-4291-83B6-3328366B9097")]
[ClassInterfaceAttribute(ClassInterfaceType.None)]
[ComImportAttribute()]
class BackgroundCopyManager{}

[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
[GuidAttribute("5CE34C0D-0DC9-4C1F-897C-DAA1B78CEE7C")]
[ComImportAttribute()]
interface IBackgroundCopyManager {
void CreateJob(
[MarshalAs(UnmanagedType.LPWStr)] string DisplayName,
BG_JOB_TYPE Type, out Guid pJobId,
[MarshalAs(UnmanagedType.Interface)]
out IBackgroundCopyJob ppJob);

void GetJob(ref Guid jobID,
[MarshalAs(UnmanagedType.Interface)]
out IBackgroundCopyJob ppJob);

void EnumJobs(uint dwFlags,
[MarshalAs(UnmanagedType.Interface)]
out IEnumBackgroundCopyJobs ppenum);

void GetErrorDescription(
[MarshalAs(UnmanagedType.Error)] int hResult, uint LanguageId,
[MarshalAs(UnmanagedType.LPWStr)]
out string pErrorDescription);
}

图1.定义RCW类型

  图1中的代码属性(ComImportAttribute、 GuidAttribute、MarshalAsAttribute等等)很难以理解,以至于实际代码看起来不清楚。实际上这不是真实的代码。在通用语言运行时(CLR)调用的元数据形式中接口和类定义仅仅是一些排列整齐的占位符。

  代码例子中的InteropBits.cs文件为C#提供了与BITS API使用所有接口、枚举类型和结构体的交互操作代码。虽然例程只使用了少数几个方法,但是我也包含了完整的实现,因为如果你决定研究API的更多特性会发现这是有用的。InteropBits.cs文件中的代码不是可管理(managed)的API,因此它没有什么价值,但它是通过COM API与API实现了交互操作。最后,预计微软将发放一个暴露BITS功能到.NET框架组件代码的API,这样才能与其它的.NET框架组件的类库一致。

  图1中代码文件InteropBits.cs是手工建立的。.NET框架组件SDK发布了一个叫TlbImp.exe的工具,它建立和编译相似的代码成为一个可管理的组件,假如你有一个描述目标COM API的TLB文件的话。BITS API没有与TLB文件一起发布,但是平台SDK包括一个定义接口的叫Bits.idl的文件描述该接口。

  我使用MIDL.exe工具(与平台SDK一起发布)从Bits.idl中建立TLB文件。接着我使用TlbImp.exe建立一个描述该TLB文件的可管理组件。我使用ILDasm.exe将TlbImp.exe产生的组件分解为中间语言,最后我使用中间语言为向导建立C#代码,调整它的位置使它更有用、更正确。这种COM与C#交互操作的途径诚然乏味,但是它提供了对最后输出的例外控程度。

=================================
在自动更新应用程序中使用BITS

  BITS服务在工作期间管理文件下载。一个应用程序建立传送工作,接着给该工作添加一个或者多个文件。一旦工作的文件列表确定了,任务就继续开始(因为开始时工作的状态是挂起的)。工作用于管理一些细节,例如优先级、认证和错误管理。在任何时候工作都能被应用程序终止。

  一旦BITS在一个工作中完成了所有文件的传输,应用程序就调用一个方法结束该工作。Complete方法复制所有文件到它们的最后目的地。尽管BITS文档指导使用"正在复制(copying)"文件结束任务,但是现实中是在目的地将建立临时隐藏文件,Complete方法简单地更改隐藏文件的名称并使它们可见。

  即使最小使用BITS API(只使用IBackgroundCopyManager和IBackgroundCopyJob接口)也能得到自动更新应用程序所需要的每个文件。如果需要使用其它特性,例如枚举或者完成、错误通知,你必须使用另外的接口。

  例程AutoUpdater.exe使用BITS同时完成更新的发现和下载。为了到这个目的,应用程序为更新定义了连续的名称模式:Update1.dll、Update2.dll等等。尽管这不是发现更新的唯一途径,但是它使BITS功能工作得很好。例程维护了一个XML文件用于保存更新状态,该文件的两个相关的部分是接下来的更新数量和描述当前BITS下载工作的GUID(如果目前没有工作的话)。

  应用程序每次运行,它打开XML文件并检查它看接下来的更新是否已经下载。如果没有的话,XML文件中就没有工作的GUID,应用程序初始化BITS工作来在线下载接下来的更新。下面的代码演示了初始化下载工作的必要方法:

IBackgroundCopyJob job=null;

// 建立工作下载接下来的更新
bcm.CreateJob("Application Update",
BG_JOB_TYPE.BG_JOB_TYPE_DOWNLOAD, out jobID, out job);

// 将工作添加到文件
job.AddFile(updateUrl, localLocation);
job.Resume(); // 启动工作

  调用job.AddFile传递要下载文件的位置、完成的本地路径和最后保存的文件名。注意任务在开始为挂起状态,在BITS服务处理工作前必须重新开始。

  如果XML文件中有GUID,BITS任务就已经被初始化了。因此该GUID被传递到IBackgroundCopyManager.GetJob方法查看先前安排的下载任务是否有结果了。下一步依赖工作的状态。
在下面的代码(图2)中如果工作处于错误状态,应用程序就结束该工作(它从队列中清除失败工作)并接着为相同的下载文件建立一个新工作。错误状态的最可能的原因很简单,即有名称不存在的更新。这实现了例程中的发现更新部分。

  使用BITS查询更新的好处是BITS在后台工作。因此尽管查询并不是特别的优雅,非强制性的BITS下载是发现更新的可行方法。

  如果处于已传输状态,应用程序接着调用IBackgroundCopyJob.Complete来结束任务。这导致文件写入目标目录,使它准备好更新。接着应用程序返回,这时下载的文件片会融合在一起。

bcm.GetJob(ref jobID, out job); //获取BITS工作对象
job.GetState(out state); // 检查状态

switch(state){
case BG_JOB_STATE.BG_JOB_STATE_ERROR: //如果出现错误
job.Complete();
xml.BitsJob = Guid.Empty;
Marshal.ReleaseComObject(job);
job = null;
break; //继续建立新工作
case BG_JOB_STATE.BG_JOB_STATE_TRANSFERRED: //如果获得了文件
job.Complete(); //结束工作
xml.BitsJob = Guid.Empty;
return; //所有完成,返回
default:
return;
}
... // 为更新下载初始化一个新工作

图2.检查工作状态和结束工作

  最后,在默认情况下图2种的例程简单地返回并忽略工作。工作可能有两种状态:正在传输状态(这意味者工作在进行中)或者瞬间错误状态。这两种状态都被认为是可以潜在成功的,因此应用程序单独留下足够空间。如果BITS任务进入瞬时错误状态,BITS服务认为该错误可以恢复,因此它再次尝试。最后,瞬时错误的任务要么成功要么BITS将它置为错误状态。

  错误恢复逻辑覆盖了整个发现和下载AutoUpdater.exe应用程序组件。鉴于下载过程的更深的功能,该实现方法看起来不像网络通讯并且与通常的文件复制相似。在产品应用程序中你会发现将使用BITS API的大量其它特性的使用,例如自动传输通知、任务优先级维护和上载任务。

===================================
BITS的一些考虑因素

  BITS设计为从不初始化网络连接。这样的设计最好,因为你不希望无需连接的应用程序仅仅为了检查更新而拨号到一个ISP。但是BITS 1.0和1.5受收到Internet连接共享限制的影响。如果系统A共享系统B的Internet连接,在系统A上的初始化BITS工作的应用程序可能引起系统B初始化拨号,这是因为当前BITS服务的实现没有考虑共享连接的问题。

  这是一个难以解决的问题,但是可能在BITS的未来版本中会有一个补丁。Windows更新特性也有这个问题,使用BITS的应用程序在初始化共享连接的拨号与Windows没有什么区别。

  BITS的最后一个问题是工作文件的一致性。如果你下载的工作包含多个文件,BITS不认为工作完成了,也不认为文件到达了它们的最后目的地,直到所有文件都可用。但是,BITS无法知道在其它文件已经下载后,服务器上的一个文件改变了。因为这个原因,服务器上的更新可能影响客户端下载的一致性。

  有两种方法解决该问题。第一种是限制BITS下载为单个文件的工作,这是我在例程中使用的方法。第二种方法是保存单个工作的所有文件到服务器的同一个目录中。当对这些文件有更新时,在服务器上为新文件集合建立一个新目录。使用这种方法,在原来的工作上的传输中的客户端不会受到新文件集合的影响,这种解决方法的问题是服务端的文件定位改变了,客户端需要一条途径来获取新文件的位置。

  如果你需要查找BITS代码,BitsAdmin.exe是一个最好的帮手。该工具随Windows XP光盘分发,在Support/Tools子目录下。为了节省时间,我没有安装该工具,而是直接将.exe从Support.cab文件中解压出来。

  当你第一次熟悉BITS下载时,BitsAdmin.exe工具非常有用。几乎该API的所有特性都通过这个命令行工具的选项暴露了。你能枚举工作、检查工作状态、查看详细的工作和错误信息;你能初始化、挂起、继续和结束下载工作;最后,你能改变工作的优先级并从BitsAdmin.exe中取消工作。

  Windows API通过相关工具能够像这样访问的情况很少。实际上,脚本(例如批处理文件)可以通过BitsAdmin.exe工具完整地使用BITS。BITS是发现和下载更新的一个强大的解决方案,对于大多数应用程序来说,它的缺陷很少。现在我们看看安全性问题
==================
NET框架组件与安全性

  .NET框架组件为在代码中使用介绍很多有趣的安全结构。代码访问安全性(Code Access Security)就是一个例子。安全结构的另一部分是可管理组件的强化名字绑定。为了安全地从服务器向客户端传递代码更新,我使用该特性。问题是自动更新应用程序通过Internet与服务器和下载的文件(包括将安装到客户端的代码)与一个服务器有关联。这些新文件不能在客户端和服务器之间被网络上怀有恶意的人控制。

  这个问题的解决方法之一是整个数据交换通过HTTPS连接。BITS服务能够通过HTTPS协议定位目标资源。HTTPS仅仅是使用了加密的SSL/TLS通道的HTTP通讯。但是该方案有几个缺点。

  首先是降低了性能。Web服务器优于发送静态内容(例如HTML文档或者EXE文件)。当微软Internet信息服务(IIS)接收到一个静态文件的请求时,它对TransmitFile Win32 API作一个调用。TransmitFile在驱动层将位逐个地从文件系统复制到网络流,提供了极佳的性能。当你通过HTTPS加密时,性能大幅度的减低了,因为文件的每一位都要加密,这将增加与更新相关服务器的代价。使用HTTPS的另一个问题它使Web服务器成为安全结构中易于损坏的节点。如果Web服务器被损坏了,客户端将盲目地下载并安装服务器上带有恶意的内容。

  使用.NET框架组件的一个相当简单的特性,可以在没有这些缺点时获得更新文件传递的更高安全性。首先,加密的SSL/TLS通道是不必要的(这样Web服务器的输出不会受影响),其次使用这个方案,来自损坏服务器的大的安全威胁将被服务器和客户端拒绝,服务器将被禁止在客户端上运行恶意代码。.NET框架组件的该特性就是强化名字绑定,它是组件载入程序的一部分。
============================
强化名字和安全绑定

  .NET框架组件减轻的长期困扰DLL的一个问题是使用一个文件名来标识可重用(reusable)代码地命名方案不好使用。这导致了所有类型的应用程序都驶进了"DLL地狱"。它地解决方法就是强化名字。

  强化名字由几个部分的信息组成:文件名、版本号、文化信息和公共密钥。.NET框架组件使用强化名字实施组件间更严格的绑定。该框架组件在载入前使用公共/私有密钥对来验证强化命名组件。在强化命名组件的情况下,.NET框架组件语言编译器(C#或Visual Basic .NET)将公共密钥构建到DLL文件中。作为公共密钥的补充,编译器也使用私有密钥(由建立的组织保存)将文件内容弄混乱并加密。

  加密的杂乱信号也就是数字签名。为了检查数字签名,需要从组件文件中提取公共密钥并解密该杂乱信号。接着该文件的内容被再次弄混乱,并将结果与数字签名比较。如果组件内容发生了任何的变化,由于杂乱信号不匹配,签名验证将失败。

  如果你认为使用组件中的公共密钥来证实相同的组件并不安全,你就错了。但是,如果执行验证的代码有办法确认公共密钥是已知的或期望的密钥,那么你就需要一个更完整的安全方案了。.NET框架组件使用我们所知道的公共密钥的杂乱信号,称为公共密钥令牌(public key token)或者发起者(originator)。
当组件载入到应用程序后,你可以指定一个公共密钥令牌。如果一个组件是强化命名的并且该强化名字的公共密钥与传递给Assembly.Load方法的公共密钥令牌匹配,.NET框架组件就只载入该组件。

  下面我将演示强化命名一个组件的过程并使用发起者载入它。下面的C#代码可用于建立一个强化命名组件。该组件不作任何操作,只用于演示强化名字绑定:

using System.Reflection;
[assembly:AssemblyKeyFile(@"Keys.snk")]

  在代码编译前,你必须建立包含公共/私有密钥对的密钥文件(Keys.snk)。你可以在命令行使用随.NET框架组件SDK一起发布的Sn.exe工具实现:

>sn -k Keys.snk

  现在你可以使用C#编译器将该强化命名组件编译进一个.NET框架组件DLL中。

>csc -t:library StrongName.cs

  结果组件叫StrongName.dll。你可以使用Sn.exe的/T参数查看该强化名字的公共密钥令牌,如图3所示:


图1.提取一个组件的公共密钥令牌

  图3中的例子显示了一个公共密钥令牌为d586e46fd39d13b5(十六进制) 的DLL 。为了使用强化名字验证载入该组件,使用下面的代码:

//载入更新
String name = Path.GetFileNameWithoutExtension(patchName);
name = "StrongName.dll, PublicKeyToken=d586e46fd39d13b5");
Assembly update = Assembly.Load(name);

  这些代码要么验证签名并给组件对象返回一个引用,要么验证失败并且Assembly.Load方法出错。另外,使用Assembly.Load,代码可以验证组件在建立后没有被修改过。

  自动更新应用程序可以使用BITS下载更新文件而不涉及服务器端的安全问题。接下来,在更新安装并使用之前,可以使用Assembly.Load验证该组件。唯一的先决条件是原应用程序时与已经存在的公共密钥令牌的公共/私有密钥对一起安装。在安装和使用前,只有使用匹配密钥对建立的更新能被客户端验证。在这期间,用于建立更新的密钥对能存储在一个从不连接到网络的计算机上。

  这种安全机制很强大,但是也出有一些问题。例如,你能更新无代码文件(例如位图和数据文件)吗?为了使用该机制你必须使用强化命名建立所有的DLL和EXE文件吗?这些问题的答案分别为是和否。
=======================
更新包和替换

  如果自动升级应用程序对无代码文件没有作用,或者它要求所有EXE和DLL文件都可以管理并做名字强化,我也不会对它感兴趣。因此我需要一条途径来使用.NET框架组件强化命名绑定而不强迫更新文件是.NET框架组件文件。答案是将更新文件作为组件的资源嵌入一个组件文件。这不仅使所有文件类型受到强化命名保护,而且简化了BITS工作,从多个工作变为单个文件下载,一个大文件实际上是多个小文件包组成。实际上,包可以包括CAB文件或者MSI脚本--这些都没有限制。

  所有的.NET框架组件语言编译器都允许向一个组件文件种嵌入任意文件。我将演示怎样使用C#编译器实现该功能,但是概念与Visual Basic .NET或者可管理C++一样。下面的代码将Code.dll、 Bitmap.gif和Data.xml编译为一个Update2.dll文件,它包含了作为嵌入资源的每个文件:

csc /res:Code.dll /res:Bitmap.gif /res:Data.xml /out:Update2.dll
/t:library

  注意C#编译器有足够的灵活性,能在没有.cs代码文件时建立DLL组件。该DLL只包含嵌入资源。但是,如果你希望DLL包含一个强化名字,你需要包括一个短的.cs文件,它的代码与StrongName.cs的类似。

Assembly update = Assembly.Load(...); //载入组件

//获取资源名称
String[] resources = update.GetManifestResourceNames();

//枚举资源
foreach(String s in resources){
using(
//获取资源流
Stream res = update.GetManifestResourceStream(s),
file = new FileStream(s, FileMode.CreateNew) //建立文件
){
Int32 pseudoByte;
while((pseudoByte = res.ReadByte())!=-1){ //复制字节
file.WriteByte((Byte)pseudoByte);
}
}
}

提取组件资源代码

  发现嵌入资源并将它们复制到文件系统的必要代码在图4中显示。代码使用Assembly.GetManifestResourceName和Assembly.GetManifestResourceStream方法来查找资源并将它们复制为文件系统的文件。代码中没有保存文件系统的原文件数据,但它可以简单修改实现该功能。注意提取过程只在Assembly.Load已经验证了容器组件的签名后才执行。

  文件替换和提取

  即使有强化名称和组件资源,在应用程序更新过程中提取文件进入应用程序目录也是一个不容易的问题。该问题的症结是应用程序正在运行时要复制自己的文件。更为复杂的是,你运行的应用程序也许像记事本,这种应用程序,同时多个实例在运行。

  如果有类似的应用程序,我们是有通用的解决方案的。与其规定一个解决方案,不如查看可用的管理替换问题的特性。首先,一个简单的可能性是在与主程序的过程分开的过程中运行自动升级特性。当应用程序是不可管理的或者想扩充使它能够使用本文的技术时,这是个非常好的途径。更新过程能处理所有的BITS逻辑和文件提取,该过程在试图提取文件前一直等待,直到主应用程序终止为止。

  在单独的过程中运行提取代码主要有三个不利之处。首先,它使自己批处理自动更新代码很困难。其次,在服务器端这种方法没有什么好处,因为服务器不太可能关闭。第三,如果主应用程序被Windows Job对象管理,那么当主过程存在时,自动更新过程将被Windows自动终止。

  AutoUpdateApp.exe例程在主应用程序逻辑的过程中运行自动升级逻辑。如果你也决定同样做,可能会遇到删除和替换正在被该过程使用的DLL和EXE文件的问题。删除使用中的DLL或EXE文件的一个公开的小窍门是将该文件改名为临时文件,并把新文件复制为就文件名。接着你能在下次重启系统时使用MoveFileEx Win32 API记录该文件的删除,或者在应用程序中包含查找临时文件并删除的逻辑。

  就算使用更名的窍门,也得考虑更新正在进行时,用户运行了应用程序得另一个实例,它将载入一部分旧文件和一部分新文件。有多种方法解决问题,其中一个是在CLR中使用应用程序域的影像(shadow)复制特性。

  CLR在逻辑应用程序容器中(称为应用程序域或者AppDomain)运行可管理代码。多个AppDomain可以在一个Windows过程中运行。建立一个AppDomain时,可以标识新域来影像复制文件。它的意思是在应用程序载入组件时,组件文件被复制到一个隐藏的文件夹并从影像位置载入。该特性被ASP.NET框架组件中的ASP.NET类使用,用于把应用程序文件导出,这样才能被ASP.NET代码生成器更新。

  AppDomain对应用程序的再次执行也有用处。理想情况下,当有更新存在时,应用程序将提示用户是否有兴趣更新。回答"是"后,应用程序使用新特性运行。对有些应用程序,在新过程中重新载入应用程序是可以接受的,在其它应用程序中,更好的方式是新代码在执行更新的过程中运行。

  使用AppDomain重新运行可管理EXE是很容易的。本文的例程中的RelaunchExe方法就是一个实现的例子。RelaunchExe方法建立一个新AppDomain并重新运行执行文件,给它传递相同的命令行参数。
================
AutoUpdateApp.exe示例应用程序

  本文的代码示例不仅包括自动升级逻辑,还建立了应用程序的两个升级版本,这样你可以看到的整个操作方案。

  尽管Visual Studio .NET是一个强大的编程环境,但是也有一些时候你仅仅需要创建批处理建立(build)。我的项目就是一个例子,一个多版本构造,它很难作为Visual Studio项目生成。因此在发布时例子是一系列资源文件和一个用于从命令行执行建立的批处理文件。

  为了测试该应用程序,请下载本文应用程序文件和代码(http://download.microsoft.com/download/9/4/c/94cd450c-e7ae-46a3-ad1e-d19f2b80fa0c/bits.exe),并将它们解压到一个空目录。接着从命令行运行Build.bat。为了该批处理文件运行成功,要求csc.exe(C#命令行编译器)和sn.exe(强化名称工具)都在环境变量的路径中。你会发现.NET框架组件中的csc.exe安装在C:/Windows/Microsoft.Net/Framework/目录中,.NET框架组件的sn.exe一般安装在C:/Program Files/Microsoft.NET或者C:/Program Files/Microsoft Visual Studio .NET位置。

  一旦该项目已经使用Build.bat建立了,就创建了两个重要的目录:app和updates。注意会出现一个sln文件,这样你才能使用Visual Studio .NET简单地编辑代码文件(但是你需要接着使用Build.bat建立该项目)。第一个目录(app)包含应用程序安装。将当前目录转到app目录并运行AutoUpdateApp.exe来作测试。该应用程序的实际功能是显示它在载入目录找到地第一个位图(见图2)。


图2.自动更新的简单应用程序

  为了查看更新功能,建立一个叫updates的IIS虚拟根目录并赋予匿名下载访问权限。它的目的是http://localhost/updates作为一个发布所有更新的工作位置。接着将第二个目录(updates)的内容复制到该虚拟目录。

  假定所有都设置正确,你的应用程序将能够在http://localhost/updates上找到Update1.dll 和Update2.dll。从命令行执行和关闭AutoUpdateApp.exe几次,大约在第二次和第三次载入时,应用程序将提示第一个更新可用了。第一个更新仅仅向目录中添加了新的GIF文件。第二个更新实际上安装了AutoUpdateApp.exe的新副本,它现在有了运行幻灯显示载入目录中的所有GIF文件的特性。
你第一次载入AutoUpdateApp.exe时应用程序建立一个叫UpdateState.xml的数据文件。该文件包含了控制AutoUpdateApp.exe功能的信息,包括应用程序查找更新的网络位置。如果你不想使用http://localhost/上的位置,你能改变UpdateState.xml中的URL来指定不同的更新服务器。

  自动更新应用程序的未来

  目前有许多编写自动更新应用程序的特性可以使用。也就是说,微软开发人员目前在努力工作使自动更新更加简单并成为应用程序开发的自然部分。.NET框架组件的1.0版本目前已经提供了一些基本的功能。最终,你编写的应用程序越易于安装和更新,应用程序就越成功。

  在Windows的未来版本中将有介绍更新发现和替换的内建(built-in)服务的计划。BITS服务将继续成为下层下载机制,但附加的API将简化下载和安全性。作为补充,BITS将扩展成包括管理应用程序更新的易于使用的用户界面。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值