天天看點

使用Mono讓.NET程式跨平台運作從Mono到XamarinMono跨平台的原理開發第一個跨平台程式談談Mono跨平台以後小結

  衆所周知,Unity3D引擎憑借着強大的跨平台能力而備受開發者的青睐,在跨平台應用開發漸漸成為主流的今天,具備跨平台開發能力對程式員來說就顯得特别重要。傳統的針對不同平台進行開發的方式常常讓開發者顧此失彼,難以保證應用程式在不同的平台都有着相同的、出色的體驗,這種情況下尋找到一種跨平台開發的方式将會為解決這個問題找到一種思路。從目前的開發環境來看,Web應該是最有可能成為跨平台開發的神兵利器,可是長期以來Web開發中前端和後端都有各自不同的工作流,雖然現在出現了前端和後端逐漸融合的趨勢,可在部落客看來想讓Web開發變得像傳統開發這樣簡單還需要一定的過渡期。

從Mono到Xamarin

  對Unity3D來說,Mono是實作它跨平台的核心技術。Mono是一個旨在使得.NET在Linux上運作的開源項目。它通過内置的C#語言編譯器、CLR運作時和各種類庫,可以使.NET應用程式運作在Windows、Linux、FreeBSD等不同的平台上。而在商業領域,Xamarin則實作了用C#編寫Android和iOS應用的偉大創舉。Windows10釋出的時候,微軟提出了通用應用UWP的設想,在這種設想下開發者可以直接在最新的Visual Studio中使用C#編寫跨平台應用。最近微軟收購了Xamarin,這一舉措能夠保證Xamarin這樣的商業項目可以和微軟的産品融合地更好。雖然在傳統Web開發中Java和PHP目前占據主要優勢,可是雖然雲計算技術的流行,伺服器成本的降低或許會讓C#這樣優秀的語言更加成熟。我一直堅信技術沒有好壞的差別,一切技術問題的核心是人,是以接下來,我們打算追随着跨平台開發的先驅——Java,最早提出的“一次編寫、到處運作”的偉大思想來探索C#程式跨平台的可能性。

Mono跨平台的原理

  在提到Mono跨平台的時候,我們首先需要引入公共語言基礎(Common Language Infrastructure,CLI)這個概念,CLI是一套ECMA定義的标準,它定義了一個和語言無關的跨體系結構的運作環境,這使得開發者可以用規範定義内各種進階語言來開發軟體,并且無需修正即可讓軟體運作在不同的計算機體系結構上。是以我們可以說跨平台的原理是因為我們定義了這樣一個和語言無關的跨體系結構的運作環境規範,隻要符合這個規範的應用程式都可以運作在不同的計算機體系結構上,即實作了跨平台。針對這個标準,微軟實作了公共語言運作時(Common Language Runtime,CLR),是以CLR是CLI的一個實作。我們熟悉的.NET架構就是一個在CLR基礎上采用系統虛拟機的程式設計平台,它為我們提供了支援多種程式設計語言如C#、VB.NET、C++、Python等。我們編寫的C#程式首先會被C#編譯器編譯為公共中間語言即CIL或者是MSIL(微軟中間語言),然後再由CLR轉換為作業系統的原生代碼(Native Code)。

  好了,現在我們來回答最開始的問題:Mono為什麼能夠跨平台。我們回顧.NET程式運作機制可以發現實作.NET跨平台其實需要這三個關鍵:編譯器、CLR和基礎類庫。在.NET下我們編寫一個最簡單的“Hello World”都需要mscorlib.dll這個動态連結庫,因為.NET架構已經為我們提供了這些,因為在我們的計算機上安裝着.NET架構,這是我們編寫的應用程式能夠在Windows下運作的原因。再回頭來看Mono,首先Mono和CLR一樣,都是CLI這一标準的實作,是以我們可以了解為Mono實作了和微軟提供給我們的類似的東西,因為微軟的.NET架構屬于商業化閉源産品,是以Mono除了在實作CLR和編譯器的同時實作了大量的基礎庫,而且在某種程度上Mono實作的版本與相同時期.NET的版本有一定的差距,這點使用Unity3D開發遊戲的朋友應該深有感觸吧!這就決定了我們在将應用程式移植到目标平台時能否實作在目标平台上和目前平台上是否能夠具有相同的體驗。因為公共中間語言即CIL能夠運作在所有實作了CLI标準的環境中,而CLI标準則是和具體的平台或者說CPU無關的,是以隻要Mono運作時能夠保證CIL的運作,就可以實作應用程式的跨平台。我們可以通過下面這張圖來總結下這部分内容:

開發第一個跨平台程式

  下面我們來嘗試開發第一個跨平台程式,我們使用Visual Studio或者MonoDevelop編寫一個簡單的控制台應用程式,為了減少這個程式對平台特性的依賴,我們這裡選擇System這個命名空間來實作最為基礎的Hello World,這意味着我們的應用程式沒有使用任何除mscorlib.dll以外的庫:

using System;

namespace MonoApplication
{
    class MainClass
    {
        public static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}
           

  因為我們的計算機安裝了.NET架構,是以我們編寫的這個程式會被C#編譯器編譯為公共中間語言CIL,然後再由CLR轉換為Native Code。通常情況下公共中間語言(CIL)會被存儲到.il檔案中,可是在這裡我們在編譯的時候好像并沒有看到這個檔案的生成啊,這是因為這裡生成的可執行檔案(.exe)本質上是公共中間語言(CIL)形态的可執行檔案。這一點我們可以通過ildasm這個工具來驗證,該工具可以幫助我們檢視IL代碼,通常它位于C:\Program Files\Microsoft SDKs\Windows\v7.0A\bin這個位置。下面是通過這個工具獲得的IL代碼:

.method public hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代碼大小       13 (0xd)
  .maxstack  
  IL_0000:  nop
  IL_0001:  ldstr      "Hello World!"
  IL_0006:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_000b:  nop
  IL_000c:  ret
} // end of method MainClass::Main
           

  可以看到這段代碼和我們編寫的程式中的Main方法完全對應,關于這段代碼的含義,大家可以通過搜尋引擎來了解IL代碼的文法。因為我們這裡想要說明的是,這裡生成的可執行檔案(.exe)從本質上來講并非是一個可執行檔案。因為它能否執行完全是取決于CPU的,這和我們直接用C++編寫的應用程式不同,我們知道不同的編譯器如Windows下的VC++和Linux下的GCC都是和硬體緊密相連的,是以我們編譯的程式能夠在各自的平台直接運作,即CPU是認識這些程式的。可是在.NET這裡就不一樣了,因為我們通過C#編譯器即csc.exe編譯出來的檔案,其實是一個看起來像可執行檔案,實際上卻是一個和平台無關、和CPU無關的IL檔案。

  那麼我們就會感到迷茫了啊,平時我們編譯完C#程式輕按兩下就可以打開啊,哈哈,現在隆重請出.NET程式的家長公共語言運作時(CLR)。公共語言運作時實際上是程式運作的監管者,程式運作的情況完全由運作時來決定。我們輕按兩下這個檔案的時候,公共語言運作時會将其加載到記憶體中,然後由即時編譯器(JIT)來識别IL檔案,然後由CPU去完成相應的操作。

  是以我們可以這樣了解.NET程式跨平台,因為IL檔案是一個和平台無關、和CPU無關的、跨平台的檔案結構,是以我們隻需要在不同的平台上實作這樣一個公共語言運作時(CLR)就可以實作在不同的平台上運作同一個程式。但這個過程中,需要有一個C#編譯器負責将C#代碼轉換為IL代碼,然後需要有一個公共語言運作時(CLR)來解析IL代碼。與此同時,我們在.NET架構下使用了大量的基礎類庫,這些類庫在Windows以外的平台是沒有的,是以除了C#編譯器和公共語言運作時以外,我們還需要基礎類庫。現在大家是不是對Mono有了更清楚的認識了呢?沒錯,Mono所做的事情其實就是我們在讨論的這些事情。這裡部落客想說說即時編譯(JIT)和靜态編譯(AOT),這兩種編譯方式我們可以按照”解釋型”和”編譯型”來了解,為什麼Unity3D在iOS平台上做熱更新的時候會出現問題呢?這是因為iOS平台考慮到安全性禁止使用JIT即時編譯,是以像C#這種需要編譯的語言在這裡就無計可施了。

  好了,既然我們有Mono這樣的工具能夠幫助我們實作跨平台開發。那麼我們現在就來考慮将這個程式移植到Linux平台,這裡以Linux Deepin為例,我們按照C#程式編譯的過程來完成這個移植過程:

* 1、将C#程式編譯為IL檔案:在.NET下我們使用csc.exe這個程式來完成編譯,在Mono下我們使用mcs.exe這個程式來完成編譯,這個程式在安裝完Mono以後在其安裝目錄内可以找到。我們在指令行下輸入指令:

mcs D:\項目管理\CSharp\MonoApplication\MonoApplication\Main.cs
           
  • 2、這樣将生成Main.exe這樣一個IL檔案,現在我們需要一個運作時來解析它,在.NET下我們使用CLR來完成這個步驟,在Mono下我們使用mono.exe這個檔案來完成這個步驟。我們在指令行下輸入下列指令:
mono D:\項目管理\CSharp\MonoApplication\MonoApplication\Main.exe
           
使用Mono讓.NET程式跨平台運作從Mono到XamarinMono跨平台的原理開發第一個跨平台程式談談Mono跨平台以後小結

我們可以看到指令行下輸出了我們期望的Hello World,這意味着我們編寫的程式現在運作在Mono中了,實際上在Windows下由Mono提供的C#編譯器mcs.exe編譯的IL檔案輕按兩下是可以直接運作的,因為我們的計算機上安裝了CLR,它作為.NET的一部分内置在我們的計算機中。由此我們會發現一個問題,我們這裡的跨平台實際上是編譯器、運作時和基礎類庫這三部分的跨平台,這意味着我們在Linux下運作.NET程式是需要Mono提供支援的。因為在這裡我無法在Linux離線安裝Mono,是以Linux下運作.NET程式的驗證需要等部落客以後有時間再來更新啦!可是我們可以想象到,通過C#編譯器編譯得到的可執行檔案在Linux下是無法正常運作的,因為通常情況下Windows程式在Linux下運作是需要虛拟機環境或者Wine這樣的軟體來支援的,顯然讓這樣一個Windows程式運作在Linux環境下是因為我們在Linux下安裝了Mono。

談談Mono跨平台以後

  好了,到現在為止我們基本理清了Mono跨平台的原理。我們知道微軟的技術體系在發展過程中因為某些曆史遺留問題,.NET程式在不同的Windows版本中的相容性有時候會出現問題,雖然微軟宣布Windows XP停止維護,我們編寫Windows應用程式的時候可以忽略對Windows XP版本的支援,可是因為國内使用者不喜歡線上更新更新檔的這種普遍現狀,是以假如讓使用者在安裝程式的時候先去安裝.NET架構一定會降低使用者體驗,其次.NET架構會增加應用程式安裝包的大小,是以我們需要一種能夠讓我們開發的.NET應用程式在脫離微軟的這套技術體系時,同時能夠安全、穩定的運作,是以我們這裡考慮借助Mono讓.NET程式脫離.NET架構運作。

  首先,我們來說說.NET程式為什麼能夠脫離.NET架構運作,我們注意到Mono提供了一個Mono運作時,是以我們可以借助這樣一個運作時來運作編譯器生成的IL代碼。我們繼續以Hello World為例,我們在使用Mono編譯出IL代碼以後需要使用Mono運作時來解析IL代碼,是以假如我們可以編寫一個程式來調用Mono運作時就可以解決這個問題。在這個問題中,其實精簡應用程式安裝包的大小從本質上來講就是解決基礎類庫的依賴問題,因為Mono實作了.NET架構中大部分的基礎類庫,是以移植.NET應用程式的關鍵是基礎類庫的移植,比如WinForm在Linux下的解決方案是GTK,這些細節在考慮跨平台的時候都是非常重要的問題。

小結

  本文從Mono跨平台的原理說起,探讨了.NET應用程式跨平台的可能性和具體實作。跨平台是一個涉及到非常多内容的話題,我個人了解的跨平台是要編寫跨平台的代碼,這意味着我們在編寫程式的時候需要考慮減少對平台特性的移植,比如說Linq是一個非常棒的特性,可是這個特性離開了Windows、離開了.NET就沒有辦法得到保證,是以如果要讓使用了Linq的應用程式跨平台就會是一件非常麻煩的事情!在不同的平台間保持相同的體驗很難,就像我們編寫的Web程式在不同的浏覽器間都有着不一樣的表現,是以跨平台這個問題我們就抱着學習的态度來研究吧!

繼續閱讀