關於託管與非託管

什麼是託管代碼?

託管代碼就是Visual Basic .NET和C#編譯器編譯出來的代碼。編譯器把代碼編譯成中間語言(IL),而不是能直接在你的電腦上運行的機器碼。中間語言被封裝在一個叫程序集(assembly)的文件中,程序集中包含了描述你所創建的類,方法和屬性(例如安全需求)的所有元數據。這個程序集是.NET世界中的一個一站式購物(譯者注:就是程序集具有自描述性)部署單元。你可以拷貝這個程序集到另一臺服務器上部署它--通常來說,這個拷貝的動作就是部署流程中唯一的一個操作。

託管代碼在公共語言運行庫(CLR)中運行。這個運行庫給你的運行代碼提供各種各樣的服務,通常來說,他會加載和驗證程序集,以此來保證中間語言的正確性。當某些方法被調用的時候,運行庫把具體的方法編譯成適合本地計算機運行的機械碼,然後會把編譯好的機械碼緩存起來,以備下次調用。(這就是即時編譯)

隨着程序集的運行,運行庫會持續地提供各種服務,例如安全,內存管理,線程管理等等。這個程序被“託管”在運行庫中。

Visual Basic .NET和C#只能產生託管代碼。如果你用這類語言寫程序,那麼所產生的代碼就是託管代碼。如果你願意,Visual C++ .NET可以生成託管代碼。當你創建一個項目的時候,選擇名字是以.Managed開頭的項目類型。例如.Managed C++ application。

什麼是非託管代碼?

非託管代碼就是在Visual Studio .NET 2002發佈之前所創建的代碼。例如Visual Basic 6, Visual C++ 6, 最糟糕的是,連那些依然殘存在你的硬盤中、擁有超過15年曆史的陳舊C編譯器所產生的代碼都是非託管代碼。託管代碼直接編譯成目標計算機的機械碼,這些代碼只能運行在編譯出它們的計算機上,或者是其它相同處理器或者幾乎一樣處理器的計算機上。非託管代碼不能享受一些運行庫所提供的服務,例如安全和內存管理等。如果非託管代碼需要進行內存管理等服務,就必須顯式地調用操作系統的接口,通常來說,它們會調用Windows SDK所提供的API來實現。就最近的情況來看,非託管程序會通過COM接口來獲取操作系統服務。

跟Visual Studio平臺的其他編程語言不一樣,Visual C++可以創建非託管程序。當你創建一個項目,並且選擇名字以MFC,ATL或者Win32開頭的項目類型,那麼這個項目所產生的就是非託管程序。

這樣子會導致一些混淆:當你創建一個託管的C++程序,那麼構建出來的是一箇中間語言程序集和一個擴展名爲.exe的可執行文件。當你創建一個MFC程序,構建出來是一個Windows原生代碼的可執行文件,這個文件的擴展名也是.exe。這兩個文件的內部結構是完全不一樣的。你可以用中間語言反彙編器(ildasm)來查看程序集的內部以及中間語言的元數據。如果嘗試用中間語言反彙編器來查看一個非託管可執行文件,那麼改反彙編器會告訴你這個可執行文件沒有包含一個合法的CLR頭,所以不能被反編譯。可見,這兩個文件雖然有相同的擴展名,但是它們是完全不一樣的。

 

原生代碼又是什麼呢?

原生代碼這個短語可以用在兩個不同的上下文中。很多人會把原生代碼跟非託管代碼看作是同一個意思:用較老的工具構建的代碼,故意採用Visual C++並使直接運行在計算機上,而且不運託管在運行庫中。這可以是一個完整的程序,或者是一個COM組件,又或者是一個可以被託管代碼利用COM Intero或者平臺調用(PInvoke)所調用的DLL文件,COM Intero或者平臺調用(PInvoke)可以幫助你在遷移到新的技術平臺下依然能重用老代碼的兩個強大工具。我更願意說是非託管代碼,因爲這強調的是那些不能利用運行庫所提供的服務的代碼。例如在託管代碼中,代碼訪問安全服務可以防止在另一個服務器上裝載的代碼運行特定的操作。如果你的代碼運行的是非託管代碼,那麼你沒法利用這樣的保護服務。

