1. 程式人生 > >.NET Core跨平臺的奧祕[上篇]:歷史的枷鎖

.NET Core跨平臺的奧祕[上篇]:歷史的枷鎖

微軟推出的第一個版本的.NET Framework是一個面向Windows桌面和伺服器的基礎框架,在此之後,為此微軟根據裝置自身的需求對.NET Framework進行裁剪,不斷推出了針對具體裝置型別的.NET Framework版本以實現針對移動、平板和嵌入式裝置提供支援。除此之外,在Windows平臺之外一致遊蕩著一隻特立獨行的猴子(Mono)。.NET平臺看起來欣欣向榮,而實際上卻日薄西山,就在這個時候微軟走了一條唯一正確的道路,那就是基於跨平臺理念重新設計的.NET Core,以及由此驅動地對整個.NET平臺進行全新佈局。

對於計算機從業人員來說,“平臺(Platform)”是一個我們司空見慣的詞語,在不同的語境中它具有不同的語義,比如它可以指代作業系統環境和CPU架構型別,也可以表示硬體裝置型別。經過多年的苦心經營,微軟已經為在Windows平臺下構建了一個完整的支援多種裝置的.NET生態系統。與此同時,通過藉助於Mono和Xamarin,.NET已經可以被成功移植到包括Mac OS X、Linux、iOS、Android和FreeBSD等非Windows平臺。

一、Windows下的.NET

微軟在2002年推出了第一個版本的 .NET Framework,這是一個主要面向Windows 桌面(Windows Forms)和伺服器(ASP.NET Web Forms)的基礎框架。在此之後,PC的霸主地位不斷受到其他裝置的挑戰甚至取代,為此微軟根據裝置自身的需求對.NET Framework作了相應的簡化和改變,不斷推出了針對具體裝置型別的.NET Framework,主流的包括Windows Phone、Windows Store、Silverlight和.NET Micro Framework等,它們分別對移動、平板和嵌入式裝置提供支援。由於這些不同的.NET Framework分支是完全獨立的,這使我們很難開發一個支援多種裝置的“可移植(Portable)”應用。

.NET Framework的層次結構

針對不同裝置.NET Framework的獨立性導致了在很多情況下我們不得不針對具體的裝置平臺進行程式設計,跨裝置平臺程式碼的重用顯得異常困難。為了讓讀者朋友們對這個問題具有深刻地理解,我們從.NET Framework的結構開始講起。從結構組成的角度來講,.NET Framework由如下圖所示的兩個層析構成,它們分別是提供執行環境的CLR(Common Language Runtime)和提供API的FCL(Framework Class Library)。

2-1

CLR之於.NET等同於JVM之於Java,它是.NET虛擬機器。作為一個執行時(Runtime),CLR為程式的執行提供一個託管(Managed)的執行環境,它是.NET Framework的執行引擎,為託管程式的執行提供記憶體分配、垃圾回收、安全控制、異常處理和多執行緒管理等方面的服務。CLR是.NET Framework的子集,但是兩者卻具有不同的版本策略。到目前為止,微軟僅僅釋出了4個版本的CLR,它們分別是1.0、1.1、2.0和4.0,.NET Framework 1.0和1.1分別採用CLR 1.0和1.1,CLR 2.0被.NET Framework 2.0和3.x共享,.NET Framework 4.x下的執行時均為CLR 4.0。

FCL是一個旨在為開發人員提供API的類庫,由它提供的API又可以劃分為如上圖所示的兩個層次。處於最底層的部分被稱為BCL(Basic Class Library),它提供了一系列基礎型別,它們用於描述一些基本的資料型別和資料結構(比如字串、數字、日期/時間和集合等)和提供一些基礎性的操作(比如IO、診斷、反射、文字編碼、安全控制、多執行緒管理等)。在BCL之上的則是面向具體應用型別的API,我們大體上可以將它們劃分為入下三種類型:

  • 面向應用(比如ASP.NET、WPF和Windows Forms等)
  • 面向服務(比如WCF、WF和Data Services等)
  • 面向資料(比如ADO.NET、Entity Framework和LinQ to SQL等)

我們也可以採用另一種方式對FCL進行重新劃分:將面向某種應用或者服務型別(比如Windows Forms、WPF、ASP.NET和WCF等)的部分成為AppModel,那麼整個.NET Framework則具有了如下圖所示的三層結構。

2-2

大而全的BCL

