聊一聊 C#中有趣的 SourceGenerator生成器

来源:https://www.cnblogs.com/huangxincheng/p/18442192
-Advertisement-
Play Games

一:背景 1. 講故事 前些天在看 AOT的時候關註了下 源生成器,挺有意思的一個東西,今天寫一篇文章簡單的分享下。 二:源生成器探究之旅 1. 源生成器是什麼 簡單來說,源生成器是Roslyn編譯器給程式員開的一道口子,在這個口子里可以塞入一些自定義的cs代碼,讓Roslyn編譯器在編譯代碼的時候 ...


一:背景

1. 講故事

前些天在看 AOT的時候關註了下 源生成器,挺有意思的一個東西,今天寫一篇文章簡單的分享下。

二:源生成器探究之旅

1. 源生成器是什麼

簡單來說,源生成器是Roslyn編譯器給程式員開的一道口子,在這個口子里可以塞入一些自定義的cs代碼,讓Roslyn編譯器在編譯代碼的時候順帶給一起處理了,簡單的說就是 夾帶私貨 ,但古話又說 師不順路, 醫不叩門,所以還是比較尷尬的,看一下官方給的圖,圖中的橙色區域就是夾帶的私貨。

有些朋友肯定好奇,這玩意有什麼用?其實在AOT領域中,JsonSerializer 就使用了 SourceGeneration 來給序列化的類型(WeatherForecast)生成元數據,參考代碼如下:


[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(WeatherForecast))]
internal partial class SourceGenerationContext : JsonSerializerContext
{
}

2. 一個簡單的例子

上面的例子不過多深入,先看看怎麼實現0到1的問題,這裡使用官方例子,用鉤子來實現 分佈方法 的方法體。

  1. 新建 SourceGenerator 類庫項目

這裡面的source就是鉤子代碼,不過目前只能是.NET Standard 2.0項目,應該是要達到最大的相容性,參考代碼如下:


namespace SourceGenerator
{
    [Generator]
    public class HelloSourceGenerator : ISourceGenerator
    {
        public void Execute(GeneratorExecutionContext context)
        {
            // Find the main method
            var mainMethod = context.Compilation.GetEntryPoint(context.CancellationToken);

            // Build up the source code
            string source = $@"
                                // <auto-generated/>
                                   using System;
                                   
                                   namespace {mainMethod.ContainingNamespace.ToDisplayString()}
                                   {{
                                       public static partial class {mainMethod.ContainingType.Name}
                                       {{
                                           static partial void HelloFrom(string name) =>
                                               Console.WriteLine($""Generator says: Hi from '{{name}}'"");
                                       }}
                                   }}
                              ";
            var typeName = mainMethod.ContainingType.Name;

            // Add the source code to the compilation
            context.AddSource($"{typeName}.g.cs", source);
        }

        public void Initialize(GeneratorInitializationContext context)
        {
            // No initialization required for this one
        }
    }
}

  1. 新建 Example_21_15 項目

這是一個控制台程式,引用剛纔的項目,並聲明部分方法 HelloFrom,參考代碼如下:


namespace Example_21_15
{
    partial class Program
    {
        static void Main(string[] args)
        {
            HelloFrom("Generated Code");
            Console.ReadLine();
        }

        static partial void HelloFrom(string name);
    }
}

要記住在 Example_21_15.csproj 中 Include 時要額外增加兩個參數,參考如下:


	<ItemGroup>
		<ProjectReference Include="..\SourceGenerator\SourceGenerator.csproj"
						  OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
	</ItemGroup>

配置好之後就可以把程式跑起來了,可以看到方法體確實是鉤子中的代碼。

二:Roslyn如何夾帶私貨

1. windbg調試

究竟是如何夾帶私貨,本質上是 Roslyn 內部的邏輯,現在的問題是如何給他挖出來了呢?這就需要使用強大的 windbg,採用exe啟動劫持的方式洞察,流程步驟如下:

  1. windbg 的exe劫持

