可重用代码的艺术

简介

在内部代码管理的组织和在外部二进制文件的部署是在开发周期早期就应该被考虑的问题。本文的目的是强调一些可以应用到小型以及大型企业级应用程序的实用版本控制解决方案。这两个问题在移动开发领域内变得很关键,在该领域中,资源是稀缺的,而干净的代码就等同于有效率的、快速执行的代码。我们因而花费一些时间来解释可信计算如何应用到智能设备,以及您可以采取什么步骤来更好地保护您的代码。最后,我们将所有的理论结合在一个具有内置版本控制和更新的实际示例应用程序中。

智能设备和版本控制

一个易于使用且高度可自定义的工具 — Microsoft Visual Source Safe (VSS) — 允许开发小组(从一个人到数百人)对引入一个代码基或部署包的更改保持紧密的控制。任何类型的内容(从二进制文件到 XML 文档)都可以由 VSS 管理。不仅你们可以在小组成员间共享一个单独的项目或解决方案,在其他小组工作的其他小组成员也可以访问,而且如果赋予其足够的权限,他们可以修改您的代码基以及您的工作成果。因为任何添加到 VSS 的文件都在提供 VSS 服务的服务器上备份,所以如果需要,您始终可以回滚到一个先前的版本。任何小组成员都能看到他们有权限访问的文件的版本历史,在版本化文件间一行一行地比较差异,进行更改,或者在数据库中保存一个新的版本。

Visual Studio .NET 2003 中的版本控制

VSS 最新的更新 v6.0d 增加了 Visual Studio .NET 2003 集成,意味着只要您已经正确地管理了一个 VSS 数据库(参见此骤的产品文档),您就可以从 IDE 很容易地与数据库进行交互。我们假定我们已经打开了 IDE,而且系统管理员已经为我们创建了一个空的 VSS 数据库。我们选择小组领导的角色,这样我们可以添加我们认为适合的文件。从 Visual Studio .NET 的 Solution Explorer 中右键单击解决方案根,然后选择 Add Solution to Version Control。图 1 显示了用户从 Visual Studio .NET 2003 中将一个解决方案添加到 VSS。


图 1. 将解决方案从 Visual Studio .NET 2003 添加到 VSS

在本例中,我的数据库中没有任何文件,所以我将解决方案内的项目添加到数据库的根中。下一步您将看到所有文件已经被锁定并签入。作为一个提醒,签入主要意味着您已经放弃了修改一个文档的权利,并且该文档可以被其他小组成员自由签出以进行修改。图 2 显示了一个配置为通过 VSS 使用版本控制的解决方案。


图 2. 配置为通过 VSS 使用版本控制的解决方案

如果在任何时候您希望放弃自从签出后对文件做的更改,那么请右键单击该文件并选择 Get Latest Version 来覆盖您的更改。现在,如果您启动代码文件之一并试图做一个更改,系统将会提示您签出该文件或放弃您的更改,如图 3 所示。


图 3. VSS 的签出提示

一旦提示签出,就要为您的操作输入一个详细的注释。请注意,因为 VSS 为所有文件维护一个包括日期/时间戳的版本历史,所以没有必要在您的注释上附加一个日期或时间。Visual Studio .NET 2003 通过为您提供一个版本控制可停靠任务窗格来进一步显示其强大的功能,该任务窗格可以用来在其他小组成员可以访问整个代码基之前,监控需要被重新签入的文件。如图 4 所示,一旦您有文件签出,通过从 Solution Explorer 选择 Show Pending Checkins 也可以使用这个窗格。


图 4. Visual Studio .NET 挂起的签入任务窗格

我想要展示的最后一个功能(无须进入 VSS 提供的一些有趣的自定义)是,快速地在一个文件的标记版本(当您已经到达了某种里程碑并认为该版本将是未来所有工作的下一个基线,一个标签就被应用到您的代码基)或一个正常存档文件之间逐行比较差异的功能。您可以通过右键单击任何文件并选择 Compare Versions 访问这个功能。图 5 显示了出现的对话框。


图 5. 比较版本对话框

所有这种方便的 Visual Studio.NET 集成只不过是锦上添花。Visual Source Safe v6.0d 在记录开发工作方面是一种非常有用的工具,尤其在您的小组没有完全同地协作时。通过仔细地记录对您的代码基做出的所有更改,每个开发人员都被赋予了更大的责任来确保他或她的更改完全经过了组件及程序集测试,并且最终让您的解决方案不容易出错。

eVC v3.0/4.0 中的版本控制

以与您的托管代码相同的方式,您的本机代码可以从正确的版本控制获益。要将版本控制添加到一个本地项目,首先打开这个项目并展开 VCW (Visual C Workspace) 内的文件列表。图 6 显示用户将项目文件从 eVC 3.0/4.0 添加到一个 VSS 实例。


图 6. 将项目文件从 eVC 3.0/4.0 添加到一个 VSS 实例

与 Visual Studio .NET 2003 不同,在其中可以将文件自动递归添加到您的 VSS 实例,而这里您必须选择要包含在添加过程中的所有文件,如图 7 所示。


