Silverlight™ 2 包含对 Windows® Presentation Foundation (WPF) UI 框架所做的大量更改:新控件、丰富的网络 API 和数字版权管理 (DRM) 支持。Silverlight 2 中的一项主要更改就是能够使用与 Microsoft® .NET 兼容的语言编写 Web 客户端。在本文中,我将重点介绍 Silverlight 的开发核心:CoreCLR。
在 过去的十几年中,我们已经拥有了许多不同的 Web 编程技术,从 CSS 到 ECMAScript 变体。其中大部分技术都特定于 Web 编程任务,举例来说,通过编程 CSS 所学到的技术并不适用于其他领域。相比之下,Silverlight 2 允许您使用适用于桌面编程的相同 .NET Framework 技术(如基类库、XAML 和 C#),还允许您将这些技术直接应用到 Web 客户端应用程序。此外,也无需创建单独的 CoreCLR 开发环境:您可以直接使用 Visual Studio® 来设计、开发、调试和配置 C# 或 Visual Basic®,就像使用桌面应用程序一样。我们创建的 Silverlight 2 CoreCLR 可使 Web 编程像桌面编程一样丰富。
虽 然拥有一个丰富的编程环境对开发人员很有好处,但用户并不想下载大型的浏览器插件。为了使 Silverlight 适合用户使用,必须实现快速安装。我们已将 Beta 1 的安装大小缩减到 4.3MB,通过宽带连接大约需要 6 到 10 秒钟即可安装。想想 .NET Framework 2.0 CLR 的两个主要核心部件(mscorwks.dll 和 mscorlib.dll)的大小大约都等于 Silverlight 2 coreclr.dll 和 mscorlib.dll 相加的大小,这真是很了不起的成就。
深入了解 CoreCLR 引擎
自 2005 年 10 月发行 CLR 的 2.0 版本后即开始了 CoreCLR 的设计。它的两个主要设计目标是大小和兼容性:从编程人员的角度来看,针对 CLR 的编码应该始终相同,而从用户的角度来看,下载必须非常小。由于 Silverlight 旨在提供一组不同于桌面 CLR 的方案,因此,我们可以进行一些更改,以简化 CoreCLR 并允许我们缩减 Silverlight 的安装大小。但是,堆栈底部的一致性至关重要。行为差异(即使这些行为差异都正确)表明堆栈上部有错误。
为了确保兼容性,我们在堆栈底部的各个组件中使用相同的代码。执行引擎和虚拟机都是相同的。其中包括类型系统和元数据、垃圾回收器 (GC)、JIT 编译器、线程池以及运行时引擎的其他核心部件。
但 是,为了适应 Web 应用程序方案,进行了一些更改。例如,富 Internet 应用程序通常简单且运行时间短,JIT 编译器主要侧重于减少启动时间,而非执行更复杂的优化操作。同样,服务器垃圾回收模式可以对使用相似分配模式的多个工作线程进行优化,而对 Web 托管应用程序则行不通。因此,Silverlight 只包含针对交互式应用程序进行优化的标准工作站 GC。但是,在 Silverlight 应用程序中使用 Microsoft 中间语言 (MSIL) 和元数据的方式与在针对桌面的托管应用程序中的使用方式完全相同,而且应用程序的行为在用户的桌面上和在浏览器上一致。
事 实上,Silverlight 并不打算取代桌面 CLR,这就引发了核心引擎中最大的变化:CoreCLR 将与桌面 CLR 进程并行运行。过去,我们从来就不能在同一进程中运行 CLR 的两个版本。这是一个难题,原因有好几个,其中一个是管理进程范围的状态:每个 CLR 实例都假定进程中只有一个 CLR,因而只有它可以处理其静态数据。如果 CLR 1.1 和 2.0 中都包含 staticFoo 变量,而且在同一个进程中同时加载了这两个 CLR 版本,则任一版本都无法在不影响另一个 CLR 状态的情况下写入 staticFoo 变量。
虽 然进程范围的状态是最明显的问题,但在一个进程中并行运行两个 CLR 还会导致其他问题。例如,如果您同时运行了两个 GC,如何防止其中一个 GC 挂起另一个 GC 的线程?此外,空间占用也存在问题:如果在一个进程中加载多个 CLR,每个 CLR 都必须加载代码(这些代码可能都相同),并为其静态变量和托管堆留出相应的空间。
在 某些重要的情况下,托管 CoreCLR 需要与桌面运行时并行运行。如果 CoreCLR 和桌面 CLR 不能同时运行,我们将无法编写托管 Web 浏览器控件(该控件可以导航到使用 Silverlight 的网页)的桌面 Windows 窗体或 WPF 应用程序。若要解决这个潜在问题,只需在您的 Windows 计算机上安装依赖于 CLR 的 Silverlight:每次安装 Windows XP SP2 和 Windows Vista® 时,都会随操作系统安装相当新的 CLR。但是,无论您的计算机上安装了哪种版本的 CLR(对于 Mac OS X 来说,即使您的计算机上未安装任何 CLR),只要使所有 Silverlight 代码在 CoreCLR 上运行都必须确保绝对兼容。因此,我们努力使 CoreCLR 可以与桌面 CLR 进程并行运行,我们相信经过我们的努力用户会获得更好的 Silverlight 体验。
CoreCLR 安全模型
核 心引擎中的另一个重大更改与新的安全模型有关。注意,一直以来,.NET 开发人员使用代码访问安全性 (CAS) 来阻止不受信任的代码执行特权操作。CAS 功能非常强大,但使用起来相当复杂。它允许用户或管理员使用权限集定义各种代码沙箱,然后将各个程序集映射到这些沙箱中。对于 Silverlight 应用程序,我们只需一个沙箱,该沙箱等效于 Internet Explorer® 用来在网页中运行脚本的沙箱。使用此简化方案,我们可以删除所有的 CAS 策略。
我 们也简化了安全执行模型。新的模型基于在 CLR 2.0 版本中引入的一个概念 — 安全透明。透明模型的本质是将代码分为以下三类:透明代码、SafeCritical 代码或关键代码。透明代码的信任级别最低,它无法提升权限或访问计算机上的敏感资源或信息。在 Silverlight 2 中,所有的应用程序代码都是透明代码。关键代码的信任级别最高,它可以通过 P/Invoke 与系统进行交互,甚至可以包含无法验证的代码。对于 Silverlight 2,所有关键代码必须是 Silverlight 平台的一部分。SafeCritical 代码则相当于二者之间的桥梁,借助它,透明代码可以通过调用关键代码来访问系统资源。我们可以将关键代码想象成 Windows 的内核 API;将透明代码想象成用户应用程序代码;而将 SafeCritical 代码想象成用户代码和内核代码之间的 API。
透明代码只能调用其他透明代码或 SafeCritical 代码,而 SafeCritical 代码可以代表用户代码调用关键代码。SafeCritical 代码需要将输入规范化或调整为标准格式,净化关键代码的输出以保护系统的安全性(参见图 1)。
图 1 CoreCLR 中的安全执行机制(单击图像可查看大图)
将 输入规范化为关键代码的情况要比净化输出的情况更简明易懂。例如,如果我的 Web 应用程序需要在本地磁盘上写入一个文件,则使用独立存储即可实现此操作。但是,如果您不希望此应用程序写入名为“..\..\..\.. \bootmgr”的文件中,就必须确保输入的格式正常、规范。认为关键代码的输出存在安全风险的情况非常罕见。主要的安全概念是,控制信息泄露在减小受 到攻击者各种类型的攻击方面起着至关重要的作用。假如我尝试访问系统上的一些用户信息,获得的响应是“权限被拒绝”。但作为另一个用户重复相同的访问操作 时,我获得的响应是“用户 Bob 不存在”。如果我知道会获得这两种响应,重复尝试无效访问就可以获得系统上的用户名列表。
简 化的安全策略为使用 .NET 代码的开发人员带来了极大的便利,同时也有助于开发人员研究 .NET 代码。我们已尽可能少地使用关键代码和 SafeCritical 代码了。如果使用的大部分都是透明代码,将有助于减少需要我们进行深入安全检查的代码的数量。虽然仍要检查透明代码的正确性和安全性,但至少我们清楚这些 代码不会执行任何特权操作。许多 Silverlight 大型部件(包括动态语言运行时 (DLR))都是全部使用透明代码编写的。通过限制 Silverlight 的特权部件,我们可以将精力集中到确实需要仔细检查的领域,从而提高所交付的产品的安全性。
基类库
.NET Framework 在桌面上的演变发展是为了解决用户和服务器这两方面的问题。因此,基类库 (BCL) 中的很多功能在 Web 客户端上没有任何意义。例如,由于 Silverlight 不支持 CAS,因此大部分 System.Security 都不是必要的。诸如 System.Console 等许多其他类在 Web 中也没有任何意义(既然如此,为什么还要包含精简的 System.Console 类呢?因为它可以帮助我们测试产品)。
我 们使用库与使用核心引擎的目标是一样的:将功能集减至最少,以便 .NET 开发人员无需全面了解全新技术即可成功使用。.NET Compact Framework 解决了不同情况中出现的同一问题,我们从中获得了一些灵感和指导。虽然我们从 Silverlight 中删除了 BCL,但保留了 .NET Compact Framework 和 Silverlight 间的兼容性。通过此方法在所有平台之间共享一个库,可以在最大程度上重复使用 .NET 技术。
您 可以在 BCL 中的许多地方找到重复的功能。有些功能在 BCL 内部就是重复的,例如,泛型集合与非泛型集合。有时,有些功能已经存在于基操作系统中,如全球化支持。我们不仅不必支持 Silverlight BCL 中的所有替代选项,还可以通过省略此重复行为来提供最佳的性能和一致性。
由 于 .Net Framework 2.0 版本中引入了对泛型集合的支持,所以我们主张人们将注意力转移到泛型上。在运行时的 1.x 版本中,通用数据结构必须基于对象,相同的核心数据结构类才能用来创建不同类型的集合。使用泛型类型参数,编译器可以通过扩展这些通用数据结构来提供类型 安全性,从而使代码更易于编写和维护。此外,相对于非泛型集合来说,泛型集合对值类型执行操作效果更好,因为它不需要框起项目。总之,泛型不但可以提供非 泛型集合提供的所有功能,而且由于不必使用重复,所以 Silverlight BCL 中可以不包含诸如 ArrayList 等非泛型集合。
每 个人或多或少都了解一些全球化问题:许多欧洲地区将逗号用作小数点;中文数字将每四位数分为一组 (1000,0000),等等。.NET Framework 在内部实施全球化功能,因此它可以在多个领域正常运行。为此,它包含所有支持区域的全球化数据,使以 .NET 为目标的应用程序在所有支持的 Windows 版本中行为一致。但是,这也存在一些弊端。CLR 必须包含大型数据表,而这些数据通常会随着时间的流逝而失效。此外,这些数据都以 Windows 为中心,因此某些 .NET 区域中的数据与 Mac OS X 中相同区域不同。鉴于这些原因,CoreCLR 不包含自己的全球化数据。相反,System.Globalization.CultureInfo 可以使用宿主操作系统提供的全球化功能。因此,Silverlight 应用程序在 Mac OS X 中运行时,其行为更像 Mac,而在 Windows 中运行时,其行为更像 Windows 应用程序。
总 之,我们已经尽力在 CLR、.NET Compact Framework 和 Silverlight 之间维护类似的 API 外围应用,但在 BCL 中仍分散着其他细微差异。例如,由于 Silverlight 中有一个单独的 UI 线程,因此它还包含一个单独的 Dispatcher 对象以承载 UI 的工作项队列。借助 Dispatcher,您可以通过非 UI 线程更新 UI。此代码允许您使用在其他线程(如后台线程)中创建的集合更新 UI 元素 — MyListBox:
MyListBox.Dispatcher.BeginInvoke(() => MyListBox.ItemsSource = MyItems);
我 们建议在 Silverlight 中使用 System.ComponentModel.BackgroundWorker,因为它可以在完成时封装更新的 UI,但出于兼容性考虑,我们仍将低级别线程 API 包括在内,如 System.Threading.ThreadPool.QueueUserWorkItem 和 System.Threading.Monitor.Enter。
跟 安全透明模型一样,Silverlight BCL 中的部分新增功能实际上在先前版本的 .NET Framework 中就出现过。独立存储就是其中一个很好的示例,它可为经过沙箱处理的应用程序提供虚拟化文件系统。自 .NET Framework 1.0 推出以来,独立存储就已存在,但它仅适用于几种有限的情况。Silverlight 侧重于经过沙箱处理的应用程序,因此它可以完全利用独立存储:
using (IsolatedStorageFile isoStore =
IsolatedStorageFile.GetUserStoreForApplication())
{
using (StreamWriter writer = new StreamWriter(isoStore))
{
writer.Write("This is an isolated storage file.");
}
}
与 Web 浏览器中的 Cookie 一样,独立存储允许 Silverlight 应用程序跨调用维持其状态。但是,独立存储提供的完全虚拟化文件系统支持目录和文件的创建。虽然独立存储不是为了存储高价值的数据(如密码),但是其存储 位置不明确,而且仅限于拥有存储的应用程序进行访问。
独 立存储的配额由应用程序组定义,以 Silverlight 应用程序的域名为基础。例如,如果两个 Microsoft 应用程序位于 microsoft.com 的目录下,则它们将共享一个应用程序组,也就表示这两个应用程序共享相同配额。默认情况下,为每个应用程序组提供 1MB 的存储区。
但 是,如果某个应用程序需要更多存储空间,它可以通过一个可指定的对话框来提示用户以请求更大配额,例如,microsoft.com 需要将其配额增加到 8MB。那么,用户可以启用或禁用独立存储,还可以在 Silverlight 配置对话框中删除当前使用的独立存储(在对话框中称为应用程序存储)。应用程序组还可以有共享存储区,这就使相关应用程序可以共享其中的数据。
虽然独立存储已经出现一段时间,但只有在 Silverlight 中使用后才引起人们的关注。适用于交互式 Web 应用程序的可配置安全文件系统,不仅可以开发文字处理程序等传统的 Office 应用程序,也可以开发维护大量数据的应用程序,如股票跟踪系统。
跨平台运行
Silverlight 可以在非 Windows 平台上运行。我们与 Novell 有合作,在 Mono 项目的 Moonlight 运行时的整个过程中都支持 Linux。Microsoft 还致力于研究适用于业界领先的 Symbian OS 和 Windows Mobile® 的 Silverlight 版本。Moonlight 可以在 Mono 上运行,而 Silverlight 的移动版本可以在 .NET Compact Framework(其内存占用量比 CoreCLR 低)上运行。但是,Silverlight 的 Mac OS X 版本只能在与 Windows 完全相同的 CoreCLR 上运行。
我们只有在平台适配层 (PAL) 的帮助下,才能达到此目标。此 PAL 是专为在不同平台上运行而编写的 API。它可为错误处理、文件处理、网络服务、线程语义等提供抽象层。PAL 中的功能可以共享 Win32® API 的名称,但实现过程却不同。某些 API 只可以通过 PAL 功能的参数传递到 OS X 功能中,而其他 API 需要使用自定义逻辑才能使 OS X 功能与 Windows API 签名相匹配。CoreCLR 使用的若干 Windows 功能 Mac 中并不具备,因此必须完全在 PAL 中实现(参见图 2)。
图 2 平台适配层(单击图像可查看大图)
许 多 Silverlight PAL 得益于开发 Shared Source Common Language Infrastructure (SSCLI)(也称为 Rotor)所获得的教训。SSCLI 可以在许多 UNIX 类型的平台和 Windows 上运行。基操作系统功能因各个 UNIX 类型平台差异很大。SSCLI PAL 必须同时在微内核(如 Mac OS X 中的 Mach 内核)和单内核上才能运行,并且需要处理线程、异常处理和网络堆栈等不同的操作系统服务。由于 Silverlight 仅适用于 Windows 和 Intel Mac 计算机,因此,我们能够为 PAL 中的许多功能编写特定的 Mac 实现,这对 PAL 的大小和性能很有帮助。
PAL 仅支持运行 Silverlight 必须使用的 Win32 子集,而无需支持注册表、GDI+ 或 COM。我们不能在 OS X 之上实现 Windows,也不能实现足够的 Windows 功能来支持桌面 CLR 的所有功能。将 PAL 限制为仅支持 Silverlight 可以缩小其大小并加快执行速度。
当 您意识到 OS X 与 Windows 之间的差异有多大时,就会了解隐藏操作系统之间的差异是多么棘手的问题。大部分 OS X 使用 Objective C 编写,而其提供的异常处理系统与 C++ 不兼容。CLR 创建的 I/O 线程与工作线程不同。这些 I/O 线程基于 Windows NT®3.5 中引入的 I/O 完成端口,而 OS X 中不存在此类端口。由于 Windows 中存在反斜杠目录分隔符,因此即使像查找文件这样简单的操作在 Mac 上也不一样。
在 CoreCLR 的整个设计和开发过程中,我们侧重于使提供的环境能让开发人员重用现有技术和工具,以便为安全的小型运行库开发更为丰富的内容。大部分决策的驱动力都源自 富 Internet 应用程序的精减方案,但部分设计还得益于过去的工作成果。对 CoreCLR 所做的部分决策将最终在桌面上得到体现。例如,您可以期待桌面 CLR 的下一个版本能够与 CLR 的其他版本并行运行。另外,对改进的安全透明模型所做的大部分更改也会出现在下一版本的 CLR 中。
我 们仔细考虑了哪些操作对基于 Web 的方案有意义,哪些在运行库中并无必要。我们希望自己做出了正确的选择,而且相信您会对我们应如何继续改进提出建议。来体会对 Silverlight 2 进行编写代码的乐趣吧!请密切关注此内容以便将来继续深入探索 CoreCLR。