.NET Emit 入門教程:第六部分:IL 指令:3:詳解 ILGenerator 指令方法:參數載入指令

来源:https://www.cnblogs.com/cyq1162/p/18102455
-Advertisement-
Play Games

本篇教程深入探討了 ILGenerator 中的參數載入指令,通過詳細解釋Ldarg、Ldarga、Ldloc和Ldloca等指令的使用,讀者能夠清晰地認識到Ld指令用於載入參數或本地變數到堆棧,而St指令用於將值從堆棧存儲到參數或本地變數中。這些指令為動態方法的生成提供了基礎,幫助開發者更好地掌握... ...


前言:

在上一篇中,我們介紹了 ILGenerator 輔助方法。

本篇,將詳細介紹指令方法,並詳細介紹指令的相關用法。

在接下來的教程,關於IL指令部分,會將指令分為以下幾個分類進行講解:

1、參數載入指令:ld 開頭的指令,單詞為:load argument

2、參數存儲指令:st 開頭的指令,單詞為:store

3、創建實例指令: new 開頭的指令。

4、方法調用指令:call 開頭的指令。

5、分支條件指令:br 開頭的指令,單詞為 break

6、類型轉換指令:cast 或 conv 開頭的指令,單詞為:convert

7、運算操作指令:add/sub/mul/div/rem ,加減乘除取餘。

8、其它指令

下麵開始介紹第一部分,參數載入指令:

參數載入指令:

參數載入指令用於在方法中載入參數到操作數棧中,為後續的操作做準備。

當涉及 CIL(Common Intermediate Language)指令時,以 "ld" 開頭的指令通常用於載入數據到操作數棧中。

以下是一些常見的以 "ld" 開頭的參數載入指令及其簡要說明:

  1. ldarg: 將指定索引位置的參數載入到操作數棧中。用於將方法的參數載入到操作數棧中,以便在方法中進行操作或傳遞給其他方法。

  2. ldarga: 將指定索引位置的參數的地址載入到操作數棧中。通常用於獲取參數的地址,以便在方法中對參數進行引用傳遞。

  3. ldc_X: 將常量載入到操作數棧中。其中 X 可以是 I(整數)、I4(32 位整數)、I8(64 位整數)、R4(單精度浮點數)、R8(雙精度浮點數)、I4_M1(-1 的特殊表示)、I4_0、I4_1、I4_2、I4_3、I4_4、I4_5(特殊整數常量)等。

  4. ldloc: 將指定索引位置的本地變數載入到操作數棧中。用於將方法內部的局部變數載入到操作數棧中,以進行後續的操作或傳遞給其他方法。

  5. ldloca: 將指定索引位置的本地變數的地址載入到操作數棧中。通常用於獲取局部變數的地址,以便在方法中對局部變數進行引用傳遞。

  6. ldfld: 將對象的欄位值載入到操作數棧中。用於載入對象的欄位值,以便在方法中進行操作或傳遞給其他方法。

  7. 其它:。

這些指令提供了豐富的功能,使得在方法內部能夠方便地處理參數、常量、本地變數和對象欄位值。通過合理使用這些指令,可以實現對數據的靈活操作和處理。

參數載入指令:短格式和長格式

在 IL(Intermediate Language)中,有一些指令支持短格式和長格式的表示(以 “_S” 結尾代表 short 短指令,預設對應無 _S結尾的即代表長格式指令)。

短格式指令通常用於跳轉到相對較近的位置,而長格式指令則可以用於跳轉到較遠的位置。

在實際應用中,由於 IL 的靈活性和可擴展性,編譯器會根據需要自動選擇合適的指令格式。

這樣可以根據具體的跳轉距離來選擇最有效的指令格式,從而使生成的代碼更加高效。

總的來說,短格式和長格式的區別在於其編碼的範圍,短格式指令編碼的範圍較小,適用於相對較近的跳轉位置,而長格式指令則可以覆蓋更大的跳轉範圍。