图 7. 添加到源控件对话框

下一步将要求您确认添加所选择的文件。您可以将单个文件保持为签出(例如,当您想将更改应用于共享代码基,但又希望为之后的版本继续工作时),或者只添加文件并强制自己在稍后签出。图 8 显示了用户将工作区/项目文件添加到 VSS 实例的结果。


图 8. 将工作区/项目文件添加到 VSS 实例的结果

一旦您的文件正确添加到了 Visual Source Safe,您就可以像使用 Visual Studio.NET 时一样签入/签出。如果您试图写入一个已签入文件,那么会要求您在做任何更改之前签出该文件。

智能设备和代码可维护性

尽管 Compact Framework 向经验丰富的桌面和服务器解决方案开发人员提供了围绕屏幕空间、内存可用性和有限处理能力的新挑战,但在开发过程自身方面,大多数概念都是从桌面扩展来的。在创建任何有价值的应用程序时,尽早从需求阶段就考虑应用程序在未来将如何使用,以及从未在早期阶段见过应用程序的新开发人员将如何在客户认为必须的地方创建功能并做出更改,这是至关重要的。当然,根本问题是代码的可维护性 — 这是大大小小的软件公司都在为之努力的事情。我愿意与您分享几条基本的指导原则,从而在您坐下来应对新的软件问题时引导您的思路。

命名约定

一个智能命名方案可以帮助您在最初看到应用程序时理解其逻辑。为代码中的命名组件提供一个抽象级,以便不会显示某些信息,例如一个函数是如何 工作的,而显示它是什么,这是很重要的。例如,在处理一个 Form 对象数组时,您可以使用一个名为 GetNextForm 而非 GetNextArrayItem 的函数。命名更像是一种艺术而不是科学,在足够清楚和过于冗长之间有一个艰难的平衡。有时,尽管有可能难以为人所理解,但更改一个变量名称来保持与编程语言标注的兼容是必要的。注意,以下内容是根据“命名约定”MSDN Library 桌面文档产生的,其中的元素对我们移动开发人员来说具有重要意义。

函数

决不要为函数或例程提供过于含糊不清的名称以至于会使它们不能捕获输出。例如,DoAnalysis() 可能是一个糟糕的选择。对于移动设备来说,WebService 调用是函数交互的一种常见形式,这是因为它们通常返回一个单个对象或基元,函数名称应该明确这一点。

在一个格式规范且正确使用了面向对象编程 (OOP) 的 Visual Basic .NET 或 C# 应用程序中,不要在属性、方法或函数中包含多余的类名称(如 AnimatedForm.FormCache(),而是使用 AnimatedForm.Cache())。通过避免函数中的冗余代码,这是一些可以很容易避免的事情,这在代码大小是关键因素时尤为重要。

使用后面跟有名词的操作来命名一个例程通常是很有帮助的 (FindGreatestInteger())。

如果您正在使用一种支持函数重载的语言,那么所有重载函数都做非常相似的工作是很重要的。否则,需要考虑重新命名这些函数以避免混淆。

为增强可读性,函数名称应该是使用 Pascal 大小写形式的 (MyFunctionName())。

变量

限制字母在应用程序中的使用。在类的顶部定义常量 (MAX_VALUE),或者如果客户在能够调整值时找到该值(参见下面的 ConfigReader),也可在一个独立的配制文件中定义该常量。

限制将无意义的计数器变量用于小的 for/while 循环或其他迭代结构,这是因为移动设备没有处理超过最基本递归的处理能力,迭代结构是很常见的。

布尔类型的变量应该包含单词“is”来表示一个 yes/no 值。

为增强可读性,函数名称应该是使用 camel 大小写形式的 (currentFormSize())。

为窗体控件赋予反映其类型的名称。例如 txtFirstName (用于包含第一个名称窗体的文本框,lblError 用于包含一个错误消息的标签,等等)。好的移动应用程序会在试图通过 RAPI、WebServices 和 RDA 调用远程服务之前在本地进行大量的基本验证。由于这类远程调用通过一个按每 KB GPRS 付费的连接发生,所以时间和金钱成本都很高。因此,您应该通过使用相应的控件名称来将事情简化。

使表名称保持其单数形式。

不要将表名称包含在字段名中(CarModelYear 在表 Car 中)。

不要在字段名中包含数据类型,这是因为之后对数据类型的更改需要额外的工作。

其他考虑事项

可以使用缩写,只要您保持一致且不将同一缩写用于多个概念。

尽量避免将相似的名称重复用于函数和变量(如函数 GenerateReceipt() 和名为 iGeneratedSales 的变量)。

注意拼写!很容易就会发生意外的拼写错误,并最终使流水线下游的开发人员产生迷惑。很多移动应用程序都是作为快速原型开始的。

文档

