首页 > 编程语言 >聊一聊 C#中有趣的 SourceGenerator生成器

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

时间:2024-09-30 17:26:14浏览次数:9  
标签:src C# 生成器 System Microsoft Threading cs SourceGenerator CodeAnalysis

一:背景

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 何处来论证官方给出的流程图,对吧。

图片名称

标签:src,C#,生成器,System,Microsoft,Threading,cs,SourceGenerator,CodeAnalysis
From: https://www.cnblogs.com/huangxincheng/p/18442192

相关文章

  • 常见问题解决 --- 如何解决CROS跨域问题
    问题原因:前后端不是一个服务导致的浏览器禁止访问的安全问题。比如前端部署在http://x.x.x.x:8888,后端部署在http://x.x.x.x:9999,由于端口不一致,浏览器安全起见不允许一个web页面有不同ip或端口的地址发送出流量。在开发者工具可以看出CROS错误。解决办法:关闭浏览器安全策......
  • Capital许可证管理系统介绍及使用指南
    随着企业对软件依赖程度的不断加深,Capital许可证管理系统成为了确保软件合规使用和提升管理效率的重要工具。本文将为您详细介绍Capital许可证管理系统的功能和使用指南,帮助您轻松实现合规与高效管理。一、Capital许可证管理系统介绍Capital许可证管理系统是一款专为企业设计的......
  • CITS2402 Introduction to Data Science
    CITS2402Introduction to Data ScienceSemester2, 2024AssignmentAssessed,worth 20%. Due: 11:59pm,Friday 4th  October 20241 AimThisassignmentaimstoinvestigatethesimilaritiesanddifferencesbetweenAustraliaandNew Zealand regarding......
  • 解决 PbootCMS 网站程序提示“执行 SQL 发生错误
    步骤一:清理缓存文件打开FTP客户端使用常用的FTP客户端(如FileZilla、WinSCP等)连接到服务器。找到runtime文件夹在FTP客户端中找到PbootCMS的安装目录,通常是在 public_html 或 www 目录下。删除runtime文件夹中的内容进入 runtime 文件夹,删除......
  • 有效地解决 PbootCMS 网站程序提示“执行 SQL 发生错误!错误:DISK I/O ERROR”的问题,并
    打开FTP客户端使用FTP客户端连接到服务器。找到runtime文件夹在FTP客户端中找到PbootCMS的安装目录,例如:  /var/www/html/pbootcms删除runtime文件夹中的内容进入 runtime 文件夹,删除其中的所有文件和子文件夹。升级程序备份现有数......
  • PBOOTCMS网站程序提示“执行SQL发生错误!错误:DISK I/O ERROR”
    当使用PbootCMS网站程序时,如果遇到提示“执行SQL发生错误!错误:DISKI/OERROR”,通常是因为磁盘空间不足导致的。这可能是由于系统生成了大量的缓存文件所致。以下是一些具体的解决方法:解决方法方法一:清理缓存文件打开FTP客户端使用FTP客户端连接到服务器。找到......
  • Docker入门实践(五)
    什么是容器?定义容器是每个应用组件的独立进程,它完全和你机器的其他东西隔离。独立性:每个容器包含所有它需要的功能,不依赖于主机上预安装的依赖项。隔离行:由于容器是隔离运行的,它们对主机和其他容器的影响最小,增加了应用的安全性。独立性:每个容器独立管理,删除一个容器不会......
  • MSVC工具链
    一、MSVC工具链有哪些工具MSVC(MicrosoftVisualC++)工具链是用于开发C++应用程序的综合工具集。它包含了多种工具和组件,帮助开发者编写、编译、调试和优化C++代码。以下是MSVC工具链的一些主要组成部分:1.编译器(Compiler):-cl.exe:MSVC的C/C++编译器,负责将源代码编译成目标代码(......
  • OpenCV(图像对比度增强)
    目录1.直方图均衡化2.自适应直方图均衡化3.限制对比度自适应直方图均衡化4.线性对比度拉伸5.Gamma校正6.Retinex方法7.多尺度对比度增强8.方法选择与应用场景总结增强图像对比度是图像处理中的一个重要步骤,旨在提高图像中不同亮度区域之间的差异,使细节更加清晰和明显......
  • AT_awtf2024_c Fuel
    dp好题。直接考虑不好考虑,但我们有显然的转化,我们并不会多加毫无意义的油。所以我们要加的油是\(l-2C\),那么现在要求我们在每一时刻油不小于\(0\),并最小化距离。发现不太好贪心,考虑dp。如果我们到了一个加油站,我们一定会将当前的油给加满至\(C\),除非已经没必要了,但这并不重......