這種設計可以使得 IL 代碼在執行時更加高效。

1、從方法傳參中載入:ldarg 載入參數值

ldarg: 將指定索引位置的參數載入到操作數棧中,該參數為 Load Argument(載入參數)的簡寫。

該參數的作用:是從指定的方法傳參中獲得參數值,並將該值載入到操作數棧中。

OpCodes一共提供了5個相關的指令:

Ldarg_0:0索引參數。
Ldarg_1:1索引參數
Ldarg_2:2索引參數
Ldarg_3:3索引參數
Ldarg:使用自定義索引

示例代碼:

ILGenerator il = methodBuilder.GetILGenerator();
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg,2);//這裡使用自定義索引
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Ret);

對應生成代碼:

在本段示例代碼中:

Ldarg_0:代表 this 當前對象。

Ldarg_1:代碼參數a

Ldarg_2:代碼參數b

Ldarg_3:無。

需要註意的是:

示例中定義的是實例方法,因此 Ldarg_0 代表 this 對象,而通過 DynamicMethod 創建的方法,預設則是靜態方法,Ldarg_0 則會代表參數a。

2、從方法傳參中載入:ldarga 載入參數引用地址

ldarga: 將指定索引位置的參數的地址載入到操作數棧中,該參數為 Load Argument Address(載入參數地址)的簡寫。

該參數的作用:是從指定的方法傳參中獲得參數的地址,並將該地址載入到操作數棧中。

OpCodes一共提供了2個相關的指令:

Ldarga:需要指定索引值。
Ldarga_S:需要指定索引值

需要註意的是,在 C# 中除了操作 ref 或  out 會涉及到引用地址,其它操作操作引用地址(操作指針)都需要在unsafe方法下。

否則強行操作,會出現以下異常,例如:

也可能是以下異常:

3、數字類型值載入:ldc_X 載入參數值

在 Common Intermediate Language(CIL)中,以 "ldc" 開頭的操作碼指令主要用於將常量載入到操作數棧中。

這些指令在.NET平臺的程式集中起著重要的作用,用於處理常量數據的載入和操作。

以下是幾種以 "ldc" 開頭的常見操作碼指令及其分類和用途:

1. ldc_i4 / ldc_i4_s

  • 分類: 整數常量載入指令
  • 用途: 將 32 位整數常量載入到操作數棧中。ldc_i4 指令用於載入常量值介於 -2^31 到 2^31-1 之間的整數,而 ldc_i4_s 則用於載入介於 -128 到 127 之間的整數常量。

2. ldc_i8

  • 分類: 長整數常量載入指令
  • 用途: 將 64 位長整數常量載入到操作數棧中。適用於載入大於 Int32 範圍的整數常量。

3. ldc_r4 / ldc_r8

  • 分類: 浮點數常量載入指令
  • 用途: 將單精度浮點數(float)或雙精度浮點數(double)常量載入到操作數棧中。ldc.r4 用於載入單精度浮點數常量,而 ldc.r8 用於載入雙精度浮點數常量。

4. ldc_i4_m1 / ldc_i4_0 / ldc_i4_1 / ldc_i4_2 / ....../idc_i4_8

  • 分類: 特殊整數常量載入指令
  • 用途: 分別用於載入特定的整數常量值,例如 -1、0、1、2、......、6、7、8 等。

通過合理使用這些以 "ldc" 開頭的操作碼指令,開發人員可以方便地載入各種類型的常量數據到操作數棧中,為程式的運行和計算提供必要的數據支持。

下麵來一個簡單的示例:

ILGenerator il = dynamicMethod.GetILGenerator();
il.Emit(OpCodes.Ldc_I4, 9999);
il.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }));
il.Emit(OpCodes.Ret);

運行後輸出:

4、局部變數載入:ldloc 參加變數值

ldloc: 將指定索引位置的本地變數載入到操作數棧中。用於將方法內部的局部變數載入到操作數棧中,以進行後續的操作或傳遞給其他方法。

