MSDN Magazine > Home > Issues > 2008 > 8 月 >  深究 CLR:使用 CoreCLR 進行 Silverlight 的程式設計
深究 CLR
使用 CoreCLR 進行 Silverlight 的程式設計
Andrew Pardoe
Silverlight™ 2 包含許多 Windows® Presentation Foundation (WPF) UI 架構的變更:新的控制項、豐富的網路 API,以及數位版權管理 (Digital Rights Management,DRM) 的支援。Silverlight 2 的一項主要變更,是能夠使用 Microsoft® .NET 相容語言,來設計 Web 用戶端。本文將著重在 Silverlight 的開發核心:CoreCLR。
過去十幾年出現過許多不同的 Web 程式設計技術,包括 CSS 到 ECMAScript 的變體。其中大部分都與 Web 程式設計工作相關,亦即設計 CSS 程式時學到的技能,並不適用於其他領域。反之,Silverlight 2 可以讓您使用桌面程式設計中的相同 .NET Framework 技能,例如基底類別程式庫 (Base Class Library)、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 引擎
CoreCLR 從 CLR 2.0 版在 2005 年 10 月出貨之後即開始設計。大小和相容性是兩大設計目標:從程式設計師的觀點,撰寫 CLR 的程式碼時應該一律相同,而從使用者的觀點,下載檔必須非常的小。因為 Silverlight 的目標與桌面 CLR 的目標不同,所以我們做了一些變更以簡化 CoreCLR,並縮減了 Silverlight 安裝檔案的大小。但是堆疊底部的一致性是最重要的。行為差異 (即使都是正確的) 在堆疊中的位置越高,越容易發生錯誤。
為了確保相容性,我們在堆疊底部的元件使用相同的程式碼。執行引擎和虛擬電腦都相同。其中包括型別系統和中繼資料、記憶體回收行程 (GC)、JIT 編譯器和執行緒集區,以及執行階段引擎的其他核心部分。
不過,我們也做了某些變更以配合 Web 應用程式情況。例如,由於功能豐富的網際網路應用程式通常只會執行簡單的作業且持續時間很短,所以 JIT 編譯器便著重在縮短啟動時間,而非較複雜的最佳化。同樣地,針對使用類似配置模式之多個工作者執行緒調整過的伺服器記憶體回收模式,對 Web 裝載的應用程式並沒什麼意義。因此 Silverlight 只包含標準的工作站 GC,這已針對互動式應用程式調整完畢。不過 Silverlight 應用程式中所使用的 Microsoft 中繼語言 (MSIL) 和中繼資料,與桌面 Managed 應用程式中使用的完全相同,所以您的應用程式在瀏覽器中的行為,將和使用者桌面上的行為一樣。
Silverlight 並非用於取代桌面 CLR 的事實,則造成核心引擎的最大變更:CoreCLR 將與桌面 CLR 一起在處理序中執行。以前,我們從來無法從同一個處理序中執行兩個 CLR 版本。這之所以困難有幾個原因。其中之一是泛處理序狀態的管理:CLR 的每個執行個體,都會假設它是處理序中唯一的執行個體,因此它是唯一接觸其靜態資料者。如果 CLR 1.1 和 2.0 版中都有變數 staticFoo,而兩個 CLR 版本同時載入同一個處理序中,任一個版本若要寫入變數 staticFoo,都會影響另一個 CLR 的狀態。
全處理序狀態只是最明顯的問題,在一個處理序中同時執行兩個 CLR,還可能造成其他問題。例如,您若是一次執行兩個 GC,要如何避免其中一個 GC 暫停另一個 GC 的執行緒呢?另外,還有一個關於佔用資源的問題:當您將多個 CLR 載入一個處理序時,每個 CLR 都必須載入可能是共同的程式碼,而且每個也都各自要有靜態變數和 Managed 堆積的空間。
有些關鍵情況,會需要一起裝載 CoreCLR 和桌面執行階段。如果 CoreCLR 和桌面 CLR 無法一起執行,將無法寫入裝載網頁瀏覽器控制項的桌面 Windows Form 或 WPF 應用程式,而該控制項可能會瀏覽到使用 Silverlight 的網頁。為了避免此潛在問題,我們可以讓 Silverlight 依賴安裝在您 Windows 電腦上的 CLR:每個 Windows XP SP2 和 Windows Vista® 安裝,都會隨作業系統安裝一個相當新的 CLR。不過,若能夠讓所有的 Silverlight 程式碼都在 CoreCLR 上執行,則不論您的電腦上安裝的是哪一版 CLR (或是在 Mac OS X 的情況下,亦即電腦上不會有任何 CLR!),也都可保證絕對的相容性。所以我們經過了一番努力,讓 CoreCLR 和桌面 CLR 可以在處理序中一起執行,而且我們相信如此可讓使用者享有更美好的 Silverlight 使用經驗。