原生代碼的另一個意思是描述即時編譯器的輸出,那些實際上運行在運行庫中的機械碼。這些代碼是託管代碼,但是並不是中間語言,而是機械碼。所以不要簡單地假設原生就是等同於非託管

託管代碼就意味着託管數據?

對於Visual Basic和C#來說,生活是簡單的,因爲你沒有其它選擇。當你在那些語言裏面聲明一個類,那麼這個類的實例會在託管堆中被創建,垃圾收集器(GC)會幫我們管理這些對象的回收。但是在Visual C++中,你有另一個選擇。即使你正創建一個託管程序,你可以決定哪些類是託管類型,哪些類是非託管類型的。

這就是非託管類型:

class Foo
{
   private:
      int x;
   public:
      Foo(): x(0){}
      Foo(int xx): x(xx) {}
};

這就是託管類型

__gc class Bar
{
   private:
      int x;
   public:
      Bar(): x(0){}
      Bar(int xx): x(xx) {}
};

他們唯一的區別就是類Bar的定義中有__gc關鍵字。這個關鍵字會給代碼帶來巨大的區別。

託管類型是可以被垃圾回收器所回收的。他們必須要用關鍵字new來創建,永遠都不會在棧中出現。所以下面這行代碼是合法的:

    Foo f;

但是這一行代碼就是非法的:

    Bar b;

如果我在堆中創建一個Foo對象,那麼我必須要負責清理這個對象:

    Foo* pf = new Foo(2);
    // . . .
    delete pf;

C++編譯器實際上會用兩個堆,一個託管堆和一個非託管堆,然後通過對new操作符的重載來實現對創建不同類型類的實例,分配不同的內存。

如果我在堆裏面創建一個Bar實例,那麼我可以忽略它。當沒有其他代碼在使用它的時候,垃圾回收器會自動清理這個類,釋放其佔用的資源。

對於託管類型會有一些約束:它們不能實現多重繼承,或者繼承與非託管類型;它們不能用friend關鍵字來實現私有訪問,它們不能實現拷貝構造函數。所以,你有可能不想把你的類聲明爲託管類型。但是這並不意味着你不想讓你的代碼成爲託管代碼。在Visual C++中,你可以選擇。

 

 

託管和非託管資源,是C#中的事,就不在這討論了。

 

 

      基本上每個人都知道的是,所有.Net語言都將被編譯成爲一個叫做IL彙編的中間語言。但是計算機是如何執行這個中間代碼的,卻是很多人不知道,甚至理解錯誤了的。
      JIT是.NET程序運行的重要部件之一,全稱是即時編譯器。我剛纔說的誤解,就是很多人(絕對不是少數,問了很多c++程序員,10個有9個這種想法)都以爲JIT其實就是跟Java VM差不多的東西,是一個Interpreter,在運行時讀取IL彙編代碼,然後模擬成x86代碼(也就是俗稱的虛擬機)。但是事實上,.NET使用的是更爲高級的技術。 .Net程序被加載入內存以後,當某段IL代碼被第一次運行的時候,JIT編譯器就會將這段IL代碼,全部編譯成本地代碼,然後再執行。這也就是爲什麼.NET程序第一次運行都啓動很慢的原因! 隨.NET庫,微軟還附帶了一個工具,可以事先將.NET程序所有的IL代碼都編譯成本地代碼並保存在緩存區中,這樣一來,這個程序就跟c++編譯的一模一樣了,沒有任何區別,運行時也可以脫離JIT了(這裏不要混淆了,這裏不是說可以脫離.NET庫,而是說不需要在進行即時編譯這個過程了)。所以,請不要將.NET和Java混爲一談,兩個的運行效率根本不是一個等級的!

     JIT的優化指的是可以針對本地CPU,在編譯時進行優化。傳統程序在編譯時,爲了保證兼容性,通常使用最通用的指令集(比如古老的386指令集)來編譯。而JIT知道CPU的具體類型,可以充分利用這些附加指令集進行編譯,這樣的性能提升是很可觀的。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章