1.2 Rotor:一個CLI的共享源代碼實現
1.2.1.
2001的夏天,微軟雷蒙德總部的一個team宣佈要啓動一個難得的項目。這個項目就是Shared Source CLI(SSCLI,代號“Rotor”),項目的範圍涵蓋了一個全功能的CLI執行引擎、C#編譯器、基礎類庫以及一系列相關開發工具。和商業版本的.NET框架一樣,它們都是微軟開發工具戰略的重要部分。SSCLI有三大目標:驗證CLI標準的可以執性;幫組人們更好的理解商業版本的.NET;使學術界對CLI保持長期的興趣。最重要的是,SSCLI是完全符合ECMA標準的,這爲任何希望實現CLI標準的人提供了指南。
雖然主題講的是SSCLI,不過CLI標準纔是核心內容。SSCLI只是用來幫助我們更好的理解CLI標準的how和why。本書試圖爲需要在CLI理論之上理解和hacking代碼的人提供指南。在將來CLI會變得更加重要,沒有比直接瀏覽、構建、觀察並進行調整CLI實現更好的方式來理解它了。
Rotot展示了一種構建可移植地、語言無關的CLI的方式,當然這絕不是唯一的方式。在本書的寫作時,微軟已經發布了兩個CLI實現(商業版的.NET Framework以及Compact Framework)。還有兩個第三方的開源實現,一個來自Ximian(Mono),還有一個來自DotGNU項目(Portable.NE)。相較而言Rotor提供了更豐富的開發工具,並且在標準的基礎之上提供了更多的特性。圖1-3展示了Rotor和微軟商業版本CLI(.NET CLR)以及C#之間的差異。
如圖1-3所示,SSCLI是CLI的一個超集,而微軟所提供的商業版本的CLI又是SSCLI的一個超集。
Rotor本身是一個開發了多年的大項目,擁有衆多代碼,體系結構複雜多變。在規模上,Rotor可以和任何類似的開源項目如XFree86、Mozilla還有OpenOffice相媲美。其代碼量龐大地讓人望而卻步。所以本書將幫助我們更容易的上手。
圖 1-3 Shared Source的組件
SSCLI同時使用了C++和C#混合編寫而成。要構建他的話,需要分三步進行。首先,使用C++編譯器對用來隱藏底層操作系統細節的平臺適配層(PAL)進行編譯。然後,在PAL的基礎上進行生成C#編譯器等工具。最後,基於上述工具(C#編譯器)和PAL編譯剩下的部分。
表 1-1 列舉了SSCLI源代碼的相關目錄,本書的隨書CD中包含了所有這些代碼(也可以從http://msdn.microsoft.com/net/sscli下載)。
· CLI執行引擎
· 對執行引擎的擴展和增強的組件框架
· Tools、測試代碼和工具、各種編譯器、文檔、utilities
1.2.1.1 CLI執行引擎
執行引擎是CLI的心臟。它包含組件模型和一組運行時服務,如異常處理、自動化的堆棧管理等。在許多方面,它就像一個大kahuna。執行引擎中包含了我們提到過的“運行時”或“虛擬執行環境”的代碼。JIT編譯、內存管理、程序集和類的加載、類型解析、元數據解析、堆棧行走和其他的基本機制都是在執行引擎中實現的。可以在sscli/clr/src目錄和vm、fjit、md、fusion中找打執行引擎的大部分代碼。
圖 1-4 衆多庫合在一起才能運行託管代碼
如圖1-4中的所示,執行引擎由一系列的動態鏈接庫組成而不是單個的可執行體。clix程序啓動器(或者任何希望使用執行引擎的程序)加載主共享庫:sscoree在進程中創建CLI實例並提供一個啓動程序集供執行。執行引擎中並沒有主模塊,它們是用來託管其他程序。執行引擎依賴於許多其它的共享庫,爲了保證各部分的獨立性它們都被打亂了。比如mscorsn中的加密代碼對加載和簽名程序是必需的。還有一些可能在其它地方非常有用,比如rotor_pal 和rotor_palrt 被在PAL用到。最後,有些不是一定會用到的代碼也被打包進來了,比如爲調試提供支持的mscordbc。
1.2.1.2 CLI中的庫
SSCLI中不僅僅只包含規範所規定的,比如元數據、通用中間語言和通用類型系統等底層功能,還提供了一些面向生產的高級功能的庫。表1-2按照功能列出了這些庫。
這些庫提供了一些使用底層操作系統相關功能的接口,在某種程度上它們是爲CLI專門提供的,目的是提供程序員的生產力以及產品質量。
這些API還有一個不太明顯的作用:它們展示使用集成組件進行開發的便捷性,使用它們可以更容易的生產出更好的組件。而服務,最大程度的減少了組件開發者需要實現的功能;或者說最大程度的降低了管理各種組件的複雜度,使整合組件更加方便安全(也減少了代碼量)。當組件越少依賴於其它組件提供的功能,或者當組件需要爲其他組件提供功能越少時,程序的bug就會越少。爲了實現前面所說的各種好處,組件需要在一個專門設計的環境中以託管的方式執行。
有人可能會想CLI庫就像一個更先進的C運行時庫。不過CLI並沒有試圖提供可供所有程序員所需要的一切功能,反而只是提供了一組幾乎是對所有程序員都有用的組件。sscli/clr/src/bcl中的基礎庫作爲CLI規範的一部分所有的CLI都必須實現,它們是實現可移植這個目標的基礎。另外一些庫,位於sscli/fx,、sscli/clr/src/classlibnative,和 sscli/managedlibraries中,是可選的標準庫或者只是SSCLI的實現。微軟發佈的商業版本的.NET框架囊括了SSCLI中的所有庫。
|
1.2.1.3 平臺適配層
咋看之下PAL是一個非常有趣的部分。當然,就和任何其它適配或驅動層一樣,就是一堆爲了讓程序能在所有操作系統上運行的代碼。PAL的首要目標是對上層隱藏各種操作系統的實現細節。對SSCLI而言這個選擇就顯而易見了:特定於Win32 API,SSCLI的PAL被設計成Win32 API的子集(參見sscli/pal/rotor_pal.h)。也就是說並不是完全實現Win32 API,因爲它只需要提供CLI指定的調用接口。不要將PAL層看作一個通用的Win32模擬層,因爲它是不完整的。
PAL層使得Rotor具有跨平臺的能力,因爲用來構建Rotor的工具和資源都是基於PAL層的。有關的資源可以在sscli/pal/unix目錄中找到。PAL層還做了大量工作來提供通用的異常處理、線程、IO、同步、調試以及更多其它特性。某些宿主進程,比如Web服務器和數據庫,很可能有其自己的運行時機制,這也是PAL需要考慮的因素。由於這個原因以及PAL的定義是如何使用操作系統及相關資源,所以理解PAL的實現非常重要。
除了PAL,在sscli/palrt/src文件夾中有一個實現了SSCLI所需的Win32 API的庫,但並不依賴於操作系統的實現。這個庫也包含一組特定於PAL的API。比如小數運算,一個COM組件模型的stub實現、數組處理、內存管理以及許多其它的工具函數。
PAAL最有趣的方面是與執行引擎控制方面有關。SSCLI被設計成可以和本地代碼在本地進程中同時運行,這就意味着許多操作系統的系統調用需要被執行引擎捕獲以便獲得運行時所需的相關信息,比如垃圾回收或安全控制系統。PAL層的關鍵作用是:SSCLI的實現是基於PAL所提供的抽象,沒有它就不可能保證代碼隔離、安全檢查和運行時控制。例如,線程和異常處理都是在PAL中實現的,它們對執行引擎的運行而言都是至關重要的,因爲後者使用異常幀來跟蹤託管代碼,並使用線程相關的堆棧來存放各種數據。PAL方面的詳情將在第六章中討論,第九章將討論PAL的設計。
1.1.2.4 Tools、各種編譯器、測試代碼和工具、文檔和Utilities
Rotor中有相當比例用以進行構建、測試和用以實現CLI的支持代碼。比如前面討論過的PAL就是如此。可以在Rotor中找到有大量的開發工具、utilities和測試代碼。一類是是用來做開發管理的工具,還有一類是用來進行構建的。
就開發管理而言,Rotor中的許多工具都是Microsoft .NET框架平臺的開發人員的所熟悉的,因爲兩者都是用了一些共享的utilities,比如鏈接器、彙編器以及反彙編器。sscli/clr/src, sscli/clr/src/tools以及sscli/clr/src/toolbox目錄包含了這些utilities,以及SSCLI中爲開發和運行所提供的特有代碼,比如clix.exe。程序員可以從sscli/docs找出哪些功能是Rotor和.NET框架所共有的,哪些不是通用的。
用來構建Rotor的工具可以再sscli/tools中找到。這些工具基於PAL構建並用以跟蹤各種依賴關係、啓動構建進程、組裝庫和可執行體,最後將構建結果放入sscli/build目錄。Rotor各組件之間的關係是錯綜複雜的,它們彼此相互關聯,因此這些工具非常重要。
一旦構建SSCLI成功,能夠使用sscli/tests目錄中的代碼對其進行測試。特別值得注意的是對PAL的測試,可以在sscli/tests/palsuit中找到。可以用來驗證PAL的實現是否正確,或者修改PAL後驗證修改是否正確。sscli/tests/bvt目錄中的開發人員構建驗證測試(BVT)可以用來驗證執行引擎是否構建完成。也有一些其它方面的測試,比如針對基礎類庫的測試。大部分測試和BVTs及測試工具一起都在sscli/tests/harness目錄中,相關文檔則可參看sscli/docs/testing_overview.html。
Rotor的文檔和技術說明都放在sscli/docs目錄中。對於瀏覽和修改代碼、理解CLI架構及SSCLI在具體實現原理都是非常有用的參考材料。也有一份關於PAL的詳細規格說明書,這對於移植Rotor非常有用。花一些時間看看這些文檔還是非常值得的。
1.2.2 本書的範圍
本書着眼於如何實現CLI組件模型及其底層執行引擎在SSCLI中的實現。由此我們將對操作系統原理、通用可移植方面的問題進行一些簡短的討論。至於對於編譯器、語言和框架將不作討論。而CLI非面向組件方面的用途也不會被討論,因爲我們可以找的許多.NET框架和CLI方面的書。
在此非正式的聲明:本書中採用的衆多來自SSCLI源碼的C++代碼基本都被整理過,換成了僞代碼。一些的宏被刪掉了,並在真是代碼中加入了錯誤處理以及一些斷言,代碼更具有可讀性。如果你計劃在SSCLI添加代碼或對其進行修改,應該和SSCLI保持一致的編碼規範和錯誤處理方式。附錄D在這方面進行了一些簡短的描述。
1.2.3 總結
CLI是第一個在底層設計上就考慮了多語言的虛擬執行環境。平臺提供者、框架構建者以及程序員不再需要爲採用何種語言糾結了。反而會從CLI獲得異常處理、垃圾回收、反射、代碼安全以及可擴展的數據驅動元數據架構。由於增強的互操作和共享基礎架構,使用CLI能夠很容易在已有代碼的基礎上進行組件式開發。
CLI的標準格式使得打包和部署CLI組件既不依賴於特定操作系統也不依賴於特定編程語言。這個非常重要,因爲CLI的數據驅動元數據架構是基於這種格式的。數據驅動的元數據提高了程序員的生產效率,它使得各種技術、庫和工具可以無縫的集成。數據驅動的元數據也保證了組件可以永不過時。
抽象的指令集和類型系統爲CLI帶來了一個非常誘人的特性:軟件可以隨處運行。CLI的設計者當然希望有很多個CLI實現可以在多個不同的平臺上運行。不過現實情況是每個CLI實現都提供樂於提供一些特有的框架、服務、工具或者其它的語言特性。這就導致現實中的CLI和C語言很類似,很少有隻使用了標準庫所提供的功能的應用,而是既使用標準庫也使用一些平臺特定或者可以跨平臺的非標準庫。類似的,大多數CLI程序也將既使用標準組件也使用一些平臺特定或者跨平臺的第三方組件。
CLI的語言無關特性、數據驅動元數據架構以及虛擬執行模型提供了一個可供組件之間有效的合作而不用犧牲安全性和自主性的舞臺。元數據爲可供擴展組件行爲和注入安全特性的提供了基礎。CLI執行模型中的每個步驟都會從上一步驟獲取相應數據,進行轉換或者添加一些新數據,然後傳給下一步驟。本書描述了執行引擎執行過程中的所有步驟,從最初的引導開始直到最後一個託管資源的銷燬。