大多数开发人员都将代码文档视为一项杂务,不像设计精美的应用程序那样吸引人,而且因为智能设备应用程序跟与其相应的桌面应用程序相比在代码行数量上趋于更少,所以对于了解代码如何工作来说,注释看起来不是那么必需。但是,很容易理解为什么好的代码注释是很关键的:尽管外部文档可能是可用的(如设计文档、培训指南、版本历史记录等),但它们可能丢失或者与代码发布包是分开的。因此,代码注释应该从第一行开始就作为开发过程的一部分,而且保持与每个版本一起进行维护。即便有了一个出色的命名约定,在没有一点指导的情况下,代码也从不是百分之百可以理解的。稍后,我们将提醒您很容易将有用的注释加入您的代码,但在此之前,我们将讨论在编写注释时应该考虑的一些注意事项。

每个函数都应该以基础信息(例如,例程用途、输入假设、输出限制以及函数存在的主要原因)开始。还包括输入值(及类型)与输出值(及类型)的说明。

行尾注释可能难以读取,所以只有当您展开变量声明时才使用它们。确保将所有行尾注释在一个通用制表符结束处对齐。

避免将连字符和下划线用于一个注释块中的“框”。

保持您的注释干净:在代码交付以备后用之前,删除临时/无意义的注释。

如果您认为需要为特定的例程提供详尽的注释,您就需要改进变量命名,将函数拆分到若干辅助函数中,或者二者都进行。

使用正确的英语进行注释。注释的存在是为了便于理解,而不是为了带来混淆!

采取“现在编码,后面再注释”的态度会导致螺旋形下降。为什么不在此时函数逻辑在您头脑中还很清晰时花时间做这件事呢?

注释不应该是代码本身的伪代码,而应该是更高级的解释。

命名约定要与注释一致。使用一个所有编码人员都同意的相似样式。

注释 — 代码插入练习

由于 C# 语言提供很易懂的 XML 注释,关于他们为什么不应将其代码记入文档,C# 开发人员没有任何理由。产生后,C# 是唯一一种提供这种支持的语言,但是已经创建了一种方便的“powertoy”来在 Visual Basic .NET 下启用 XML,它叫做 VBCommenter PowerToy。在 C#中,XML 文档的添加是通过输入 3 个后面跟有 XML 格式注释的正斜杠来完成的。表 1 是我在智能设备应用程序中最常使用的标记的列表:

表 1. 智能设备应用程序常用标记。
标记含义

<c>

给您一种方法来表明说明内的文本应被标记为代码。使用 <code> 来表明多行为代码。

<exception>

让您指定哪些异常会被引发。这个标记应用于一个方法定义。

/// Thrown when...

<param>

<param> 标记应该用在对方法声明的注释中,以描述该方法的参数之一。

/// <param name="Int1">Used to indicate status. </param>

<returns>

<returns> 标记应该用在对方法声明的注释中,以描述返回值。

<seealso>

<seealso> 标记让您指定您可能希望在 See Also 部分显示的文本。使用 <see> 从文本内指定一个链接。示例:

/// <summary> Some other method. 
 /// <seealso cref="SomeMethod(string)" > 
public int SomeOtherMethod()
{
return 0;
}

<summary>

应该用于描述一种类型或一个类型成员。

<value>

<value> 标记让您描述一个属性。注意,当您在 Visual Studio .NET 开发环境中通过代码向导添加一个属性时,它会为新属性添加一个 <summary> 标记。然后,您应该手动添加一个 <value> 标记来描述此属性表示的值。

当您创建一个新项目时,通常会在多个代码点为您插入“///”字符。您可以使用这些起始点来添加您的注释(IntelliSense 会试着为您推荐标记),但所有注释都必须使用格式规范的 XML,否则编译器会拒绝您的要求。让我们以一个干净的类开始,并定义一个不太重要的函数。

      private void computeSum(int i, int j)   
      {
      }

当您只是在第一行上方键入“///”并键入空格时,IntelliSense 会真的显示出来。看看 IntelliSense 显示什么:

      /// <summary>
      /// </summary>
      /// <param name="i"></param>
      /// <param name="j"></param>
      private void computeSum(int i, int j)   
      {
      }

这很容易!快速而不费力地将 XML 记入文档是 Visual Studio .NET 2003 优于文本编辑器的众多长处之一。现在我们已经很好地注释了代码,那么我们余下的开发工作会从中得到什么好处呢?IntelliSense 不断帮我们解决难题!当我填入上面的 summary 和 param属性时,看看 IntelliSense 弹出什么。

当您滚动到这两个参数时,它们也会将其类型和用途在 IntelliSense 输出中显示出来,如图 9 所示。


图 9. IntelliSense 对于您已经记入文档的函数或变量很奏效

注释 — 生成 XML 输出

用 XML 可以完成的很多有趣的事情之一是高可用的代码文档。在本节中,我们花费一些时间来说明如何利用一些 Visual Studio .NET 功能来消除手动创建文档的需要 — 如果操作正确,上述任务都可以用 IDE 来处理,从而为您节约宝贵的时间。使用上面的属性,您可以非常容易地生成看上去很专业的代码指南,可以承载如您所想要的数量的详细信息。正如您所预料到的,实际的 XML 文档对人眼来说并不是非常有用,但是,.NET 附带一种机制,通过它您可以将格式设置应用于 XML 并生成 HTML 文档。我们假设,我们有正确记入档案的项目并打开了 Visual Studio .NET。在默认情况下,智能设备应用程序在生成结果中不包括 XML 输出。让我们看一下如何逐步启用 XML 输出。右键单击您的项目并选择 Properties。现在选择 Project NameProperty Pages 对话框中的 Configuration Properties 文件夹,如图 10 所示。


