日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > C# >内容正文

C#

【CLR via C#】CSC将源代码编译成托管模块

發(fā)布時間:2025/4/16 C# 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【CLR via C#】CSC将源代码编译成托管模块 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

????? 下圖展示了編譯源代碼文件的過程。如圖所示,可用支持 CLR 的任何一種語言創(chuàng)建源代碼文件。然后,用一個對應(yīng)的編譯器檢查語法和分析源代碼。無論選用哪一個編譯器,結(jié)果都是一個托管模塊(managedmodule)。托管模塊是一個標(biāo)準(zhǔn)的 32 位 Microsoft Windows 可移植執(zhí)行體(PE32)文件 6 ,或者是一個標(biāo)準(zhǔn)的 64 位Windows 可移植執(zhí)行體(PE32+)文件,它們都需要 CLR 才能執(zhí)行。順便說一句,托管的程序集總是利用了 Windows 的數(shù)據(jù)執(zhí)行保護(hù)(Data Execution Prevention,DEP)和地址空間布局隨機化(Address SpaceLayout Randomization,ASLR);這兩個功能旨在增強整個系統(tǒng)的安全性。


托管模塊的組成部分

   PE32 或 PE32+頭:標(biāo)準(zhǔn) Windows PE 文件頭,類似于“公共對象文件格式(Common Object File Format,COFF)”頭。如果這個頭使用 PE32 格式,?文件能在Windows的 32 位或 64 位版本上運行。如果這個頭使用 PE32+格式,文件只能在 Windows 的 64 位版本上運行。這個頭還標(biāo)識了文件類型,包括 GUI,CUI 或者 DLL,并包含一個時間標(biāo)記來指出文件的生成時間。對于只包含 IL 代碼的模塊,PE32(+)頭的大多數(shù)信息會被忽視。對于包含本地 CPU代碼的模塊,這個頭包含了與本地 CPU 代碼有關(guān)的信息

  CLR 頭:包含使這個模塊成為一個托管模塊的信息(可由 CLR 和一些實用程序進(jìn)行解釋)。頭中包含了需要的 CLR 版本,一些 flag,托管模塊入口方法(Main 方法)的 MethodDef 元數(shù)據(jù) token,以及模塊的元數(shù)據(jù)、資源、強名稱、一些 flag 以及其他不太重要的數(shù)據(jù)項的位置/大小

  元數(shù)據(jù):每個托管模塊都包含元數(shù)據(jù)表。主要有兩種類型的表:一種類型的表描述源代碼中定義的類型和成員,另一種類型的表描述源代碼引用的類型和成員

  IL(中間語言)代碼:編譯器編譯源代碼時生成的代碼。在運行時,CLR 將 IL 編譯成本地 CPU指令。

????? 本地代碼編譯器(native code compilers)生成的是面向特定 CPU 架構(gòu)(比如 x86,x64 或 IA64)的代碼。相反,每個面向 CLR 的編譯器生成的都是 IL(中間語言)代碼。IL 代碼有時稱為托管代碼,因為 CLR 要管理它的執(zhí)行。

  ? 除了生成 IL,面向 CLR 的每個編譯器還要在每個托管模塊中生成完整的元數(shù)據(jù)。簡單地說,元數(shù)據(jù)(metadata)是一組數(shù)據(jù)表。其中一些數(shù)據(jù)表描述了模塊中定義的內(nèi)容,比如類型及其成員。還有一些元數(shù)據(jù)表描述了托管模塊引用的內(nèi)容,比如導(dǎo)入的類型及其成員。元數(shù)據(jù)是一些老技術(shù)的超集。這些老技術(shù)包括 COM 的“類型庫(Type Library)”和“接口定義語言(Interface Definition Language,IDL)”文件。要注意的是,CLR 元數(shù)據(jù)遠(yuǎn)比它們完整。另外,和類型庫及 IDL 不同,元數(shù)據(jù)總是與包含 IL 代碼的文件關(guān)聯(lián)。事實上,元數(shù)據(jù)總是嵌入和代碼相同的 EXE/DLL 文件中,這使兩者密不可分。由于編譯器同時生成元數(shù)據(jù)和代碼,把它們綁定一起,并嵌入最終生成的托管模塊,所以元數(shù)據(jù)和它描述的 IL 代碼永遠(yuǎn)不會失去同步。元數(shù)據(jù)有多種用途,下面僅列舉一部分。

  *? 編譯時,元數(shù)據(jù)消除了對本地 C/C++頭和庫文件的需求,因為在負(fù)責(zé)實現(xiàn)類型/成員的 IL 代碼文件中,已包含和引用的類型/成員有關(guān)的全部信息。編譯器可直接從托管模塊讀取元數(shù)據(jù)。

  *? Microsoft Visual Studio 使用元數(shù)據(jù)幫助你寫代碼。它的“智能感知(IntelliSense)”技術(shù)可以解析元數(shù)據(jù),指出一個類型提供了哪些方法、屬性、事件和字段。如果是一個方法,還能指出方法需要什么參數(shù)。

  *? CLR 的代碼驗證過程使用元數(shù)據(jù)確保代碼只執(zhí)行“類型安全”的操作。(稍后就會講到驗證。)。

  * 元數(shù)據(jù)允許將一個對象的字段序列化到一個內(nèi)存塊中,將其發(fā)送給另一臺機器,然后反序列化,在遠(yuǎn)程機器上重建對象的狀態(tài)。

  *? 元數(shù)據(jù)允許垃圾回收器跟蹤對象的生存期。垃圾回收器能判斷任何對象的類型,并從元數(shù)據(jù)知道那個對象中的哪些字段引用了其他對象

