CLR C++ 託管與非託管

來源:互聯網
上載者:User

一.將原始碼編譯成Managed 程式碼
1, CLR(Common Language Runtime)通用語言執行平台是一個可由多種語言使用的“運行時”,CLR的核心功能(比如記憶體管理,程式集載入,安全性,異常處理和線程同步)可由面向CLR的所有語言使用。如“運行時”使用異常來報告錯誤,所以面向“運行時”的所有語言都能通過異常來報告錯誤。
2, 可以將編譯器視為語法檢查器和“正確代碼”的分析器,他們檢查你的代碼,確定你的一切都有一些含義,然後輸出你對意圖進行描述的代碼,不同的程式設計語言允許不同的文法來開發。不要低估選擇的價值。如對數學和金融領域,使用APL文法比用Perl表達同樣的意圖更節約開發時間。
3, MicroSoft已經建立好了幾個面向“運行時”的編譯器,其中包括:C++/CLI,C#,Visual Basic,F#,iron Python,iron Ruby和一個中繼語言(Intermediate Language,IL)彙編器,其他的一些公司和大學還開發了一些,如:Ada,APL,COBOL,PHP等等的編譯器,無論是哪一種編譯器,編譯後都是一個託管模組(Managed Module)。Managed 程式碼是一個標準的32位的Windows可移植執行(PE32)檔案。
4, PE檔案的組成
①PE32或PE32+頭:標準的Windows PE檔案頭,如果這個頭使用PE32格式,檔案能在Windwos的32位或64位版本上運行。如果使用PE32+可是,檔案只能在Windwos 的64位版本上運行。這個頭還標識了檔案的類型,包括GUI,CUI,DLL,並包含一個時間標記來指出檔案的產生時間。對於只包含IL的代碼模組,PE(+)頭的大多數資訊會被忽略,對包含本地CPU代碼的模組(如C++編寫的),這個頭包含了與本地CPU代碼相關的資訊。
②CLR頭:包含這個模組成為一個託管模組的資訊(可由CLR和一些公用程式進行解釋)。頭中包含了需要的CLR的版本,一些flag,Managed 程式碼入口方法(Main)的MethodDef中繼資料token,以及模組的中繼資料,資源,強式名稱,一些flag以及其他不太重要的資料項目的位置/大小。
③每個託管模組都包含中繼資料表。主要有兩種類型的表:一種類型的表描述原始碼中定義的類型和成員,另一種類型的表描述原始碼引用的類型和成員。
④IL(中繼語言)代碼:編譯器編譯原始碼是產生的程式碼。在運行時CLR將IL編譯成本地cpu指令。
5, 本地編譯器產生的程式碼是面向特定cpu架構的(比如:x86,x64,ia64),CLR產生的都是中間代碼(IL)。有時也稱作Managed 程式碼,因為CLR要管理它的執行。
6, 除了產生IL,編譯器還在託管模組中產生完整的中繼資料(也就是PE頭的③部分)。中繼資料(metadata)是一組資料表,描述了模組中定義的內容,比如類型和成員。還有一些描述引用的內容,比如匯入的類型和成員。中繼資料始終和IL代碼一起被編譯到Managed 程式碼中。中繼資料的用途:
   A.編譯時間,中繼資料消除了對本地C/C++頭和庫檔案的需求,因為在負責實作類別型/成員的IL代碼檔案中,已包含了參考型別/成員有關的全部資訊。編譯器可以直接從Managed 程式碼讀取中繼資料。
   B.Visual Studio使用中繼資料協助你寫代碼。它的“智能感知(Intellisense”技術可以解析中繼資料,指出一個類型提供了那些方法,屬性,時間,欄位。
   C.CLR代碼驗證過程使用中繼資料確保只執行“型別安全”的操作。
   D.中繼資料允許將一個對象的欄位序列化到記憶體中,將其發送到另外一台機器,然後還原序列化,在遠程機器上重建對象。
   E.中繼資料允許記憶體回收行程跟蹤對象的生存期。記憶體回收行程能判斷任何對象的類型,並從中繼資料知道那個對象中的哪個欄位引用了其他的對象。
  7.Microsoft的C++編譯器預設產生非託管(本地)代碼的EXE/DLL模組,並在運行時操縱本地記憶體。這些模組不需要CLR就可以執行。然而,指定了一個/CLR開關,C++編譯器就能產生含Managed 程式碼的模組,當然,只有在安裝了CLR的機器上才能執行這種代碼。在眾多的編譯器中,C++編譯器是最特殊的一個。

二.將Managed 程式碼合并成程式集
1. CLR實際不合模組一起工作。相反,他是和程式集一起工作的。程式集(Assembly)是一個抽象的概念,初學者很難把握它的精髓,我就是。程式集是一個或多個模組/資源檔的邏輯分組。其次,程式集是重用,安全性以及版本控制的最小單元。在CLR的世界裡,它相當與一個組件,第2章有更深如的介紹,這裡略過。
2. 圖1-2有助於理解程式集,一些託管模組和資源(或資料)檔案準備交由一個工具處理,該工具產生單獨一個PE32(+)檔案來表示檔案的邏輯分組。實際發生的事情是,這個PE32(+)檔案包含一個名為“清單”的資料區塊。清單是有中繼資料表構成的另一種集合。這些表描述了構成程式集的檔案,由程式集的檔案實現的公開匯出的類型(就是程式集中定義的Public類型,它在程式集的外部可見),以及與程式集關聯在一起的資源和資料檔案。

預設是由編譯器產生託管模組轉換成程式集。

三.載入通用語言執行平台
1. 要知道是否已安裝.Net Framework,只需檢查%SystemRoot%System32目錄中的MSCorEE.dll檔案。存在該檔案,表明.Net Framework已安裝。一台機器上可能安裝了好幾個版本,要瞭解安裝了那些版本,可檢查以下註冊表子項:
KEY_LOCAL_MACHINE/SOFTWARE/Microsoft/NET Framework Setup/NDP
在xp環境下並沒有這個子項,應該是:
KEY_LOCAL_MACHINE/SOFTWARE/Microsoft/.NET Framework
2.  .NetFramework SDK提供了CLRVer.exe工具 + 生產力來查看已安裝的.Net版本。
  >clrver      //查看本機所有已安裝的的.NET版本。
  >clrver -all //查看所有正在啟動並執行.NET程式使用的.NET版本。
  >clrver 322  //查看進程號為322的程式使用的.NET版本。

四.執行程式集的代碼
    為了執行一個方法,首先必須把它的 IL 轉換成本地 CPU 指令。這是 CLR 的 JIT(just-in-time 或者“即時”)編譯器的職責。

圖 1-4 展示了一個方法首次調用時發生的事情。   


就在 Main 方法執行之前,CLR 會檢測出 Main 的代碼引用的所有類型。這導致 CLR 分配一個內部資料結構,它用於管理對所引用的類型的訪問。在圖 1-4 中,Main 方法引用了一個 Console 類型,這導致 CLR分配一個內部結構。在這個內部資料結構中,Console 類型定義的每個方法都有一個對應的記錄項 10 。每個記錄項都容納了一個地址,根據此地址即可找到方法的實現。對這個結構進行初始化時,CLR  將每個記錄項都設定成(指向)包含在 CLR 內部的一個未文檔化的函數。我將這個函數稱為
JITCompiler。
    Main 方法首次調用 WriteLine 時,JITCompiler 函數會被調用。JITCompiler 函數負責將一個方法的 IL 代碼編譯成本地 CPU 指令。由於 IL 是“即時”(just in time)編譯的,所以通常將 CLR 的這個組件稱為 JITter或者 JIT 編譯器。

注意:如果應用程式在 Windows 的 x86 版本或 WoW64 中運行,JIT 編譯器將產生 x86 指令。如果應用程式
以一個 64 位元應用程式的形式在 Windows 的 x64 或 Itanium 版本上運行,那麼 JIT 編譯器將分別產生 x64 或
IA64 指令。 
    JITCompiler 函數被調用時,它知道要調用的是哪個方法,以及具體是什麼類型定義了該方法。然後,JITCompiler 會在定義(該類型的)程式集的中繼資料中尋找被調用的方法的 IL。接著,JITCompiler 驗證 IL 代碼,並將 IL 代碼編譯成本地 CPU 指令。本地 CPU 指令被儲存到一個動態分配的記憶體塊中。然後,JITCompiler返回 CLR 為類型建立的內部資料結構,找到與被調用的方法對應的那一條記錄,修改最初對 JITCompiler 的引用,讓它現在指向記憶體塊(其中包含了剛才編譯好的本地
CPU 指令)的地址。最後,JITCompiler 函數跳轉到記憶體塊中的代碼。這些代碼正是 WriteLine 方法(擷取單個 String 參數的那個版本)的具體實現。這些代碼執行完畢並返回時,會返回至 Main 中的代碼,並跟往常一樣繼續執行。
    現在,Main 要第二次調用 WriteLine。這一次,由於已對 WriteLine 的代碼進行了驗證和編譯,所以會直接執行記憶體塊中的代碼,完全跳過 JITCompiler 函數。WriteLine 方法執行完畢之後,會再次返回 Main。圖 1-5 展示了第二次調用 WriteLine 時發生的事情。

    一個方法只有在首次調用時才會造成一些效能損失。以後對該方法的所有調用都以本地代碼的形式全速運行,無需重新驗證 IL 並把它編譯成本地代碼。
    JIT 編譯器將本地 CPU 指令儲存到動態記憶體中。一旦應用程式終止,編譯好的代碼也會被丟棄。所以,如果將來再次運行應用程式,或者同時啟動應用程式的兩個執行個體(使用兩個不同的作業系統進程),JIT  編譯器必須再次將 IL 編譯成本地指令。
    對於大多數應用程式,因 JIT 編譯造成的效能損失並不顯著。大多數應用程式都會反覆調用相同的方法。在應用程式運行期間,這些方法只會對效能造成一次性的影響。另外,在方法內部花費的時間很有可能比花在調用方法上的時間多得多。
    還要注意的是,CLR 的 JIT 編譯器會對本地代碼進行最佳化,這類似於非託管 C++編譯器的後端所做的工作。同樣地,可能要花費較多的時間來產生最佳化的代碼。但是,和沒有最佳化時相比,代碼在最佳化之後將獲得更出色的效能。

五.本地代碼產生器:NGen.exe
    使用.NET Framework 配套提供的 NGen.exe 工具,可以在一個應用程式安裝到使用者的電腦上時,將 IL代碼編譯成本地代碼。由於代碼在安裝時已經編譯好,所以 CLR 的 JIT 編譯器不需要在運行時編譯 IL 代碼,這有助於提升應用程式的效能。NGen.exe 能在兩種情況下發揮重要作用:
l     加快應用程式的啟動速度    運行 NGen.exe 能加快啟動速度,因為代碼已編譯成本地代碼,運行時不需要再花時間編譯。
l     減小應用程式的工作集     如果一個程式集會同時載入到多個進程中,對該程式集運行  NGen.exe可減小應用程式的工作集(working set)。NGen.exe 會將 IL 編譯成本地代碼,並將這些代碼儲存到一個單獨的檔案中。這個檔案可以通過“記憶體映射”的方式,同時映射到多個進程地址空間中,使代碼得到了共用,避免每個進程都需要一份單獨的代碼拷貝。

六.Framework類庫
    .NET Framework 中包含了 Framework 類庫(Framework Class Library,FCL)。FCL 是一組 DLL 程式集的統稱,其中含有數千個類型定義,每個類型都公開了一些功能。Microsoft 發布的庫並非僅限於此,其他還有Windows SideShow Managed API SDK15 和 DirectX SDK 等等。這些額外的庫提供了更多的類型,公開了更多可用的功能。事實上,Microsoft 正在以驚人的速度發布大量庫,開發人員在使用各種
Microsoft 技術時,變得前所未有的簡單。

七.一般型別系統
    CLR  是完全圍繞類型展開的,這一點到現在為止應該很明顯了。類型為應用程式和其他類型公開了功能。通過類型,用一種程式設計語言寫的代碼能與用另一種語言寫的代碼溝通。由於類型是 CLR 的根本,所以Microsoft 制定了一個正式的規範,叫做“一般型別系統”(Common Type System,CTS),它描述了類型的定義和行為。
CTS  規範規定,一個類型可以包含零個或者多個成員。本書第Ⅱ部分“設計類型”將更詳細地討論這些成員。目前只是簡單地介紹一下它們。
l     欄位(Field)    一個資料變數,是對象狀態的一部分。欄位根據名稱和類型來區分。
l     方法(Method)    一個函數,能針對對象執行一個操作,通常會改變對象的狀態。方法有一個名稱、一個簽名以及一個或多個修飾符。簽名指定參數的數量(及其順序);參數的類型;方法是否有傳回值;如果有傳回值,還要指定傳回值的類型。
l     屬性(Property)    對於調用者,該成員看起來像是一個欄位。但對於類型的實現者,它看起來像是一個方法(或者兩個方法,稱為 getter 和 setter,或者稱為取值方法和賦值方法)。屬性允許實現者在訪問值之前對輸入參數和對象狀態進行校正,以及/或者只有在必要的時候才計算一個值。屬性還允許類型的使用者採用簡化的文法。最後,可利用屬性建立唯讀或唯寫的“欄位”。
l     事件(Event)    事件在對象以及其他相關對象之間實現了一個通知機制。例如,利用按鈕提供的一個事件,可以在按鈕被單擊之後通知其他對象。
    CTS 還指定了類型可視性規則以及類型成員的訪問規則。例如,如果將類型標記為 public(在 C#中使用 public 修飾符),任何程式集都能看見並訪問該類型。但是,如果將類型標記為 assembly(在 C#中使用internal 修飾符),只有同一個程式集中的代碼才能看見並訪問該類型。所以,利用 CTS 制定的規則,程式集為一個類型建立了可視邊界,CLR 則強制(貫徹)了這些規則。調用者雖然能“看見”一個類型,但並不是說就能隨心所欲地訪問它。利用以下選項,可進一步限制調用者對類型中的成員的訪問。
l     private    成員只能由同一個類(class)類型中的其他成員訪問。
l     family    成員可由衍生類別型訪問,不管那些類型是否在同一個程式集中。注意,許多語言(比如C++和 C#)都用 protected 修飾符來標識 family。
l     family  and  assembly    成員可由衍生類別型訪問,但這些衍生類別型必須是在同一個程式集中定義的。許多語言(比如 C#和 Visual Basic)都沒有提供這種存取控制。當然,IL 組合語言不在此列。
l     assembly    成員可由同一個程式集中的任何代碼訪問。許多語言都用  internal  修飾符來標識assembly。
l     family  or  assembly    成員可由任何程式集中的衍生類別型訪問。成員也可由同一個程式集中的任何類型訪問。在 C#中,是用 protected internal 修飾符來標識 family or assembly。
l     public    成員可由任何程式集中的任何代碼訪問。
除此之外,CTS  還為類型繼承、虛方法、物件存留期等定義了相應的規則。這些規則在設計之初,便順應了可以用現代程式設計語言來表示的語義。

八.Common Language Specification
    COM 允許使用不同語言建立的對象相互連信。現在,CLR 整合了所有語言,允許在一種語言中使用由另一種語言建立的對象。之所以能實現這樣的整合,是因為 CLR 使用了標準類型集、中繼資料(自描述的類型資訊)以及公用執行環境。
    語言的整合是一個宏偉的目標,最棘手的問題是各種程式設計語言存在極大的區別。例如,有的語言在處理符號時不區分大小寫,有的語言不支援 unsigned(無符號)整數、操作符重載或者參數數量可變的方法。
    要建立很容易從其他程式設計語言中訪問的類型,只能從自己的程式設計語言中挑選其他所有語言都確定支援的那些功能。為了在這個方面提供協助,Microsoft  定義了一個“Common Language Specification”(Common  Language Specification,CLS),它詳細定義了一個最小功能集。任何編譯器產生的類型要想相容於由其他“符合 CLS 規範、面向 CLR 的語言”所產生的組件,就必須支援這個最小功能集。
    1-6 所示,CLR/CTS 提供了一個功能集。有的語言公開了 CLR/CTS 的一個較大的子集。例如,假定開發人員使用 IL 組合語言寫程式,就可以使用 CLR/CTS 提供的全部功能。但是,其他大多數語言(比如 C#、Visual  Basic 和 Fortran)只向開發人員公開了 CLR/CTS 的一個功能子集。CLS 定義了所有語言都必須支援的
一個最小功能集。
    用一種語言定義一個類型時,如果希望在另一種語言中使用該類型,就不要在該類型的  public  和protected 成員中使用位於 CLS 外部的任何功能。否則,其他開發人員使用其他語言寫代碼時,就可能無法訪問這個類型的成員。
    以下代碼使用 C#定義一個符合 CLS 規範 的類型。然而,類型中含有幾個不符合 CLS 規範 的構造,造成 C#編譯器報錯:
using System;
// 告訴編譯器檢查 CLS相容性
[assembly: CLSCompliant(true)]
 
namespace SomeLibrary {
    // 因為是 public類,所以會顯示警告
    public sealed class SomeLibraryType {
 
        // 警告:SomeLibrary.SomeLibraryType.Abc()的傳回型別不符合 CLS 規範
        public UInt32 Abc() { return 0; }
 
        // 警告:僅大小寫不同的標識符 SomeLibrary.SomeLibraryType.abc()
        // 不符合 CLS 規範
        public void abc() { }
 
        // 不會顯示警告:該方法是私人的
        private UInt32 ABC() { return 0; }
    }
}
    上述代碼將[assembly:CLSCompliant(true)]這個 attribute16 應用於程式集。這個 attribute 告訴編譯器檢查pubic 類型,判斷是否存在任何不合適的構造,阻止了從其他程式設計語言中訪問該類型。上述代碼編譯時間,C#編譯器會報告兩條警告訊息。第一個警告是因為 Abc 方法返回了一個不帶正負號的整數;有一些語言是不能操作不帶正負號的整數值的。第二個警告是因為該類型公開了兩個 public 方法,這兩個方法(Abc 和 abc)只是大小寫和傳回型別有別。Visual Basic
和其他一些語言無法區分這兩個方法。
    有趣的是,刪除 sealed class SomeLibraryType 之前的 public 字樣,然後重新編譯,兩個警告都會消失。因為這樣一來,SomeLibraryType 類型將預設為 internal(而不是 public),將不再向程式集的外部公開。

九.與Unmanaged 程式碼的互通性
    .NET  Framework 提供了其他開發平台沒有的許多優勢。但是,能下定決心重新設計和重新實現其現有的全部代碼的公司並不多。Microsoft 也知道這個問題,並通過 CLR 來提供了一些機制,允許在應用程式中同時包含託管和Unmanaged 程式碼。具體地說,CLR 支援三種互操作情形。
l     Managed 程式碼能調用 DLL 中的一個非託管函數    Managed 程式碼可以採取一種名為 P/Invoke(Platform Invoke 的簡稱)的機制來調用  DLL  中包含的函數。畢竟,FCL  中定義的許多類型都要在內部調用從Kernel32.dll、User32.dll 等匯出的函數。許多程式設計語言都提供了一個機制,允許Managed 程式碼方便地調用包含在 DLL 中的非託管函數。例如,C#應用程式可調用從 Kernel32.dll 匯出的 CreateSemaphore函數。
l     Managed 程式碼可使用現有的 COM 組件(伺服器)    許多公司都已經實現了大量非託管 COM 組件。利用來自這些組件的類型庫,可建立一個託管程式集來描述 COM 組件。Managed 程式碼可以像訪問其他任何託管類型一樣訪問託管程式集中的類型。這方面的詳情可以參見.NET  Framework  SDK 配套提供的 TlbImp.exe 工具 17 。有的時候,你可能沒有一個類型庫,或者想對 TlbImp.exe 產生的內容進行更多的控制。在這種情況下,可以在原始碼中手動構建一個類型,使 CLR 能用它來實現正確的互通性。例如,可以從一個
C#應用程式中使用 DirectX COM 組件。
l     Unmanaged 程式碼可使用託管類型(伺服器)    許多現有的Unmanaged 程式碼要求提供一個 COM 組件,從而確保代碼正確工作。使用Managed 程式碼可以更簡單地實現這些組件,避免所有代碼都不得不和引用計數以及介面打交道。例如,可以使用 C#來建立一個 ActiveX 控制項或者一個 殼層延伸。這方面的詳情可以參見.NET Framework SDK 配套提供的 TlbExp.exe 和 RegAsm.exe 工具。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.