1. 程式人生 > 實用技巧 >反射--程式集

反射--程式集

程式集

程式集詳情,可以檢視https://docs.microsoft.com/zh-cn/dotnet/standard/assembly/

程式集構成了 .NET 應用程式的部署、版本控制、重用、啟用範圍和安全許可權的基本單元。採用可執行檔案 (.exe) 或動態連結庫檔案 (.dll) 的形式,是 .NET 應用程式的構建基塊 。是一個或多個型別定義檔案及資原始檔的集合。

程式集定義以下資訊:

  • 公共語言執行時執行的程式碼。
  • 安全邊界。
  • 型別邊界。 每一型別的標識均包括該型別所駐留的程式集的名稱。 在一個程式集的範圍中載入的稱為 MyType 的型別不同於在另一個程式集範圍中載入的稱為 MyType 的型別。
  • 引用範圍邊界。
  • 版本邊界。
  • 部署單元。
  • 並行執行單元。

靜態程式集可能由以下四個元素組成:其中只有程式集清單是必需的,但也需要型別或資源來向程式集提供任何有意義的功能。

  • 程式集清單,包含程式集元資料。
  • 型別元資料。
  • 實現這些型別的 Microsoft 中間語言 (MSIL) 程式碼。 它由編譯器從一個或多個原始碼檔案生成。
  • 資源集。

其中C#原始碼經過C#編譯器,生成託管模組,最後編譯器預設把生成的託管模組和資原始檔合併成程式集。如果只有一個託管模組,且沒有資源(或資料)檔案的專案,程式集就是託管模組,生成過程中無需執行任何額外的步驟。
程式集是PE32檔案或者64位Windows可移植執行體(PE32+:Portable Executable)檔案,包括

  • PE32或PE32+頭:
  • CLR頭:包含了CLR版本,
  • 元資料:主要有兩種表:分別是描述原始碼中定義的型別和成員、引用的型別和成員
  • IL程式碼:編譯器編譯原始碼時,生成的程式碼。執行時,CLR把IL程式碼編譯成本機CPU指令。

程式碼執行過程:

  • JIT(just in time)編譯器的職責:把方法的IL轉換成本機(native)CPU指令
  • Console.WriteLine("Hello")的執行
    • 1、Main方法執行之前,CLR為Console型別分配一個內部結構,在這個內部結構中Console型別定義的每個方法都有一個對應的記錄項
      每個記錄項含有一個地址,根據此地址就可以找到此方法的實現
      對這個結構初始化時,CLR將每個記錄項都設定成在CLR內部的一個未變檔函式,成為JITCompiler
    • 2、首次呼叫,JITCompiler函式會被呼叫。負責將IL程式碼編譯成本機CPU指令。將這個元件叫JIT編譯器
      1、在負責實現型別的程式集的元資料中查詢被呼叫的方法
      2、從元資料中獲取被呼叫的方法的IL
      3、分配記憶體塊
      4、將IL編譯成本機CPU執行,然後將這些原生代碼放到3中的記憶體
      5、在Type表中修改與方法對應的條目,使它指向3分配的記憶體塊
      6、調整到記憶體塊中的本機程式碼
      CPU指令存在動態記憶體中的
      JIT編譯器會對本機程式碼進行優化

獲取程式集:
獲取不到丟擲異常,而不是返回null值

//Assebmly靜態方法獲取程式集 
Assembly assembly = Assembly.Load("StudyConsole, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
assembly = Assembly.Load("StudyConsole");
assembly = Assembly.Load(new AssemblyName("StudyConsole, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null") { });
            
Assembly assembly1 = Assembly.LoadFile(@"D:\1-辦公\dnSpy\dnSpy\dnSpy.Console\obj\Debug\netcoreapp3.1\dnSpy.Console.dll");

Assembly assembly2 = Assembly.LoadFrom(@"D:\1-辦公\dnSpy\dnSpy\dnSpy\bin\Debug\netcoreapp3.1\dnlib.dll");

//僅檢視程式集,不能執行
Assembly assembly3 = Assembly.ReflectionOnlyLoad("");
Assembly assembly4 = Assembly.ReflectionOnlyLoadFrom("");

//獲取當前執行的程式集
Assembly assembly8 = Assembly.GetExecutingAssembly();
//獲取當前應用程式域所有載入的程式集
Assembly[] assembly6 = AppDomain.CurrentDomain.GetAssemblies();

//根據型別獲取
Assembly assembly5 = Assembly.GetAssembly(typeof(System.String));
Assembly assembly7 = typeof(Program).Assembly;

//獲取程式集的特性
var asstributes= assembly.CustomAttributes;
//獲取引用的AssebmlyName
AssemblyName[] ass= assembly.GetReferencedAssemblies();

獲取程式集的方法中,Load(string)方法,會先把string轉換成Assebmly,在呼叫。和Load(AssebmlyName)的內部方法方式一致;LoadFrom方法和Load方法最終呼叫的方法一致,傳入的地址最終轉換成Assebmly型別。
(程式集名稱的長格式包含其簡單名稱(如 System .dll 程式集的 "System")及其版本、區域性、公鑰標記,還可以是其處理器體系結構。 它對應於程式集的 FullName 屬性。)
Load方法會優先使用當前應用程式域中的程式集(猜測),因為當用LoadFile載入程式集之後,在載入同樣的程式集不會報錯。
Load方法初始化的是Assebmly的Name屬性,LoadFrom是CodeBase;
其中LoadFile和Load方法實現方式不一致,CSDN(LoadFile 不會載入相應的引用程式集,但是 LoadFrom 會。 LoadFrom 不能用於載入具有相同標識但路徑不同的程式集;它將只加載第一個此類程式集。)
ReflectionOnlyLoad、ReflectionOnlyLoadFrom最終的方法和Load方法一致,只是傳入了一個區分的bool值。但是使用.NET CORE執行會報錯。

程式集的屬性截圖:

其中CodeBase和Location,都是檔案地址;DefinedTypes是所有的型別,ExportedTypes是所有pulbli類。