我們知道微軟的.NET戰略是在千禧年提出來的,兩年之後第一個.NET Framework版本和IDE(VS.NET 2002)隨之問世。在之後的10多年中,一系列版本的.NET Framework被先後推出。微軟目前釋出的最新.NET Framework版本為4.7,下圖為你展示了整個.NET Framework不斷升級的演進過程,以及各個版本提供的主要特性。

2-3

上圖勾勒出.NET Framework這些年的發展歷程旨在說明一個問題:作為整個.NET平臺的基礎框架,.NET Framework在不斷升級過程中是自己變得更加強大和完備,但是在另一方面也是自己變得越來越臃腫。隨著版本的不斷升級,構成.NET Framework的應用模型、BCL和執行時(CLR)都在不斷地膨脹(.NET Framework 2.0/3.x和.NET Framework 4.x分別採用CLR 2.0和CLR 4.0),下圖很直觀地說明了這個問題。

2-4

我們知道程式集是.NET最基本的部署單元,不論定義其中的多少型別被使用,CLR總是將整個程式集載入到記憶體中。對於上面介紹的構成.NET Framework的三個層次來說,應用模型是針對具體應用/服務型別的,相應的API通過獨立的程式集來承載(比如ASP.NET的核心框架定義在程式集System.Web.dll中,承載整個Windows Forms框架的程式集則是System.Windows.Forms.dll),所以.NET Framework的各個應用模型是相互獨立的。在開發某種型別的應用時,我們只需要引用應用模型對應的程式集就可以了,也就是說我們開發一個Windows Forms應用,是不需要去引用System.Web.dll程式集的。

但是BCL的絕大部分核心程式碼都定義在mscorlib.dll這個核心程式集中,所以BCL基本上來說是作為一個不可分割的整體存在於.NET Framework之中。.NET Framework需要對執行在本機各種型別的託管程式提供支援,針對所有應用型別的基礎型別均需要定義在BCL中。在很多情況下,我們的應用可能僅僅需要使用到BCL一個很小的子集,但是我們不得不將定義整個程式集都載入到記憶體之中。

一方面BCL總是作為一個不可分割的整體被載入,另一方面其自身的尺寸也在隨著.NET Framework的升級而不斷地膨脹。對於客戶端應用(比如Windows Forms/WPF應用)來說,這應該不算是一個大不了的問題,但是對於移動和服務端應用(包括部署於雲端應用)來說,由此帶來的對效能和吞吐量的響應就成了一個不得不考慮的問題。

理想的BCL消費方式是“按需消費”,我們需要那個部分就載入那個部分。由於作為獨立部署單元的程式集總是作為一個整體被CLR載入到記憶體中,要完全實現這種理想的BCL消費方式,唯一的辦法就是將其劃分為若干小的單元,並分別定義到獨立的程式集中。除此之外,按照模組化的原則對整個BCL進行拆分也是版本升級變得更加容易,如果現有版本具有需要修復的Bug,或者效能需要改進,那麼只需要改動並升級相應的模組就可以了。下圖展示了具有模組化BCL的.NET Framework層級結構。

2-5

多個裝置平臺獨自為政

經過多年的經營,微軟已經為我們構建了一個完整的支援多種裝置的.NET生態系統,從最初單純的桌面平臺,逐漸擴充套件到移動、平板和嵌入式等平臺。裝置執行環境的差異性導致了針對它們的應用不能構建在一個統一的.NET Framework平臺上,所以微軟採用獨立的.NET Framework平臺來對它們提供針對性的支援。就目前來說,除了支援Windows 桌面和伺服器裝置的“完整版 .NET Framework”之外,微軟還先後推出了一系列“壓縮版.NET Framework”,這其中就包括Windows Phone、Windows Store、Silverlight.NET Micro Framework等,它們分別對移動、平板和嵌入式裝置提供支援。

這些.NET Framework並不是僅僅在AppModel層次提供針對相應裝置平臺的開發框架,它們提供的BCL和Runtime也是不同。換句話說,這些.NET Framework平臺是完全獨立的,不同.NET Framework平臺之間的獨立性很直觀地體現在下圖之中。目標平臺的獨立性導致我們很難編寫能夠在各個平臺複用的程式碼,關於這一點我們會在下面一節“複用之傷”中做重點討論。

2-6

二、非Windows下的.NET

儘管微軟自身多年以來基本上都只在Windows平臺下的一畝三分地上進行耕耘,但是.NET 則通過Mono和Xamarin將觸角延伸到其他平臺(Mac OS X、Linux、iOS和Android等)。雖然目前做得並不算完美,但是我們可以說.NET具備跨平臺的能力。

