Windows Vista 开发新特性(2)- 你的文件存在哪?

Windows Vista 开发新特性(2- 你的文件存在哪?

其实这不能算是新特性,充其量算是一个Vista开发最佳实践,但是由于种种原因;基本上所有需要进行Vista兼容性升级的软件产品都会遇到这个问题,究其原因当然有历史因素,但是归根到底还是我们的开发不够规范。

先扯远一点,软件开发需要的到底是创新还是规范?我见过很多刚刚毕业的,还有仍然在校的学生把随意当成创新,把出轨作为炫耀。当我刚刚加入SSW的时候,对公司最大的不适应就是繁多而且详细的各项规范http://www.ssw.com.au/ssw/standards,上班的第一个月我收到最多的邮件都是提醒我哪里又违反了哪条规范;但是当我工作了1年以后,我发现这许许多多的标准已经成为了一种习惯,并且在帮我更快更好的完成我的日常工作。曾经听过这样的话“优秀是一种习惯”,当你吸取了其他人的经验,并在每天都不知不觉的使用这些经验的时候,那么你会发现做事的效率会大大的提高,并且一切都那么自然。当然,学习规范的过程是痛苦的,但总归是有收获的。回到软件开发的话题上来,我一直觉得学习编程最好的方式,就是把别人的代码自己从新写一边,编译并运行。记得上学的时候学习c语言就是这样,当我吧谭浩强老师的所有代码都自己编译了一遍以后,我发现其实编程是那么简单。这样的习惯一直跟随我学习phpsqljava.net等任何的新技术。

不好意思,扯得太远了一点。Vista中的一项重大改进就是UAC的广泛应用,当我们无法使用管理员权限的时候,我们发现很多以前很简单的操作变得如此的复杂。比如:我们都习惯于把应用程序的配置文件存放在\Program Files\Application Name\这样的文件夹中。这样的位置到了Vista上,你会发现普通用户/管理员都无法在不提升权限的情况下写入,导致应用程序的崩溃。当然,如果你的程序还没有添加UAC manifest的话,这一切都不是问题。

文件系统虚拟重定向 File System Visualization


关于这一点,我们可以从下面的实验中发现其中的缘由:

首先使用EditPlus这个工具创建一个文本文件并存放到\Program Files\目录下:

<shapetype coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="[email protected]@[email protected]@[email protected]@[email protected]@5xe" filled="f" stroked="f" id="_x0000_t75"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect"></path><lock v:ext="edit" aspectratio="t"></lock></shapetype><shape o:spid="_x0000_i1032" type="#_x0000_t75" style="visibility: visible; width: 415.5pt; height: 291.75pt;" id="Picture_x0020_1"><imagedata src="file:///C:%5CUsers%5CLEIXU~1.SSW%5CAppData%5CLocal%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_image001.png" o:title=""></imagedata></shape>

你会发现程序不会遇到任何问题,文件可以正常存储,但是当你使用文件管理器打开\Program Files\目录的时候,文件却不存在。

<shape o:spid="_x0000_i1031" type="#_x0000_t75" style="visibility: visible; width: 415.5pt; height: 221.25pt;" id="Picture_x0020_4"><imagedata src="file:///C:%5CUsers%5CLEIXU~1.SSW%5CAppData%5CLocal%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_image003.png" o:title=""><font size="3"></font></imagedata></shape>

实际的文件其实是被“重新定向”到了当前用户的AppData目录之下:

<shape o:spid="_x0000_i1030" type="#_x0000_t75" style="visibility: visible; width: 415.5pt; height: 221.25pt;" id="Picture_x0020_7"><imagedata src="file:///C:%5CUsers%5CLEIXU~1.SSW%5CAppData%5CLocal%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_image005.png" o:title=""><font size="3"></font></imagedata></shape>

其实,这是Vista中向下兼容的一项功能:文件系统虚拟重定向 File System Visualization)。其功能就是针对那些还没有兼容Vista的应用程序,把那些对受保护内容的访问从新定向到用户具备访问权限的位置,也就是\Users\UserName\AppData\Local\VirualStore\。在这个文件夹中你可以找到象Program Files, Windows这样的目录;因为普通用户默认情况下没有对这些目录的访问权限,所以相应的访问被定向到了这里。

当你的程序添加了UAC manifest的时候,那么同时也就丧失了Vista的这项兼容性保护;这个时候你必须在程序中使用正确的路径,把配置文件,日志文件等等都写入到用户自己的AppData中去。我们可以从下面的记事本演示中看到,由于记事本已经对Vista进行了升级,那么访问\program files\目录将会返回“没有权限”的错误信息:

<shape o:spid="_x0000_i1029" type="#_x0000_t75" style="visibility: visible; width: 415.5pt; height: 274.5pt;" id="Picture_x0020_10"><imagedata src="file:///C:%5CUsers%5CLEIXU~1.SSW%5CAppData%5CLocal%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_image007.png" o:title=""><font size="3"></font></imagedata></shape>

如果我们比较以下EditPlus和记事本的exe文件,我们就可以看到区别了:

<shape o:spid="_x0000_i1028" type="#_x0000_t75" style="visibility: visible; width: 414.75pt; height: 329.25pt;" id="Picture_x0020_13"><imagedata src="file:///C:%5CUsers%5CLEIXU~1.SSW%5CAppData%5CLocal%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_image009.png" o:title=""><font size="3"></font></imagedata></shape>