資深的 WinDbg 玩家我相信都知道這個招數,我寫了一個簡單的 bat 腳本,對 dotnet.exe 進行啟動攔截,要提醒的是有安全軟體的話可以先卸載掉,以免出現無許可權的問題。


SET ApplicationName=dotnet.exe
SET WinDbgPath=C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\windbg.exe

REG ADD "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\%ApplicationName%" /v debugger  /t REG_SZ  /d "%WinDbgPath%" /f

ECHO 已成功設置
 
PAUSE 

  1. 修改狗子代碼

最簡單粗暴的方式就是加 Debugger.Break,好讓他在 windbg 中自動中斷。


public class HelloSourceGenerator : ISourceGenerator
{
    public void Execute(GeneratorExecutionContext context)
    {
        Debugger.Break();

        //...
    }
}

  1. 使用 dotnet publish 發佈程式

所有的埋伏做好之後,最後就是用 dotnet publish 來引誘 Roslyn 出洞,參考命令如下 dotnet publish -r win-x64 -c Debug -o D:\testdump, 命令執行之後果然給攔截到了,截圖如下:

由於調用棧難得,再弄一份文字版。


0:029> !clrstack
OS Thread Id: 0x443c (29)
        Child SP               IP Call Site
00000068E603DD18 00007ffe0135d962 [HelperMethodFrame: 00000068e603dd18] System.Diagnostics.Debugger.BreakInternal()
00000068E603DE20 00007ffd6dd357da System.Diagnostics.Debugger.Break() [/_/src/coreclr/System.Private.CoreLib/src/System/Diagnostics/Debugger.cs @ 18]
00000068E603DE50 00007ffd13920522 SourceGenerator.HelloSourceGenerator.Execute(Microsoft.CodeAnalysis.GeneratorExecutionContext)
00000068E603DF10 00007ffd6a4ad4ac Microsoft.CodeAnalysis.SourceGeneratorAdaptor.b__5_5(Microsoft.CodeAnalysis.SourceProductionContext, GeneratorContextBuilder) [/_/src/Compilers/Core/Portable/SourceGeneration/GeneratorAdaptor.cs @ 55]
00000068E603E020 00007ffd6a5bfdd8 Microsoft.CodeAnalysis.UserFunctionExtensions+c__DisplayClass3_0`2[[Microsoft.CodeAnalysis.SourceProductionContext, Microsoft.CodeAnalysis],[System.__Canon, System.Private.CoreLib]].b__0(Microsoft.CodeAnalysis.SourceProductionContext, System.__Canon, System.Threading.CancellationToken) [/_/src/Compilers/Core/Portable/SourceGeneration/UserFunction.cs @ 101]
00000068E603E070 00007ffd6a624e9c Microsoft.CodeAnalysis.SourceOutputNode`1[[System.__Canon, System.Private.CoreLib]].UpdateStateTable(Builder, Microsoft.CodeAnalysis.NodeStateTable`1,System.Collections.Generic.IEnumerable`1>>, System.Threading.CancellationToken) [/_/src/Compilers/Core/Portable/SourceGeneration/Nodes/SourceOutputNode.cs @ 70]
00000068E603E2B0 00007ffd13fd1ed8 Microsoft.CodeAnalysis.DriverStateTable+Builder.GetLatestStateTableForNode[[System.ValueTuple`2[[System.__Canon, System.Private.CoreLib],[System.__Canon, System.Private.CoreLib]], System.Private.CoreLib]](Microsoft.CodeAnalysis.IIncrementalGeneratorNode`1>) [/_/src/Compilers/Core/Portable/SourceGeneration/Nodes/DriverStateTable.cs @ 60]
00000068E603E320 00007ffd6a625349 Microsoft.CodeAnalysis.SourceOutputNode`1[[System.__Canon, System.Private.CoreLib]].AppendOutputs(Microsoft.CodeAnalysis.IncrementalExecutionContext, System.Threading.CancellationToken) [/_/src/Compilers/Core/Portable/SourceGeneration/Nodes/SourceOutputNode.cs @ 102]
00000068E603E460 00007ffd6a4b0837 Microsoft.CodeAnalysis.GeneratorDriver.UpdateOutputs(System.Collections.Immutable.ImmutableArray`1, Microsoft.CodeAnalysis.IncrementalGeneratorOutputKind, Builder, System.Threading.CancellationToken, Builder) [/_/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs @ 340]
00000068E603E520 00007ffd6a4afc9b Microsoft.CodeAnalysis.GeneratorDriver.RunGeneratorsCore(Microsoft.CodeAnalysis.Compilation, Microsoft.CodeAnalysis.DiagnosticBag, System.Threading.CancellationToken) [/_/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs @ 303]
00000068E603ECB0 00007ffd6a4adfc1 Microsoft.CodeAnalysis.GeneratorDriver.RunGeneratorsAndUpdateCompilation(Microsoft.CodeAnalysis.Compilation, Microsoft.CodeAnalysis.Compilation ByRef, System.Collections.Immutable.ImmutableArray`1 ByRef, System.Threading.CancellationToken) [/_/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs @ 54]
00000068E603EE50 00007ffd6a468763 Microsoft.CodeAnalysis.CommonCompiler.RunGenerators(Microsoft.CodeAnalysis.Compilation, System.String, Microsoft.CodeAnalysis.ParseOptions, System.Collections.Immutable.ImmutableArray`1, Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider, System.Collections.Immutable.ImmutableArray`1, Microsoft.CodeAnalysis.DiagnosticBag) [/_/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs @ 838]
00000068E603EF20 00007ffd6a469520 Microsoft.CodeAnalysis.CommonCompiler.CompileAndEmit(Microsoft.CodeAnalysis.TouchedFileLogger, Microsoft.CodeAnalysis.Compilation ByRef, System.Collections.Immutable.ImmutableArray`1, System.Collections.Immutable.ImmutableArray`1, System.Collections.Immutable.ImmutableArray`1, Microsoft.CodeAnalysis.AnalyzerConfigSet, System.Collections.Immutable.ImmutableArray`1, System.Collections.Immutable.ImmutableArray`1, Microsoft.CodeAnalysis.DiagnosticBag, Microsoft.CodeAnalysis.ErrorLogger, System.Threading.CancellationToken, System.Threading.CancellationTokenSource ByRef, Microsoft.CodeAnalysis.Diagnostics.AnalyzerDriver ByRef, System.Nullable`1 ByRef) [/_/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs @ 1145]
00000068E603F280 00007ffd6a468c52 Microsoft.CodeAnalysis.CommonCompiler.RunCore(System.IO.TextWriter, Microsoft.CodeAnalysis.ErrorLogger, System.Threading.CancellationToken) [/_/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs @ 956]
00000068E603F450 00007ffd6a4684b6 Microsoft.CodeAnalysis.CommonCompiler.Run(System.IO.TextWriter, System.Threading.CancellationToken) [/_/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs @ 781]
00000068E603F4C0 00007ffd6aaf9def Microsoft.CodeAnalysis.CompilerServer.CompilerServerHost.RunCompilation(Microsoft.CodeAnalysis.CompilerServer.RunRequest ByRef, System.Threading.CancellationToken) [/_/src/Compilers/Server/VBCSCompiler/CompilerRequestHandler.cs @ 152]
00000068E603F5E0 00007ffd6aaff745 Microsoft.CodeAnalysis.CompilerServer.ClientConnectionHandler+c__DisplayClass8_0.b__1() [/_/src/Compilers/Server/VBCSCompiler/ClientConnectionHandler.cs @ 168]
00000068E603F640 00007ffd6de7b78f System.Threading.Tasks.Task`1[[System.__Canon, System.Private.CoreLib]].InnerInvoke() [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs @ 501]
00000068E603F680 00007ffd6dc364bd System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs @ 179]
00000068E603F6F0 00007ffd6dc50914 System.Threading.Tasks.Task.ExecuteWithThreadLocal(System.Threading.Tasks.Task ByRef, System.Threading.Thread) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 2345]
00000068E603F9C0 00007ffd72d1c663 [DebuggerU2MCatchHandlerFrame: 00000068e603f9c0] 