該參數為:load local 載入本地變數的簡寫。

該方法需要配合輔助變數使用,這個在上一篇輔助方法中有介紹到,這裡重溫一下上一篇的輔助方法,定義變數的內容:

5、局部變數載入:ldloca 參數變數值引用地址

ldloca: 將指定索引位置的本地變數的地址載入到操作數棧中。通常用於獲取局部變數的地址,以便在方法中對局部變數進行引用傳遞。

該參數為:load local address 載入本地(變數、引用)地址的簡寫。

在C#中,操作引用地址,只能是 ref 或 out 兩種方式,而操作指針(也是引用地址)則需要在unsafe 方法下才可以。

下麵演示一個通過 ldloca 參數返回 out 參數的示例:

Dictionary<string, string> dic = new Dictionary<string, string>();
dic.Add("a", "aaa");
var dicType = typeof(Dictionary<string, string>);
MethodInfo getValue = dicType.GetMethod("TryGetValue");


var method = new DynamicMethod("GetValue", typeof(object), new[] { typeof(Dictionary<string, string>), typeof(string) }, typeof(Dictionary<string, string>));//
var il = method.GetILGenerator();
var outText = il.DeclareLocal(typeof(string));

il.Emit(OpCodes.Ldarg_0); // Load the dic object onto the stack
il.Emit(OpCodes.Ldarg_1);//設置欄位名。
il.Emit(OpCodes.Ldloca_S, outText);// 使用地址變數來接收: out 值
il.Emit(OpCodes.Callvirt, getValue);//bool a=dic.tryGetValue(...,out value)
il.Emit(OpCodes.Pop);//不需要執行的bool返回值

il.Emit(OpCodes.Ldloc_0);//載入 out 變數的值。
il.Emit(OpCodes.Ret); // Return the value

var func = (Func<Dictionary<string, string>, string, object>)method.CreateDelegate(typeof(Func<Dictionary<string, string>, string, object>));

object result = func(dic, "a");

Console.WriteLine(result);

運行結果:

說明:

對於 out 參數,載入的是地址參數,用 loca 地址指令,返回的是值,用 ldloc 值指令。

6、對像成員變數值載入:ldfld 參載入對象成員變數值

ldfld: 將對象的欄位值載入到操作數棧中。用於載入對象的欄位值,以便在方法中進行操作或傳遞給其他方法,該參數為:load filed 載入欄位的簡寫。

ldsfld:該指令用於操作靜態成員變數,該參數為:load static filed 載入靜態欄位的簡寫。

下麵給出一個示例,讀取實例類員變數的值:

        private static void DMethod2()
        {
            MyEntity myEntity = new MyEntity() { ID = 111, Name = "hello" };
            FieldInfo idInfo = typeof(MyEntity).GetField("ID");


            var method = new DynamicMethod("GetterFunc", typeof(object), new[] { typeof(object) }, typeof(MyEntity));
            var il = method.GetILGenerator();

            il.Emit(OpCodes.Ldarg_0); // Load the input object onto the stack
            il.Emit(OpCodes.Ldfld, idInfo); // 載入 Id 成員變數的值到堆棧
            if (idInfo.FieldType.IsValueType)
            {
                il.Emit(OpCodes.Box, idInfo.FieldType); // Box the value type
            }

            il.Emit(OpCodes.Ret); // Return the value

            var func = (Func<object, object>)method.CreateDelegate(typeof(Func<object, object>));

            object result = func(myEntity);

            Console.WriteLine(result);

        }
        class MyEntity
        {
            public int ID;
            public string Name;

        }

運行結果:

註意事項:

1、在定義 DynamicMethod 方法時,最好手動指定 Owner 歸屬,即該動態方法歸屬哪個類型,否則在運行時可能會報以下錯誤: 

2、在操作實例成員變數(取值或賦值)時,通常需要載入兩個參數,第一個是對象值,第二個是對象的成員變數值。

7、其它常用型值載入:ldstr、ldnull、ldtoken