我们会发现记事本exe文件的manifest中多了以下代码:

< trustInfo xmlns ="urn:schemas-microsoft-com:asm.v3" >
< security >
< requestedPrivileges >
< requestedExecutionLevel level ="asInvoker" uiAccess ="false" />
</ requestedPrivileges >
</ security >
</ trustInfo >

以上代码告诉Vista上的UAC,记事本现在使用asInvoker的权限,也就是和调用者同样的权限;我在专门介绍UAC的文章中提到过,Vista上的用户默认是标准用户,没有访问\program files\的权限,这就是为什么记事本会保存失败。

多用户应用环境

Windows开始支持多用户开始,其实就已经有了用户目录的概念,但是我们一直都没有在软件开发中对这个问题作足够多的重视,造成了很多的应用程序都把一些用户自己的配置内容写入到了\program files\application name目录中。这个问题有很多人不理解,到底那些内容是应该保存在\program files\application name目录中,那些应该保存到\users\username\appdata目录中。其实我们可以简单的这样区分:

\program files\application name保存安装过程中固化的应用程序配置,这些配置不应被个别用户更改;比如:固定的网关,服务器地址,应用程序的动态链接库列表,程序版本信息等。

\users\username\appdata保存可以由用户修改的内容,并且这个配置文件应该是在应用程序第一次运行的时候由程序动态创建的。比如:窗口的位置,大小,上次登录的时间,用户名,cookie等等。

为什么不能由安装程序创建这个文件呢?主要是为了支持无值守的部署情况,这种情况下安装程序无从知晓个别用户的具体情况;所以无法正确创建这个文件。

其实,创建这个文件最简单的方式就是使用.net framework 中的ApplicationBlockConfiguration模块,因为它会自动的处理文件的读写和创建;对应用程序是完全透明的。

获取正确的系统路径

另外一个问题是如何在应用程序中正确的获得这些目录,其实也很简单,使用Environment.GetFolderPath()这个方法,我们可以轻易的获得我们需要的目录:

<shape o:spid="_x0000_i1027" type="#_x0000_t75" style="visibility: visible; width: 415.5pt; height: 286.5pt;" id="Picture_x0020_16"><imagedata src="file:///C:%5CUsers%5CLEIXU~1.SSW%5CAppData%5CLocal%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_image011.png" o:title=""><font size="3"></font></imagedata></shape>

上面的listView中使用了以下代码进行填充:

foreach (System.Environment.SpecialFolderfolder in Enum.GetValues( typeof (Environment.SpecialFolder)))
... {
ListViewItemfolderItem
=newListViewItem(
newstring[]...{folder.ToString(),Environment.GetFolderPath(folder)});
listView1.Items.Add(folderItem);
}

这里对Environment.SpecialFolder这个枚举类型进行了遍历,提取出了系统所有的特殊路径。其中值得注意:

Enviornment.SpecialFolder.ApplicationData:这个值会指向当前用户的AppData中的Roaming目录,这个目录和local的唯一区别就是当你的AD中配置了漫游功能的时候,此目录的内容会被保存在服务器上,并在用户登录其他计算机的时候预先部署。一般是在比较大的企业中为了提供更好的登录环境才使用。

向下兼容xp文件系统

说到这里大家肯定有疑问,因为在xp上,用户目录的路径是:

\Documents and Settings\,而不是\Users\,会觉得有问题,其实Vista在这一点上早就作了准备。

<shape o:spid="_x0000_i1026" type="#_x0000_t75" style="visibility: visible; width: 415.5pt; height: 270pt;" id="Picture_x0020_19"><imagedata src="file:///C:%5CUsers%5CLEIXU~1.SSW%5CAppData%5CLocal%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_image013.png" o:title=""><font size="3"></font></imagedata></shape>

在一个Vista的环境下输入了xp的路径,还是可以正常访问这个xp风格的目录。其实这里Vista借用了一个在unix上使用多年的技术“文件系统链接”来实现这个功能。

<shape o:spid="_x0000_i1025" type="#_x0000_t75" style="visibility: visible; width: 415.5pt; height: 401.25pt;" id="Picture_x0020_22"><imagedata src="file:///C:%5CUsers%5CLEIXU~1.SSW%5CAppData%5CLocal%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_image015.png" o:title=""><font size="3"></font></imagedata></shape>

当你键入dir /a的时候,出现了很多叫做<Junction>的东西,其实这些就是“文件系统链接”,Vista使用这些链接把xp风格的目录指向到了正确的Vista路径上。以保证xp的应用程序可以正常访问。如果需要的话,可以使用mklink这个命令来创建需要的链接。

所以,应用程序如果需要向下兼容的话,仍然应该使用xp风格的路径;当然我这里指的是如果你一定需要写明(hard code)这些路径的话;可能的话,尽量使用Environment来得到正确的路径。

相关资源

CFF Explorer manifest 查看工具:
http://programmerstools.org/node/307

正确使用Windows Vista UAC标准用户环境的最佳实践:
Developer Best Practices and Guidelines for Applications in a Least Privileged Environment:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnlong/html/AccProtVista.asp