從CLI談起

.NET跨平臺的能力建立在一種開放的標準或者規範之上,這個所謂的標準/規範就是CLI。CLI的制定旨在解決這樣一個問題:由不同(高階)程式語言開發的.NET應用能夠在無需任何更改的情況下運行於不同的系統環境下。要實現這個目標,必需有效地解決這裡涉及到兩種型別的差異,即程式語言的差異執行時環境的差異。程式語言之間能夠實現相互相容、執行時環境能夠得到統一,跨平臺的偉業方能實現。

CLI全稱為Common Language Infrastructure,其中Common Language說的是語言,具體來說是一種通用語言,它旨在解決各種高階開發語言的相容性問題。Infrastructure指的則是執行時環境,旨在彌合不同平臺之間執行方式的差異。Common Language是對承載應用的二進位制內容的靜態描述,Infrastructure則表示動態執行應用的引擎,所以CLI為可執行程式碼和執行引擎確立一個統一的標準。

程式語言有編譯型和解釋型之別,前者需要通過編譯器進行編譯以生成可執行程式碼,CLI涉及的Common Language指的是編譯型語言。要實現真正的跨平臺,最終需要解決的是可執行程式碼在不同平臺之間的相容和可移植的問題,而程式語言的選擇僅僅決定了應用原始檔的原始狀態,應用的相容性和可移植性由編譯後的結果來決定。如果通過不同程式語言開發的應用通過相應的編譯器編譯後能夠生成標準的目的碼,那麼程式語言之間的差異就不再是一個問題了。

按照CLI的規定,用來描述可執行程式碼的是一種叫做CIL(Common Intermediate Language)的語言,這是一種介於高階語言和機器語言之間的中間語言。如下圖所示,雖然程式原始檔由不同的程式語言編寫,但是我們可以藉助相應的編譯器將其編譯成CIL程式碼。原則上講,我們可以設計出新的程式語言並將其加入到.NET大家庭中,只需配以相應的編譯器生成統一的CIL程式碼即可。我們也可以為現有的某個程式語言設計一種以CIL為目標語言的編譯器使之成為.NET語言。CIL是一門中間語言,同時也是一門面向物件的語言,所以對於一個CIL程式來說,型別是基本的組成單元和核心要素。微軟制定了一個名為CTS(Common Type System)的規範為CLI確立了一個統一的型別系統。

2-7

程式語言的差異通過編譯器這個介面卡得以“同一化”,執行環境的差異則可以通過虛擬機器(VM:Virtual Machine)技術來解決。虛擬機器是CIL的執行容器,它能夠在執行CIL程式碼的過程中採用及時編譯的方式將它動態地翻譯成與當前執行環境完全匹配的機器指令。虛擬機器遮蔽了不同作業系統之間的差異,讓目標程式可以不做任何修改的情況下就能運行於不同的底層執行環境中,而CIL實際上是一種虛擬機器語言。

2-8