CoreCLR 安全性模型
核心引擎的另一大變更,是新的安全性模型。請注意,.NET 開發人員以往都是使用程式碼存取安全性 (CAS) 來防止未受信任的程式碼執行需要特殊權限的作業。CAS 的能力很強,但是相當複雜。它可以讓使用者或系統管理員使用權限的集合,來定義程式碼的各種沙箱,然後再將個別組件對應到這些沙箱。針對 Silverlight 應用程式,我們只需要一個沙箱,這相等於 Internet Explorer® 用來在網頁中執行指令碼的沙箱。此一簡化的案例可讓我們移除所有的 CAS 原則。
我們也簡化了強制執行安全性的模型。新的模型是以安全性透明度 (Security Transparency) 為基礎,這是在 CLR 2.0 版所引入的概念。透明度模型的核心,會將您的程式碼分類到三個容器之一:Transparent、SafeCritical 或 Critical 程式碼。Transparent 是程式碼的最低信任層級,無法在電腦上提高權限或存取機密資源或資訊。在 Silverlight 2 中,所有的應用程式碼都是 Transparent。Critical 程式碼是最受信任的程式碼層級,可以透過 P/Invoke 和系統互動,甚至可包含無法驗證的程式碼。在 Silverlight 2 中,所有 Critical 程式碼都必須是 Silverlight 平台的一部分。而 SafeCritical 程式碼則擔任橋樑的作用,可讓 Transparent 程式碼呼叫 Critical 程式碼以存取系統資源。Critical 程式碼可看成是 Windows 的核心 API,Transparent 程式碼是使用者應用程式程式碼,而 SafeCritical 程式碼則是使用者程式碼和核心程式碼之間的 API。
Transparent 程式碼只能呼叫其他 Transparent 或 SafeCritical 程式碼。而 SafeCritical 程式碼可代表使用者程式碼呼叫 Critical 程式碼。SafeCritical 程式碼需要負責將輸入標準化,或整理成標準格式,並為 Critical 程式碼的輸出進行消毒,以保護系統的安全 (請參閱 [圖 1])。
圖 1 在 CoreCLR 中強制執行安全性 (按一下影像以放大圖片)
要將 Critical 程式碼的輸入標準化,比為輸出進行消毒的情況容易理解。例如,如果我的 Web 應用程式想要將檔案寫入到本機磁碟機,可以使用隔離儲存區。但是,您不能允許我的應用程式要求寫入名為 "..\..\..\..\bootmgr" 的檔案,所以要確保輸入的內容採用正規的標準格式。大家很少會認為 Critical 程式碼的輸出,會有安全性的風險。主要的安全性概念在於,避免資訊的揭露,對於減少攻擊風險是非常重要的一點。例如,若我嘗試存取您系統上的某些使用者資訊,卻得到「使用權限被拒」的回應。當我針對不同的使用者重複相同的存取作業時,卻得到「使用者 Bob 不存在」的回應。如果我知道可以得到這兩種回應,就能夠重複嘗試無效的存取,以獲得系統的使用者名稱清單。
簡化的安全性原則對使用 .NET 程式碼的開發人員無疑是一大利多,而且對設計 .NET 程式碼的開發人員也有所幫助。我們已盡量減少 Critical 和 SafeCritical 程式碼。將大部分的程式碼定位成 Transparent,可幫助我們減少需要進行深入安全性檢查的程式碼。我們當然還是必須再檢查 Transparent 程式碼的正確性和安全性,但是至少知道它無法執行任何需要特殊權限的作業。Silverlight 中大部分的程式碼,包括動態語言執行階段 (DLR),都是以 Transparent 程式碼寫成。藉由減少 Silverlight 有特殊權限的部分,可以讓我們專注在真正需要仔細再檢查的區域,以提供更安全的產品。