图 10. 配置项目以生成一个 XML 文档文件。单击缩略图查看大图像。

一旦将 XML 文档启用为创建和部署过程的一部分,当函数错过正确的注释时您会接到警告,而且如果您的 XML 语法是无效的,您会一直收到一个编译时错误,了解这些是很重要的。XML 旅程的最后一步是创建一个在代码移交时值得检查的可交付版本。转到 Tools,然后选择 Build Comment Web Pages in Visual Studio .NET,出现图 11 中的对话框。


图 11.在 Visual Studio .NET 中创建注释 Web 页对话框

注意您可以为解决方案中的单个或所有项目创建文档。指定一个 HTML 输出路径,无论您希望将 HTML 标记嵌入到文档中(如果您的小组成员之一在属性中嵌入了一个有问题的链接,这可能会是一个安全风险),还是希望将文档添加到 Internet Explorer 收藏夹中。现在您的文档将会出现,并且您的解决方案将被项目分解,如图 12 所示。


图 12. 生成的 HTML 文档

毫无疑问,关于为什么您的代码没有详细的注释,Visual Studio .NET 的设计人员真的没有提供任何理由。IntelliSense 为您做了大量枯燥的工作,并且您可以从其他开发人员良好的注释实践中获益,其中有用的 IntelliSense 标记会在您继续访问其工作时出现。

智能设备和可信计算

强名称程序集简介

尽管您可能编写一个在断开了连接,且从不与同步提供程序(更不要说互联网)交互的 Pocket PC 上的应用程序,采取适当的措施确保代码的安全应该始终是高优先级的。作为在 .NET Framework 下生成的所有代码的生成块,程序集为正确的版本控制、重新使用和安全权限作准备。程序集还为公共语言运行库 (CLR) 提供其所需的所有信息以正确地执行使用给定程序集的应用程序。

由于程序集后面的许多概念都与完整的 .NET Framework 相同,本文并不旨在解释程序集的详细信息,而是解释如何将安全性应用于智能设备应用程序。首先,我们将提供使用安全程序集的动机,然后提供必要的步骤以确保您所生成的代码中具有正确的应用程序安全性。

在一个连接完善的企业网中,确保操作敏感数据的 PDA 正在运行“安全的”代码是极为重要的,这意味着代码是由经授权的个体部署的,并且没有被篡改。您的软件可能是通过无线或其他 Internet 连接安装的,谁能说没有人进入您的文件共享并用假的 DLL 替换了您真正的 DLL?.NET 支持“强名称程序集”的概念,它为引用代码提供了一个保证 — 给定的程序集集是可以信赖的,没有被攻击或被操纵。简而言之,强名称程序集是一个用强(唯一)名称签名的程序集,由一个数字签名、区域性详细信息、一个公钥、版本信息以及该程序集的文本名称 (MyAssembly) 组成。使用这样一个程序集我们能获得什么?

强名称是一个唯一的名称。签名密钥对(在下面讨论)共同生成一个完全唯一的名称,而且由于没有人能够正确地重新创建两个密钥,因此没有人能够将其程序集装扮得与您的程序集一样。

应用程序的版本树是可信的。因为只有您才能为程序集的未来版本签名,因此可以确保用户以后的版本是源自您而非其他人。

程序集的内容或关联行为没有被篡改。

数字签名是通过由 NIST 和 NSA 一起开发的 Secure Hashing Algorithm v1.0(或 SHA1)实现的。哈希算法在单向上有用:一旦已算出,一个哈希值就不能被解密。这意味着如果两个程序集生成同一哈希值,那么确信无疑,这两个程序集是相同的。反之,则包含不同的哈希值。但是我们如何保证哈希值没有被篡改呢?数字签名创建是相当复杂的,但概念是简单的。使用了两个相关数字、一个私钥和一个公钥:数据用公钥加密,但必须用私钥解密。

首先,从程序集生成哈希值。接着,将哈希值用私钥加密,并与公钥一起存储在您考虑的程序集本身中。图 13 图示该过程。


图 13. SHA 为 SampleAssembly 生成程序集哈希。单击缩略图查看大图像。

强名称程序集签名

如上面所述,为了对程序集进行数字签名,我们必须有一个私钥和公钥对。可以使用一个名为强名称工具 (sn.exe) 的简单工具来协调密钥的创建。尽管我们将保护一个为移动设备设计的程序集,我们使用同一个在桌面上支持的的工具。由于这里没有新步骤要介绍,我们没有理由不为您的生产移动代码签名!(从“Start”菜单上的“Visual Studio .NET Tools”)运行 Visual Studio .NET 命令提示,并输入命令 sn –k SampleAssembly.snk,在此 SampleAssembly.snk 是项目相应的程序集名称(DLL 或 EXE)。复制这个文件并将其放在项目文件夹(或任何项目可访问的文件夹)的根部。这样就生成了一个 .snk 文件,它既包含公钥也包含私钥。稍后,我们将看到如何保护您的应用程序以防未签名的程序集。

