微軟在 MSDN 博客上宣布了 .NET Native 的開發者預覽版。.NET Native 可以將 C# 代碼編譯成本地機器碼。有了它,開發者將不僅能享受 C# 的高生產力,而且能擁有 C++ 般的性能。魚與熊掌不可兼得,而有了 NET Native,我們都可以兼得 C# 的生產力與 C++ 的戰斗力。使用 .NET Native 編譯 Windows 商店應用程序,啟動速度將會加快 60%,同時占用內存的內存也更少。
目前開發者可以使用該開發者預覽版構建基於 ARM 或 x64 架構的 Windows 商店應用程序(很快就會支持 x86 架構,敬請期待)。.NET Native 很快將會實現跨設備的無縫體驗。今天的預覽版只支持 Windows 商店應用程序,我們將繼續努力改進 .NET Native 以支持所有 .NET 應用程序的本地編譯。
根據微軟提供的 FAQ,.NET Native 預覽版目前只支持 C# 語言,用 .NET Native 編譯的程序將作為獨立的原生編譯代碼部署到目標機器上,運行不再需要安裝 .NET Framework。
這裡簡單講一下.NET Native的基本架構,基本內容和上面那個talk其實差不多,只是簡單給大家講一下.NET Native的一些基本概念。如果大家有對某個部分具體感興趣,也可以提出來,我盡量在我能力范圍內解答(畢竟不是我一個人做的,呵呵)或者寫新的Blog詳細解釋。
.NET Native和之前的NGEN有本質區別。NGEN實際上是把CLR運行時的數據結構和代碼給一鍋端(當然,這個是簡化的說法,實際上比這個復雜)的放到最終的PE裡面去了,運行的時候還是需要整個.NET Framework支持,而且不能避免JIT。.NET Native是全新的技術,整個.NET Framework經過refactoring重寫,最終的runtime非常小,只有數百K,無需任何安裝(除了mrt100.dll之外,大家可以理解成msvcrt.dll)。大部分的功能都從runtime中refactor到framework中作為C#代碼或者作為toolchain一部分存在。一個典型的例子是:P/invoke原來是由CLR實現,使用C++和匯編編寫。而現在是經由MCG這個工具接手,直接生成C#。最終.NET Native生成的EXE/DLL是可以直接運行的機器碼(通過C++編譯器後端生成)。對了,有些朋友可能會問:我們是不是直接生成C++代碼?答案是否定的。我們所使用的C++編譯器後端接受IL作為輸入,生成MDIL。
整個Toolchain(工具鏈)大致可以分為下面幾個階段:
App IL + FX -> MCG -> Interop.g.cs -> CSC -> Interop.dll -> Merge -> IL transform -> NUTC -> RhBind -> .EXE
第一步:將應用程序的IL代碼和整個.NET Framework BCL的IL一起作為輸入給MCG。MCG (Marshalling Code Generator)這一塊主要是我在負責。這個工具負責檢查程序和BCL中所有的Interop相關的類型,比如WinRT接口,P/Invoke,等等。MCG都會為之生成C#代碼。這個C#代碼是可以直接調試的,有興趣的朋友可以F11試一下看看。C#代碼的作用主要是替代Windows.WinMD中的WinRT類型定義,P/invoke定義,等等,添加各種類型的轉換代碼,比如字符串類型,RCW和CCW,等等,最終直接調用到本地代碼。可能有些朋友會問道:為什麼要生成C#? 原來的CLR是直接在運行時生成IL代碼的,但是顯然這個方法在.NET Native不太適用,而且IL代碼很難調試。C#既方便大家調試,也方便我們快速的修改生成的代碼,添加更多的功能。(寫C++程序生成IL代碼可是比較麻煩的,得人工算好stack的位置)
第二步:MCG生成的C#代碼通過CSC編譯,生成PE文件。這一步沒啥可講的。
第三步:這個PE文件被打包合並到應用程序和BCL,生成一個IL代碼的集合。為下一步做好准備。
第四步:這個IL代碼的集合會被經過若干的步驟處理,每個步驟都相對簡單,只做一件事情。這些步驟的主要作用是提供原來CLR運行時提供的功能,最終的目的是使之最後的代碼能夠被C++最後編譯。在原來桌面版本的CLR裡面(也就是4.5裡面的那個),很多功能是由Runtime來提供,比如Delegate.Invoke,比如interop。這些Transform的作用是對代碼進行處理,把原來需要runtime實現的部分用實際代碼替換掉。舉個幾個例子:
1. 當你在調用Windows.UI.Xaml.Controls.Button類型的時候,MCG也會生成一個對應的Button類型,然後IL Transform會將兩者進行替換,這樣程序調用的Button類型就是MCG生成的代碼了。
2. 當你在進行Serialization和Deserialization的時候,IL Transform會調用另外一個工具SG來生成serialization/deserialization的C#代碼,最終這些操作都有這些C#代碼生成。
3. 你的程序多半不會用到整個BCL。IL Transform中會有一步叫做Dependency Reducer,使用類似GC的算法(mark->sweep),去掉不需要的代碼。MCG也和DR通力合作,減少不必要的interop代碼生成。DR也會讀取RD.XML文件,決定那些類型需要反射信息,那些不需要。RD.XML這一塊我們還在改善之中,也希望大家多提寶貴意見。
其實呢,MCG其實也是IL Transform的一部分,只不過它實際上不Transform而已,而是直接生成C#。
第五步:NUTC對IL進行處理,生成MDIL。NUTC就是傳說中的C++的編譯器後端的一個特殊版本,優化什麼的就靠它了。最後生成的MDIL接近機器碼,但是也包含一些抽象的類型信息,需要進一步處理。
第六步:RhBind負責對MDIL進行處理,將裡面和類型系統相關的信息生成代碼,最後生成一個EXE。其實最終是一個EXE+DLL,實際的代碼都在DLL中。EXE只是起到Bootstrap的作用。選用DLL的原因是我們需要支持作為Background Task在Broker裡面加載。
幾個我聽到的常問的問題(如果有些答案過於“官方”,請諒解):
1. 你們最後是生成C++代碼嗎?
答:不生成。C++後端直接從IL轉換成MDIL。
2. WPF支持嗎?
答:暫時不支持。目前暫時只支持Windows Store Apps
3. 這個會支持桌面程序嗎?
答:目前暫時只支持Windows Store Apps
4. 這個支持JIT嗎?
答:目前.NET Native不支持JIT,所有代碼都是編譯時候生成。
5. 既然是本地機器碼,為什麼還可以支持類型反射(reflection)?
答:機器碼和反射並不沖突,我們在PE文件中儲存了額外的用於反射的信息,然後動態讀取此信息進行調用。C++也可以支持反射(RTTI),只是不如.NET強大而已。
6.這個需要安裝.NET Framework嗎?
答:開發編譯的時候需要,運行時不需要。
7.為什麼不支持VB
答:VB本質上和C#都是生成IL,技術上非常類似。只是目前我們因為時間問題,暫只支持C#。
8.為什麼啟動運行速度會變快?
答:一方面歸功於C++的優秀的編譯器後端,一方面也因為runtime的重寫和簡化