將托管模塊合并成程序集

  CLR 實際不和模塊一起工作。相反,它是和程序集一起工作的。程序集(assembly)是一個抽象的概念,初學(xué)者往往很難把握它的精髓。首先,程序集是一個或多個模塊/資源文件的邏輯性分組。其次,程序集是重用、安全性以及版本控制的最小單元。取決于你對于編譯器或工具的選擇,既可以生成單文件程序集,
也可以生成多文件程序集。在 CLR 的世界中,程序集相當(dāng)于一個“組件”。

  下圖有助于理解程序集。在這幅圖中,一些托管模塊和資源(或數(shù)據(jù))文件準(zhǔn)備交由一個工具處理。該工具生成單獨一個 PE32(+)文件來表示文件的邏輯性分組。實際發(fā)生的事情是,這個 PE32(+)文件包含一個名為“清單”(manifest)的數(shù)據(jù)塊。清單是由元數(shù)據(jù)表構(gòu)成的另一種集合。這些表描述了構(gòu)成程序集的文件,由程序集中的文件實現(xiàn)的公開導(dǎo)出的類型 7 ,以及與程序集關(guān)聯(lián)在一起的資源或數(shù)據(jù)文件。

默認(rèn)是由編譯器將生成的托管模塊轉(zhuǎn)換成程序集。換言之,C#編譯器生成含有清單的一個托管模塊。清單指出程序集只由一個文件構(gòu)成。

加載公共語言運行時

  你生成的每個程序集既可以是一個可執(zhí)行應(yīng)用程序,也可以是一個 DLL(其中含有一組由可執(zhí)行程序使用的類型)。當(dāng)然,最終是由 CLR 管理這些程序集中的代碼的執(zhí)行。這意味著必須在目標(biāo)機器上安裝好.NETFramework。

  C#編譯器生成的程序集要么包含一個 PE32 頭,要么包含一個 PE32+頭。除此之外,編譯器還會在頭中指定要求什么 CPU 架構(gòu)(如果使用默認(rèn)值 anycpu,則不明確指定)。Microsoft發(fā)布了 SDK 命令行實用程序 DumpBin.exe 和 CorFlags.exe,可用它們檢查編譯器生成的托管模塊所嵌入的信息。

執(zhí)行程序集的代碼

??? 為了執(zhí)行一個方法,首先必須把它的 IL 轉(zhuǎn)換成本地 CPU 指令。這是 CLR 的 JIT (just-in-time 或者“即時”)編譯器的職責(zé)。下圖展示了一個方法首次調(diào)用時發(fā)生的事情。