基底類別程式庫
.NET Framework 在桌面上已演進到可處理使用者和伺服器的作業。因此,基底類別程式庫 (Base Class Library,BCL) 中有許多功能,在 Web 用戶端中並沒有意義。例如,因為 Silverlight 不支援 CAS,所以 System.Security 大部分都用不到。其他許多類別,像是 System.Console,在 Web 上也沒有意義 (那麼我們為何要包含精簡版的 System.Console 類別呢?因為它可幫助我們測試產品)。
我們對程式庫的目標,和對核心引擎的目標相同:盡量削減與簡化功能集,讓 .NET 開發人員不必學習太多新技術就能成功運用。.NET Compact Framework 也處理過相同問題,只是狀況不同,我們從中也得到一些靈感和指引。我們在削減 Silverlight 的 BCL 時,也保持 .NET Compact Framework 和 Silverlight 之間的相容性。以這種方式在所有平台間共用單一程式庫,可實現 .NET 技能的最大重複使用性。
您可以在 BCL 中的許多地方發現重複的功能。有時這些功能只在 BCL 本身當中重複,例如,使用泛用集合和非泛用集合時。有時則是基本 OS 中就有的功能,例如全球化支援。於是,我們沒有必要在 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 使用的是主機 OS 提供的全球化功能。因此,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。
如同安全性透明度 (Security Transparency) 模型一樣,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.");
    }
}
隔離儲存區就像網頁瀏覽器中的 Cookie 一樣,可以讓 Silverlight 應用程式在各引動過程之間保持狀態。不過,隔離儲存區所提供的是完全虛擬化的檔案系統,其中支援目錄和檔案的建立。雖然隔離儲存區並不適用於儲存高價值資料 (例如密碼),不過儲存區位置會模糊化,而且只有擁有儲存區的應用程式能夠存取。
隔離儲存區的配額會由應用程式群組定義,群組會以 Silverlight 應用程式的網域名稱為基礎。例如,位於 microsoft.com 底下之目錄中的兩個 Microsoft 應用程式,都會共用一個應用程式群組,表示這兩個應用程式共用同一個配額。根據預設,會提供 1MB 的儲存區給應用程式群組。
不過,應用程式若是需要更多的儲存空間,可以使用對話方塊向使用者要求較大的配額,例如,microsoft.com 想要將配額增加到 8MB。使用者可以在 Silverlight 組態對話方塊中啟用或停用隔離儲存區,以及刪除目前的使用 (在對話方塊中稱為 [應用程式儲存區] )。應用程式群組也可以有共用儲存區,讓相關的應用程式之間共用資料。
儘管隔離儲存區已存在一段時間,但是使用情形一直不如在 Silverlight 中使用那麼具吸引利。適用於互動式 Web 應用程式的可自訂安全檔案系統,將允許傳統辦公室應用程式 (例如文書處理器) 的開發,或者維護大量資料之應用程式 (例如庫存追蹤系統) 的開發。