最後一個問題就是如果找到這個調用棧上的源碼,當然是在 github 上找啦: https://github.com/dotnet/roslyn ,拉下來後就可以根據調用棧上的方法來分析啦,參考如下:

三:總結

在研究底層方面,windbg可謂是一把趁手的兵器,這個例子也算是活生生的論證了一把,否則真的不知道從 Roslyn 何處來論證官方給出的流程圖,對吧。

圖片名稱
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Java中的中間件配置體現在springboot的yml配置文件中。Springboot框架支持微服務和中間件和restful api遠程服務的調用。中間件是Java web系統的中間層的服務系統的調用介面。Springboot的自動裝配和約定大於配置機制初始化springcontext的容器空間和 ...
  • 前言 我們在使用IDEA開發時,經常是和Git一起使用的,這樣能方便的管理我們的代碼。 git的一個特點就是可以有很多分支,這些分支能讓我們區分不同的功能等,非常方便。 有時候,我們需要查看下某個文件中,當前分支與某個分支的差異,應該如何操作呢? 如何查看不同分支的git差異 首先,我們找到我們要對 ...
  • 機緣 電腦信息技術的需要持續學習的興趣和熱情。大學學習電腦編程開發即使是短暫,不會太漫長。大學一年級對於信息科學技術的興趣只增不減。一個剛步入大學校園的高中畢業生,對於將來發生的任何事情都是十分憧憬和好奇。大學的圖書館和自習室經常都會有不同系學生的光顧。高中趕鴨子上架,大學很輕鬆,人很多。 南方 ...
  • 人工智慧artificial intelligence 是電腦互聯網信息技術多年積累和不同互聯網公司共同構造的產物。人工智慧和大數據領域聯繫很大。每個軟體公司都有公司的業務數據和目標客戶。中央倉庫和私有倉庫保存著公有數據和私有數據。Java應用開發中的Maven是對實體本地jar包的一種互聯網分佈 ...
  • 正文 忽然想起,昨天有一個財政局的工作人員,過來轉款。那時候已經快下班了。我們給他辦完之後,他說他沒想到這麼快。看來還得回去,明明都打算不回去了來著。 我有些疑惑。照理來說,財政局應該挺好玩。我這麼問他。他苦笑了一下,說要是好玩就回去上班了,天天都不想去單位。然後開始感慨我們的銀行工作好。 我實在沒 ...
  • 大家好,我是風箏 個人博客:【古時的風箏】。 本文目的為個人學習記錄及知識分享。如果有什麼不正確、不嚴謹的地方請及時指正,不勝感激。 每一個贊都是我前進的動力。 公眾號:「古時的風箏」 前幾天順手改的一個安卓啟動器,開源沒幾天,沒想到GitHub上已經獲得了40多顆星,要知道我前段時間花 ...
  • 部署SpringBoot項目(通關版) 一、概述 使用 java -jar 命令直接部署項目的JAR包和使用Docker製作鏡像進行部署是兩種常見的部署方式。以下是對這兩種方式的概述和簡要的優劣勢分析: 1.1、使用 java -jar 命令直接部署項目的JAR包 概述: 通過 java -jar ...
  • 在 Python 中,字元串格式化是將變數插入到字元串中的一種方式,Python 提供了多種字元串格式化的方法,包括舊式的 % 格式化、新式的 str.format 方法以及 f-string(格式化字元串字面量)。 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...