???? 就在 Main 方法執(zhí)行之前,CLR 會檢測出 Main 的代碼引用的所有類型。這導(dǎo)致 CLR 分配一個內(nèi)部數(shù)據(jù)結(jié)構(gòu),它用于管理對所引用的類型的訪問。在圖中,Main 方法引用了一個 Console 類型,這導(dǎo)致 CLR分配一個內(nèi)部結(jié)構(gòu)。在這個內(nèi)部數(shù)據(jù)結(jié)構(gòu)中,Console 類型定義的每個方法都有一個對應(yīng)的記錄項 10 。每個記錄項都容納了一個地址,根據(jù)此地址即可找到方法的實現(xiàn)。對這個結(jié)構(gòu)進(jìn)行初始化時,CLR 將每個記錄項都設(shè)置成(指向)包含在 CLR 內(nèi)部的一個未文檔化的函數(shù)。我將這個函數(shù)稱為 JITCompiler。

  JITCompiler 函數(shù)被調(diào)用時,它知道要調(diào)用的是哪個方法,以及具體是什么類型定義了該方法。然后,JITCompiler 會在定義(該類型的)程序集的元數(shù)據(jù)中查找被調(diào)用的方法的 IL。接著,JITCompiler 驗證 IL 代碼,并將 IL 代碼編譯成本地 CPU 指令。本地 CPU 指令被保存到一個動態(tài)分配的內(nèi)存塊中。然后,JITCompiler返回 CLR 為類型創(chuàng)建的內(nèi)部數(shù)據(jù)結(jié)構(gòu),找到與被調(diào)用的方法對應(yīng)的那一條記錄,修改最初對 JITCompiler 的引用,讓它現(xiàn)在指向內(nèi)存塊(其中包含了剛才編譯好的本地 CPU 指令)的地址。最后,JITCompiler 函數(shù)跳轉(zhuǎn)到內(nèi)存塊中的代碼。這些代碼正是 WriteLine 方法(獲取單個 String 參數(shù)的那個版本)的具體實現(xiàn)。這些代碼執(zhí)行完畢并返回時,會返回至 Main 中的代碼,并跟往常一樣繼續(xù)執(zhí)行?,F(xiàn)在,Main 要第二次調(diào)用 WriteLine。這一次,由于已對 WriteLine 的代碼進(jìn)行了驗證和編譯,所以會
直接執(zhí)行內(nèi)存塊中的代碼,完全跳過 JITCompiler 函數(shù)。WriteLine 方法執(zhí)行完畢之后,會再次返回 Main。
??? 下圖展示了第二次調(diào)用 WriteLine 時發(fā)生的事情。

?

  一個方法只有在首次調(diào)用時才會造成一些性能損失。以后對該方法的所有調(diào)用都以本地代碼的形式全速運行,無需重新驗證 IL 并把它編譯成本地代碼。JIT 編譯器將本地 CPU 指令存儲到動態(tài)內(nèi)存中。一旦應(yīng)用程序終止,編譯好的代碼也會被丟棄。所以,如果將來再次運行應(yīng)用程序,或者同時啟動應(yīng)用程序的兩個實例(使用兩個不同的操作系統(tǒng)進(jìn)程),JIT 編譯器必須再次將 IL 編譯成本地指令。對于大多數(shù)應(yīng)用程序,因 JIT 編譯造成的性能損失并不顯著。大多數(shù)應(yīng)用程序都會反復(fù)調(diào)用相同的方法。
在應(yīng)用程序運行期間,這些方法只會對性能造成一次性的影響。另外,在方法內(nèi)部花費的時間很有可能比花在調(diào)用方法上的時間多得多。還要注意的是,CLR 的 JIT 編譯器會對本地代碼進(jìn)行優(yōu)化,這類似于非托管 C++編譯器的后端所做的工作。同樣地,可能要花費較多的時間來生成優(yōu)化的代碼。但是,和沒有優(yōu)化時相比,代碼在優(yōu)化之后將獲得更出色的性能。

?????? 有兩個 C#編譯器開關(guān)會影響代碼的優(yōu)化:/optimize 和/debug。下面總結(jié)了這些開關(guān)對 C#編譯器生成
的 IL 代碼的質(zhì)量的影響,以及對 JIT 編譯器生成的本地代碼的質(zhì)量的影響。

???? 雖然這樣說很難讓人信服,但許多人(包括我)都認(rèn)為托管應(yīng)用程序的性能實際上超過了非托管應(yīng)用程序。有許多原因使我們對此深信不疑。例如,當(dāng) JIT 編譯器在運行時將 IL 代碼編譯成本地代碼時,編譯器對執(zhí)行環(huán)境的認(rèn)識比非托管編譯器更加深刻。下面列舉了托管代碼相較于非托管代碼的優(yōu)勢:

?? 1、JIT 編譯器能判斷應(yīng)用程序是否運行在一個 Intel Pentium 4 CPU 上,并生成相應(yīng)的本地代碼來利用Pentium 4 支持的任何特殊指令。相反,非托管應(yīng)用程序通常是針對具有最小功能集合的 CPU 編譯的,不會使用能提升應(yīng)用程序性能的特殊指令。

?? 2、JIT 編譯器能判斷一個特定的測試在它運行的機器上是否總是失敗。例如,假定一個方法包含以下代碼:
if (numberOfCPUs > 1) {
...
}
???? 如果主機只有一個 CPU,JIT 編譯器不會為上述代碼生成任何 CPU 指令。在這種情況下,本地代碼將針對主機進(jìn)行優(yōu)化,最終的代碼變得更小,執(zhí)行得更快。

?? 3、應(yīng)用程序運行時,CLR 可以評估代碼的執(zhí)行,并將 IL 重新編譯成本地代碼。重新編譯的代碼可以重新組織,根據(jù)剛才觀察到的執(zhí)行模式,減少不正確的分支預(yù)測。雖然目前版本的 CLR 還不能做到這一點,但將來的版本也許就可以了。

?????除了這些理由,還有另一些理由使我們相信在執(zhí)行效率上,未來的托管代碼會比當(dāng)前的非托管代碼更優(yōu)秀。大多數(shù)托管應(yīng)用程序目前的性能已相當(dāng)不錯,將來還有望進(jìn)一步提升。

IL? 和驗證

  IL 是基于棧的。這意味著它的所有指令都要將操作數(shù)壓入(push)一個執(zhí)行棧,并從棧彈出(pop)結(jié)果。由于 IL 沒有提供操作寄存器的指令,所以人們可以很容易地創(chuàng)建新的語言和編譯器,生成面向 CLR 的代碼。

????? IL 指令還是“無類型”(typeless)的。例如,IL 提供了一個 add 指令,它的作用是將壓入棧的最后兩個操作數(shù)加到一起。add 指令不分 32 位和 64 位版本。add 指令執(zhí)行時,它判斷棧中的操作數(shù)的類型,并執(zhí)行恰當(dāng)?shù)牟僮鳌?br />
????? 我個人認(rèn)為,IL 最大的優(yōu)勢并不在于它對底層 CPU 的抽象。IL 提供的最大的優(yōu)勢在于應(yīng)用程序的健壯性 11 和安全性。將 IL 編譯成本地 CPU 指令時,CLR 會執(zhí)行一個名為驗證(verification)的過程。這個過程會檢查高級 IL 代碼,確定代碼所做的一切都是安全的。例如,驗證會核實調(diào)用的每個方法都有正確數(shù)量的參數(shù),傳給每個方法的每個參數(shù)都具有正確的類型,每個方法的返回值都得到了正確的使用,每個方法都有一個返回語句,等等。在托管模塊的元數(shù)據(jù)中,包含了要由驗證過程使用的所有方法和類型信息。

本地代碼生成器:NGen.exe

???? 使用.NET Framework 配套提供的 NGen.exe 工具,可以在一個應(yīng)用程序安裝到用戶的計算機上時,將 IL代碼編譯成本地代碼。由于代碼在安裝時已經(jīng)編譯好,所以 CLR 的 JIT 編譯器不需要在運行時編譯 IL 代碼,這有助于提升應(yīng)用程序的性能。NGen.exe 能在兩種情況下發(fā)揮重要作用:

?? 1、加快 應(yīng)用程序的啟動速度 運行 NGen.exe 能加快啟動速度,因為代碼已編譯成本地代碼,運行時不需要再花時間編譯。

?? 2、減小應(yīng)用程序的工作集 13 如果一個程序集會同時加載到多個進(jìn)程中,對該程序集運行 NGen.exe可減小應(yīng)用程序的工作集(working set)。NGen.exe 會將 IL 編譯成本地代碼,并將這些代碼保存到一個單獨的文件中。這個文件可以通過“內(nèi)存映射”的方式,同時映射到多個進(jìn)程地址空間中,使代碼得到了共享,避免每個進(jìn)程都需要一份單獨的代碼拷貝。

總結(jié)

以上是生活随笔為你收集整理的【CLR via C#】CSC将源代码编译成托管模块的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。