跨平台工作
Silverlight 可在非 Windows 平台上執行。我們正在和 Novell 合作,透過 Mono 專案的 Moonlight 執行階段支援 Linux。Microsoft 也正在開發引領產業之 Symbian OS 和 Windows Mobile® 可用的 Silverlight 版本。Moonlight 可以在 Mono 上執行,而行動版的 Silverlight 將可以在 .NET Compact Framework (記憶體佔用資源比 CoreCLR 低許多) 上執行。不過,Mac OS X 版的 Silverlight 則可以在與 Windows 完全相同的 CoreCLR 上執行。
我們借助了平台配接層 (Platform Adaptation Layer,PAL) 才做到這點。PAL 是針對不同平台所撰寫的 API。它可為錯誤處理、檔案處理、網路服務、執行緒語意...等等,提供抽象化的概念。PAL 中的功能共用 Win32® API 的名稱,但是有不同的實作。有些 API 只需要轉送 PAL 功能的參數給 OS X 函式功能,而有些則需要使用自訂邏輯,使 OS X 功能與 Windows API 簽章相配對。CoreCLR 使用的某些 Windows 功能在 Mac 中並不存在,因此必須完全在 PAL 中實作 (請參閱 [圖 2])。
圖 2 Platform Adaptation Layer (按一下影像以放大圖片)
我們在開發 Shared Source Common Language Infrastructure (SSCLI) (也稱為 Rotor) 時,得到的一些經驗,使許多 Silverlight PAL 都因而獲益。SSCLI 可在許多 UNIX 平台和 Windows 上執行。各 UNIX 平台上的基礎 OS 功能差異極大。SSCLI PAL 必須同時在微小的核心 (例如,Mac OS X 中的 Mach 核心) 和龐大的核心上工作,而且必須因應不同的 OS 服務,例如,執行緒、例外處理及網路堆疊。因為 Silverlight 只以 Windows 和 Intel Mac 電腦為目標,所以我們得以針對 PAL 中的許多功能撰寫 Mac 特定的實作,這對 PAL 的大小和效能很有幫助。
PAL 只支援 Win32 的子集,僅足以讓 Silverlight 執行。其中不需要支援登錄、GDI+ 或 COM。我們沒有在 OS X 之上實作 Windows,也沒有實作足夠的 Windows 以支援桌上型 CLR 的全部功能。限制 PAL 只支援 Silverlight,可使其保持精簡和快速。
如果細想 OS X 和 Windows 的差異有多大,就知道要克服作業系統之間的差異是一大難題。OS X 大部分是以 Objective C 寫成,其例外處理系統與 C++ 不相容。CLR 會重新建立 I/O 執行緒,這與工作者執行緒是分開的。這些都是以 Windows NT® 3.5 中引入的 I/O 完成連接埠為基礎,OS X 並沒有。即使是尋找檔案這麼簡單的作業,在 Mac 上也截然不同,因為 Windows 中使用反斜線做為目錄分隔符號。
我們在 CoreCLR 的設計和開發過程中,著重在提供一個可讓開發人員重複使用現有技能和工具的環境,以促進他們為小型、安全的執行階段開發更豐富的內容。我們的決策大多是由精簡化的網際網路應用程式導向,不過有些設計也將因為我們參考過去已完成的工作而獲益。在 CoreCLR 方面所做的某些決策,最終將回歸到桌面應用程式。例如,您可以期待下一版的桌面 CLR 將可以與其他 CLR 版本一起在處理序中執行。另外,下一版的 CLR 中,也將出現為改進安全性透明度 (Security Transparency) 所做的大部分變更。
我們在過程中很謹慎地考慮對 Web 架構有用的部分,以及執行階段中不需要的部分。我們希望所做的選擇是正確的,也相信您會告訴我們可繼續改進之處。請享受使用 Silverlight 2 撰寫程式的樂趣,並繼續觀注本專欄未來對於 CoreCLR 的更詳盡探討。

若有任何疑問或意見,請將郵件寄至 clrinout@microsoft.com

Andrew Pardoe 是 Microsoft 的 CLR 專案經理。他負責的層面包括 Silverlight 和桌面執行階段的執行引擎。他的連絡方式為 Andrew.Pardoe@microsoft.com

Page view tracker