与 Smartphone 平台相关联的安全术语之一是“数字代码签名”。我们将在后面看到,强名称签名将正确的凭据检查的责任交予开发人员,而数字代码签名却使用了一种更加灵活、更严格地强制的机制,这种机制可以在外围和运行时级别定义。有了数字代码签名,移动工作者可以选择阻止在电话上安装某些文件类型,或者不接受不满足其规范的应用程序在电话上执行。有关 Smartphone 的数字代码签名的更多信息,请访问这篇 MSDN® 文章。现在,打开项目的 AssemblyInfo 文件,并注意可用的属性(用 C# 编码)。

[assembly: AssemblyTitle("")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]   

这里的每一个属性都很易于理解,而且如果有人正在从 Windows 资源管理器读取 DLL 或 EXE 的属性,则有助于更好地个性化您的程序集。图 14 说明了填充这些值之后程序集的属性。


图 14. 示例程序集 (DLL) 版本信息

 [assembly: AssemblyDelaySign(false)]

当私钥在开发时不可用时,这允许创建者“延迟”一个程序集的完整签名,直至代码已经准备好发布为止。我们将在名为 Delayed Strong Name Assembly Signing 的小节中讨论 AssemblyDelaySign 属性。

[assembly: AssemblyKeyFile("..//..//MyKeyFile.snk")]

AssemblyKeyFile 为我们上面生成的密钥对提供一个链接。在 C# 中,密钥路径是相对于二进制文件夹而言的,而在 Visual Basic .NET 中,它是相对于解决方案文件夹而言的,注意到这一点很重要。如果您不能解析密钥对位置,编译器会拒绝您的请求!

[assembly: AssemblyKeyName("SampleAssembly")]

这是加密服务提供程序 (CSP) 的名称,该提供程序将被调用以为您的程序集进行数字签名。如果在 CSP 中可以找到 KeyName,那么这个密钥是已使用的。如果 KeyName 不存在,并且 KeyFile 也不存在,则文件中的密钥被安装在了 CSP 中而且被使用。

只要生成程序集,结果就会是一个强名称程序集。此时,数字签名存储在包含此程序集清单的可移植可执行 (PE) 文件中。我们如何确定这个过程奏效呢?Visual Studio.NET 配套了 Microsoft 中间语言 (MSIL) 反汇编程序。这不仅是巡视代码的一个极好方法,而且还可以用来识别已经用于为代码签名的公钥的存在。从 Visual Studio .NET 命令提示行运行 Ildasm,并打开程序集(DLL 或 EXE)。双击 Manifest 打开程序集清单,结果如图 15 所示。


图 15. Ildasm 输出显示了公钥的使用和对数字签名的哈希算法。

假设程序集 B 引用了进行过数字签名的程序集 A。不需要设置任何特殊的属性来使用由 CLR 处理的验证。而程序集 B 的清单将包含一个表示程序集 A 的公钥的标记。标记是完整公钥的一部分,而且是使用它而不是密钥本身来节约空间。图 16 演示了当 My Application 试图访问 SampleAssembly.dll 时 CLR 进行的操作,在此“公钥”和“原始哈希”都从 SampleAssembly 获取。


图 16. CLR 将 My Application 的公钥标记哈希与 SampleAssembly 的哈希相比较。

使用 GAC

全局程序集缓存 (GAC) 是一种代码缓存,它在任何带有已安装的 CLR 的机器(例如已安装了 CF.NET 的 Pocket PC)上都可用。旨在跨应用程序共享的程序集可以存储在这里,而其必须有一个强名称。注意,安装了 GAC 的程序集必须有匹配的程序集名称和文件名,不包括文件扩展名(因此 MyAssembly 必须与 MyAssembly.dll 或 MyAssembly.exe 配对)。

将程序集安装在 GAC 中有什么好处?

一个共享的位置意味着基于 RAM 的安装不需要有重复的 DLL(同一版本且同一文件名)耗费宝贵的空间。

并行版本允许同一 DLL 名称的多个版本对 CF.NET 应用程序可用。在编译时,程序集存储它们所依照生成的程序集版本信息,这意味着他们将永远使用相同的程序集版本运行。因此,即便是此 DLL 的更新的版本可用,程序集也会继续使用旧的 DLL,从而防止了任何类型的向下不兼容。

其他好处在于在桌面上使用 GAC,但不与压缩框架共享。

所有 CF.NET 基类库都安装在 GAC 中,那么您还在等什么?您可能已经注意到了 Pocket PC 的 /Windows 目录中前缀为“GAC”的文件。CLR 将访问 Cgautil.exe 实用工具来用关于安装了 GAC 程序集的信息更新 GAC。要将您的程序集安装到 GAC 中,首先用一个强名称为其签名,然后在您的应用程序部署过程中包括一个文本文件(扩展名为 .GAC),如 CAB 文件。这个 .GAC 文件需要放在 /Windows 目录中,而且应该用 ANSI 或 UTF-8(不用 Unicode)编码。下面是一个示例 .GAC 文件:

[/Windows/SampleApp.gac]
/Program Files/SampleApp/Engine.DLL
/Program Files/SampleApp/Support.DLL

每个程序集都必须在其自己的线上。随着 SampleApp 其后的每一次运行,程序集被移动到 GAC 中。如果您从 /Windows 移除 .GAC 文件,在 .GAC 中列出的文件将在您下一次运行应用程序时从 GAC 卸载。而且,如果您在 /Windows 中更新 .GAC 文件,GAC 也会被相应更新。

以编程方式确保强命名

现在您的公司有了强名称程序集,那么如何保证客户端代码只加载安全变量,而不是可能由黑客传播的有害文件呢?如上面所提到的,要从一个经签名的程序集获得公钥标记,从 Visual Studio .NET 命令提示调用以下命令:

Sn.exe –Tp <SampleAssembly>

图 17 显示了从用 - Tp 开关运行这一代码得来的输出。


图 17:用 - Tp 开关运行 Sn.exe 的输出。

通过这个命令,在输出结束时您将看到您的公钥标记。在本例中,它是十六进制字符串“629b481b10e3971b”。现在,以编程方式,您可用签入您的代码以确保程序集仍是经过签名的,并且带有正确的公钥标记。

         Assembly asmComp = Assembly.LoadFrom();
         byte[] myAssemblyKey = asmComp.GetName().GetPublicKeyToken();
         byte[] myPubKey = new byte[] {0x62, 0x9b, 0x48, 0x1b, 0x10,  0xe3, 0x97, 0x1b };

         if (myAssemblyKey != myPubKey) //Do Processing; Perhaps close application

这里我们对代码中公钥标记的字节表示形式进行了硬编码。这不是个好方法!我们这样做只是为了演示的目的。稍后我们将引入 ConfigReader 类,这是一个好方法,从代码本身提取已知的好公钥标记,并将其放在一个外部配置文件中。

延迟的强名称程序集签名

您的私钥需要谨慎处理。如果在您的组织以外有人想要获取这个密钥,这个人会将它与公钥(始终与程序集一起分发)组合在一起,制作出程序集看起来合法的版本。对于所有的开发人员来说,有一个该密钥的安全传输副本并不总是可操作的,所以有一个人应该是“守门员”。因为只有一个人能够生成代码则效率太低,.NET 提供了此问题的一个解决方法:延迟的签名。使用延迟签名,您可以只用公钥来生产和测试您的程序集,一旦准备好进行发布,您的程序集可以用私钥签名并发送给您的客户。以下显示了如何使用延迟签名:

1.

通过将输入标志更改为 sn.exe 从公/私钥文件 MyKey.snk 提取公钥:

Sn.exe –p MyKey.snk MyPublicKey.snk

1.

将此公钥传递给项目的所有开发人员,并将私钥文件 (MyKey.snk) 保持在一个安全、受保护的地方

2.

在您的程序集内,指定延迟签名:

[assembly: AssemblyDelaySign(true)] 
[assembly: AssemblyKeyFile("..//..//MyPublicKey.snk")]

编译器将公钥插入到程序集清单中,并在 PE 文件中为完整的强名称签名保留空间。当生成程序集时,必须存储真实的公钥,以便引用这个程序集的其他程序集能够获取这个密钥来存储在其自己的程序集引用中。

1.

如上所述,默认情况下,GAC 只接受强名称程序集。如果您将程序集存储在 GAC 中,您需要暂时关闭程序集验证来调整您的程序集。您可以通过执行下列命令来完成此任务:

sn.exe -Vr SampleAssembly.dll

重要注意事项 -Vr 选项应该只在开发过程中使用将程序集添加到跳过验证列表会产生一个安全漏洞。恶意的程序集会使用程序集(添加到跳过验证列表)的程序集名称、版本、区域和公钥标记来伪造它自己的标识,从而禁用对它自己的验证。

1.

当准备好发布您的程序集时,用下面的方法重新运行 sn:

sn.exe –R SampleAssembly.dll MyKey.snk

1.

指示 GAC 重新启用对您的程序集的验证:

sn.exe –Vu SampleAssembly.dll

处理版本号

我们浮光掠影地了解了 AssemblyVersion 属性的重要意义。这可用于指示您代码基中的生成增量,或用于始终将一个特定版本号硬编码到您的代码中。版本格式是 AAA.BBB.CCC.DDD。版本号由四个部分组成:主版本 (AAA)、次版本 (BBB)、内部版本号 (CCC) 和修订号 (DDD)。由于版本号是为程序集进行强命名的三个基本属性之一,应该给予特别关注。下面是每个部分的描述。

主版本和次版本 — 作为最外面的数字,这两个值可以表明在前一主/次版本之间很大的改变。使用这两个值来反映潜在的破坏兼容性的代码更改。

修订版本 — 这个值倾向于指出代码中更小、更不重要的更改,但是这些更改可能潜在地导致兼容性破坏。

修订版本 — 这是一个对程序集非常小的、增量的更改,可以被强加给用户而不会担心破坏另一个引用它的应用程序。

您可以指定全部四个值(比如,1.3.2.1000)或者使用通配符(比如,1.3.*),并且让 Visual Studio .NET 自动填充版本槽的剩余部分。

当您的代码编译好并且程序集清单构造完成后,任何在代码中引用的程序集都将其版本号与您的代码硬关联。换句话说,当您的代码运行时,它会寻找与您的代码引用具有相同名称、版本和区域的程序集。在完整的 .NET Framework 下,您可以使用 .NET 配置工具,并设置绑定策略来重定向一个对较旧的 DLL 的请求,从而与一个较新的、最近部署的 DLL 链接起来,以屏蔽应用程序内的任何错误。Microsoft 没有发表过任何关于在 CF.NET v2.0 中绑定策略的可用性的言论。

将代码合在一起:MemoryStatus 自定义控件

我们可能会读到大量关于新开发技术的信息,但是没有看到真实的 的代码,有时难以实现从理论到应用的飞跃。我已经创建了几个有趣的组件,它们可以使人理解上述关于相应代码结构、注释、可维护性和代码清洁度等几点。此外,我将说明如何创建这些组件:

可以用于 Pocket PC 的任何客户端应用程序上的一个自定义窗体控件。

客户端使用的处理客户端更新以及所有程序集的“启动程序”类型的应用程序。应用程序自更新是一个看起来永不过时的问题,并且有很好的理由:技术持续变化,用户需求不同,而且非常感谢这使得开发人员可以持续工作!

一个允许应用程序设置存储在用户可配置的 XML 文件中的 XML 驱动的体系结构组件。

从该解决方案可以清楚地看到,于创建伸缩良好的干净代码,已经在您(作为 Pocket PC 或 Smartphone 开发人员)的掌握之内了。

ConfigReader

使用移动设备的系统注册表在极少 情况下是明智的设计决策。访问系统注册表需要开销很大的 P/Invoke(或者两个,或者三个!),而且并不适合存储很多基本信息块。如果在执行期间您的代码必须频繁地访问该信息,并且该信息随时间改变,那么使用该注册表就根本不是一个可行的选择。相反,为什么您的应用程序(允许以明文指定某些设置)不包含 XML 文档?由于任何桌面计算机都可以编写 XML,然后在一个 ActiveSync 会话期间通过无线将更新的 XML 发送到设备,所以部署变成了一件轻而易举的事情。如果以下条件成立,则 ConfigReader 是简单的:我们有一个反映 XML 文档中所有设置键的 NameValueCollection,并允许我们在整个应用程序中快速地引用其中的每个键。

设想一个使用 FTP 控件连接到远程服务器以访问专有信息的应用程序。如果 FTP 连接信息随时间更改(并且您将此信息硬编码到程序集中),部署将变成一个问题。另外,您会没有什么正当理由地将一个新版本引入到您的代码基中!关于已填写的 XML 文档(我们使用文件名 app.config.xml)的实例,请参见此 MSDN 教程包含的解决方案。用法很简单。

要获得特殊键的信息,如:<add key="RECT_HEIGHT" value="20"/>,我们应该调用:

TechArch.ConfigReader.AppSettings["RECT_HEIGHT"];

显然,因为我希望您重用这段代码,您的命名空间信息将进行更改。要修改一个特殊键上的信息,我们需要调用:

TechArch.ConfigReader.AppSettings["RECT_HEIGHT"] = Value

在我们退出应用程序之前(并且取消 XmlDocument 实例),我们必须调用 Save 方法来保持对 app.config.xml 文件的更改。在 Initialize 方法内,我们创建一个 XmlDocument,然后开始在 XmlNodeList 对象中构造一个节点列表。

            configNodeList = xmlConfigFile.GetElementsByTagName("appSettings");

            //At the appSetting node level we will store all of our application variables
            if(configNodeList.Count!=0)
            {
               foreach(XmlNode node in configNodeList[0].ChildNodes)
               {
                  m_configReader.Add(node.Attributes["key"].Value,node.Attributes ["value"].Value);
               }
            }

当我们想要保存时,更加复杂的逻辑出现了。我们首先创建一个 xmlTextWriter 对象,并指定 UTF-8 编码。然后为磁盘上的 XML 文档建立三个层次的结构。

            System.Xml.XmlTextWriter xmlTextWriter = new XmlTextWriter(m_path, System.Text.Encoding.UTF8);
            
            //Build document with three levels, with  being the inner-most level
            xmlTextWriter.WriteStartDocument();
            xmlTextWriter.WriteStartElement("configuration");
            xmlTextWriter.WriteStartElement("configSections");
            xmlTextWriter.WriteStartElement("appSettings");

接着,对每个键/值对,我们写入磁盘。

            for(int i = 0; i < m_configReader.Keys.Count; i++)
            {
               //Now write the contents
               xmlTextWriter.WriteStartElement("add");
               //Write the key attribute
               xmlTextWriter.WriteAttributeString("key",m_configReader.Keys[i]);
               xmlTextWriter.WriteAttributeString("value",m_configReader[i]);
               xmlTextWriter.WriteEndElement();
            }

最后,我们关闭那三个层次并刷新我们的写缓冲。

            //Close each of the three levels
            xmlTextWriter.WriteEndElement();
            xmlTextWriter.WriteEndElement();
            xmlTextWriter.WriteEndElement();
            xmlTextWriter.WriteEndDocument();
            //Flush our TextWriter buffer then close it
            xmlTextWriter.Flush();
            xmlTextWriter.Close();
            xmlTextWriter = null;

MemoryStatus

当使用构建良好的 UI 时,弹出的消息框信息可以被转移。定义您自己的自定义控件,使其以一种舒服的、图形化方式向用户提供信息,这通常是一个好主意。我决定实现一个内存状态控件来阐释一个用于此类需要的解决方案。它不仅在一个图形化栏中提供自由存储和程序内存信息,还展示了内存如何在存储与程序内存之间进行拆分,甚至可以捕捉一个双击事件来提供关于系统状态的更详细信息。请注意,当前版本的 Compact Framework 不支持双击事件。

确定笔针活动打算单击还是双击的逻辑是清楚而且平台不可知的。

         int now = System.Environment.TickCount;

         // A double-click is determined if the the time elapsed
         // since the last click is within DoubleClickTime.
         if (now - previousClick <= SystemInformation.DoubleClickTime)
         {
            // Raise the DoubleClick event.
            if (DoubleClick != null)
               DoubleClick(this,EventArgs.Empty);
         }
         else   
         {
            memoryDivideDisplay = !memoryDivideDisplay;
            this.Invalidate();
         }

         previousClick = now;

因此,如果上次单击和当前单击之间的时间小于 SystemInformation.DoubleClickTime,我们就触发 DoubleClick 事件。除了这个诀窍,我们的自定义控件是非常简单的。我们可以设想 OnPaint 方法真的将任何种类的信息绘到屏幕上。在本地系统状态之外,您可以显示天气信息、股票价格等等,这一切都只使用 web 服务!

考虑到在未来更容易进行更改,MemoryStatus 使用 ConfigReader 来保持几个在代码本身之外定义的变量。

MSDNVersionSync

如上所述,应用程序启动程序可以执行多种不同的预执行步骤。这个组件为我们提供了相当简单的机制,通过它我们可以将任何移动应用程序转换为自我更新的应用程序。其中一个十分重要的步骤(倘若本文的上下文成立)是由父程序集(本例中的主应用程序)使用的程序集的自动更新。因为在编译时,程序集引用强烈地依赖于其父程序集,所以如果一个或更多子程序集需要更新,那么重新部署父程序集是必要的。

我们认为最新的文件存储在一个网络文件共享上。这些文件也可以放置在任何 Web 服务器上,但是我们无法访问文件属性(如上次访问时间或上次修改时间)。有很多机制能完成这些目标,以下代码中提供了针对此问题的一个解决方案:创建一个 XML 文档,该文档包含 Web 服务器上可用的每个组件的版本信息,然后在本地将每个本地组件的版本与 Web 服务器上的版本相比较。

要启动我们的无窗口应用程序,我们使用 System.Reflection 命名空间来获取启动程序的执行路径。

         string filePath = 
            Assembly.GetExecutingAssembly().GetModules()[0].FullyQualifiedName;
         string targetPath = "/"" + 
            Path.Combine(Path.GetDirectoryName(filePath), "MSDNSampleClient.exe") + "/"";

然后,我们在最后的文件写入时比较本地文件和服务器文件。如果日期不同,我们强制对组件进行更新。这种类型的比较是在组件在网络文件共享上可用时完成的。

如上面所述,当组件在 Web 服务器上可用时,我们需要改变我们的版本比较方法。如您在代码中将看到的,我们使用反射来获取每个组件的版本信息,并将其与在 Web 服务器上可用的 XML 配置文件中指定的版本信息进行比较。查看代码以获得详细信息。

MSDNSampleClient

这是一个基本上无代码的项目,包含带有一个 MemoryStatus 控件的窗体。图 18、19 和 20 是由此控件支持的 3 个“视图”的屏幕快照。


图 18. MemoryStatus 控件可用内存视图


图 19. MemoryStatus 控件、存储/程序内存拆分视图


图 20. MemoryStatus 控件、详细内存信息视图(双击行为)

小结

本文旨在强调并突出将桌面开发周期最佳实践用于智能设备领域的很多方法,在这一领域中很少是理所当然的,通常需要给予更多关注以确保在此类项目上投入的时间和资源没有浪费掉,从而避免代码仅仅可理解这一窘境!正确的版本控制以及清楚的变量命名和相应的注释可以有助于提高分布式开发小组的效率。确保程序集的强命名会产生更自信的应用程序部署过程。最后,我已经提供了一些组件,允许您使变量独立于代码,从而有助于未来的配置设置更改。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值