從實現原理來看,讓.NET能夠跨平臺其實不難,但是讓各種相關的人員參與進行以構建一個健康而完善的跨平臺.NET生態圈則註定不是一件一蹴而就的事情,這裡涉及的利益相關方包括程式語言的設計者,以及設計和開發編譯器、虛擬機器、IDE以及其他相關工具的人,當然還包括廣大的應用開發者。跨平臺.NET生態環境必須建立在一個標準的規範之上,所以微軟為此制定了CLI,然後提交給歐洲計算機制造商協會(ECMA:European Computer Manufacturers Association)並被後者接受,成為了一個編號為335的規範,所以CLI又被稱為ECMA-335(順便說一下,ECMA還接受了微軟為C#這們程式語言制定的規範,即ECMA-334)。

Mono與Xamarin

CLI(ECMA-335)這一開放的規範在.NET誕生的那一刻起就賦予了它跨平臺的基因,但是被烙上Windows這一印記的微軟似乎根本就不曾想過將.NET推廣到其他的平臺,真正完成這一使命了是一個叫做Mono的專案。雖然Mono已經是一個不算年輕的專案了,但是依然有很多人對它不是很瞭解,所以我們不妨來簡單介紹一下它的歷史。

1999年,Miguel de Icaza建立了一家叫做Ximian的公司,這是一家旨在為GNOME專案(這是一個為類Unix系統提供桌面環境的GNU專案,GNOME是目前Linux最常用的桌面環境之一)開發軟體和提供支援的公司。2000年6月,微軟正式釋出.NET Framework,Miguel de Icaza被個“基於網際網路的全新開發平臺”(.NET在釋出的時候被標榜為“a new platform based on Internet standards”)深深吸引。同年11月,微軟釋出了CLI規範(ECMA-335)併為公眾開放了獨立實現的許可,Miguel de Icaza從中看到了商機,因為這實際上為.NET走向非Windows平臺提供了可能。Miguel de Icaza在2001年7月開啟了Mono這個專案,並採用C#作為主要的開發語言(目前支援VB .NET),所以針對CLI和C#的兩個ECMA規範是構建Mono專案的理論基礎,如果訪問Mono的官方網站,我們會發現它是這樣定義Mono的:“Mono is an open source implementation of Microsoft's .NET Framework based on the ECMA standards for C# and the Common Language Runtime.”

Mono的使命不僅僅侷限於能夠將.NET應用正常執行在其他非Windows平臺,它還希望幫助開發人員能夠直接在其他平臺進行. NET應用的開發,所以Mono不僅僅根據CLI為相應的平臺開發了作為虛擬機器的CLR和編譯器,還提供給了IDE和相應的開發工具(被稱為MonoDevelop)。Mono的第一個正式版本(Mono 1.0)在專案開啟差不多三年之後(2004年6月)釋出。

2003年8月,Ximian被另一家叫做Novell的公司收購,後者繼續支援Miguel de Icaza開發Mono專案,在這期間Mono陸續推出了若干Mono 2.x版本。2011年4月,Novell又被另一件叫做Attachmate的公司收購,後者決定放棄Mono,於是Miguel de Icaza帶著整個Mono團隊成立了一個家新的公司,起名為Xamarin。同年7月,Xamarin向原來的母公司Novell拿到了Mono的開發許可。在此之後的幾年內,Xamarin先後釋出了Mono 3.x、Mono 4.0和Mono 5.x,目前的最新版本為5.4。Mono現今的目標是實現.NET 4.5除WPF、WF和部分WCF外的所有特性,目前缺失的部分的開發正在通過一個叫做Olive(Mono的一個子專案)的專案進行著。

在Mono專案的基礎之上,Xamarin開始開發以新公司命名的產品,其中最重要版本當屬2013年2月釋出的Xamarin 2.0。Xamarin 2.0由Xamarin.Android、Xamarin.iOS和Xamarin.Windows組成,它們使我們可以採用C#開發針對Android、iOS和Windows的Native應用。除此之外,Xamarin 2.0還攜帶著一個叫做Xamarin Studio(MonoDevelop的升級版)的IDE以及與一些與Visual Studio整合的工具。2014年5月Xamarin 3.0釋出,作為其核心的Xamarin.Forms為不同平臺的Native應用提供統一的控制元件,也就是說我們利用Xamarin.Forms API開發Native應用可以在無需做任何改變的情況下執行在Android、iOS和Windows上。

2016年2月,微軟和Xamarin宣佈雙方簽署協議達成了前者針對後者的收購。在2016年Build大會上,微軟宣佈將整個Xamarin SDK開源,並將它作為一個免費的工具整合到Visual Studio中,Visual Studio企業版的使用者還可以免費使用Xamarin企業版的所有特性。

綜上所述,由於.NET是建立在CLI這一標準的規範之上,所以它天生就具有了“跨平臺”的基因。在微軟釋出了第一個針對桌面和伺服器平臺的.NET Framework之後,它開始 “樂此不疲” 地對這個完整版的.NET Framework進行不同範圍和層次的 “閹割” ,進而造就了像Windows Phone、Windows Store、Silverlight和.NET Micro Framework的壓縮版的.NET Framework。從這個意義上講,Mono和它們並沒有本質的區別,唯一不同的是Mono真正突破了Windows平臺的藩籬。包括Mono在內的這些分支促成了.NET的繁榮,但我們都知道這僅僅是一種虛假的繁榮而已。雖然都是.NET Framework的子集,但是由於它們採用完全獨立的執行時和基礎類庫,這使我們很難開發一個支援多種裝置的“可移植(Portable)”應用,這些分支反而成為制約.NET發展的一道道枷鎖。至於為什麼“可移植(Portable)”.NET應用的開發如此繁瑣,敬請關注中篇《.NET Core跨平臺的奧祕[中篇]:複用之殤》。