ldstr:將常量字元串值載入到操作數棧中,示例如下:

ldnull:將null值載入到操作數棧中,示例如下:

ldtoken:通常適用於將運行時狀態類型載入到操作數棧中,如將 Type 類型值壓到棧中。

生成的示例代碼:

總結:

本篇教程深入探討了 ILGenerator 中的參數載入指令,通過詳細解釋Ldarg、Ldarga、Ldloc和Ldloca 等各種指令的使用,

讀者能夠清晰地認識到Ld指令用於載入參數或本地變數到堆棧,而St指令用於將值從堆棧存儲到參數或本地變數中。

這些指令為動態方法的生成提供了基礎,幫助開發者更好地掌握IL代碼的生成和調試。

下一篇,將繼續介紹存儲指令部分。 

版權聲明:本文原創發表於 博客園,作者為 路過秋天 本文歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則視為侵權。
個人微信公眾號
Donation(掃碼支持作者):支付寶:
Donation(掃碼支持作者):微信:

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 樹形下拉菜單是許多WPF應用程式中常見的用戶界面元素,它能夠以分層的方式展示數據,提供更好的用戶體驗。本文將深入探討如何基於WPF創建一個可定製的樹形下拉菜單控制項,涵蓋從原理到實際實現的關鍵步驟。 一、需求分析 樹形下拉菜單控制項的核心是將ComboBox與TreeView結合起來,以實現下拉時 ...
  • 面向對象編程(OOP)是一種使用對象及其相互作用設計應用和電腦程式的編程範例。 OOP 中有一些基本的編程概念: 抽象化 (抽象化,也在我們編程世界中 所有類都是抽象化,物以類聚,擁有共同的特性或者行為) 椅子類 人類 動物類 【本質就是歸類】 多態性 【一類多種表現形態】【本質就是抽象化的程度】 ...
  • Avalonia中的Window 在Avalonia中,Window是一個基本的UI元素,它代表了一個應用程式的視窗。每個Window都可以包含其他的UI元素,如按鈕、文本框等,並可以響應各種用戶輸入事件。 在下麵的例子中,制定了當前應用的Window是MainWindow public parti ...
  • 目錄1.Redis簡介2.使用場景3.C# 具體使用介紹(Nuget)StackExchange.RedisFreeRedisNewLife.RedisServiceStack.Redis (收費)4.Redis 常用面試問題以及回答5.建議及經驗分享建議Redis 經驗分享ShareFlow 1. ...
  • 上一篇介紹了 IL 指令的分類以及參數載入指令,該載入指令以ld開頭,將參數載入到棧中,以便於後續執行操作命令。本篇開始介紹參數存儲指令,其指令以st開頭,將棧中的數據,存儲到指定的變數中,以方便後續使用。 ...
  • 引言 在現代化的軟體開發中,單元測試和集成測試是確保代碼質量和可靠性的關鍵部分。ASP.NET Core 社區內提供了強大的單元測試框架,xUnit 是其中之一,它提供了簡單、清晰和強大的測試功能,編寫單元測試有許多優點;有助於回歸、提供文檔及輔助良好的設計。下麵幾節我們來深入淺出探討如何使用 xU ...
  • 隨著跨平臺應用的需求不斷增長,開發人員需要一種能夠在不同操作系統上運行的用戶界面(UI)框架。 Avalonia 是一種引人註目的選擇。在本文中,我們將深入瞭解 Avalonia 是什麼,它與 WPF 的區別,以及它的 UI 繪製引擎和原理、優點,以及一個簡單的示例代碼。 Avalonia 是什麼? ...
  • iNeuOS工業互聯網操作系統“表單設計”功能經過升級後,能夠適用於更多應用場景,從業務上來講可以擴展設備管理、MES等表單類的管理功能,從技術上來講可以支持資料庫單表應用、多級關聯表應用、可以自定義寫SQL語句等,現在支持22個基礎表單組件、9個高級表單組件。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...