【抬杠.NET】如何进行IL代码的开发
背景
在有些时候,由于C#的限制,或是追求更高的性能,我们可以编写IL代码来达到我们的目的。本文将介绍几种IL代码开发的几种方式,环境为visual studio 2019 + net5.0 sdk。
本文所用代码均在 https://github.com/huoshan12345/ILDevelopSamples 可以找到
方法1:创建IL项目
项目 System.Runtime.CompilerServices.Unsafe 就是由这种方式编写。
目前,visual studio 2019和dotnet命令并不支持直接创建IL项目,但实际上二者是有相关支持的,所以我们需要“救国一下”。
1.首先我们使用visual studio 2019创建一个空解决方案名为ILDevelopSamples
,然后创建一个netstandard2.0的library项目ILDevelopSamples.ILProject
,然后关闭解决方案。
2.然后修改该项目的.csproj文件的内容为:
<Project Sdk="Microsoft.NET.Sdk.IL">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<_HasReferenceToSystemRuntime>true</_HasReferenceToSystemRuntime>
</PropertyGroup>
<ItemGroup>
<Content Include="**\*.il" />
</ItemGroup>
</Project>
3.将该.csproj文件的扩展名改为.ilproj
4.修改.sln文件内容,将其中对于该项目的引用路径修正,即将其扩展名由.csproj改为.ilproj
5.在解决方案根目录下打开命令行,新建一个文件名为global.json
并填写内容为
- {
- "sdk": {
- "version": "5.0",
- "rollForward": "latestMajor",
- "allowPrerelease": false
- },
- "msbuild-sdks": {
- "Microsoft.NET.Sdk.IL": "5.0.0"
- }
- }
重新打开解决方案,然后此时vs就可以正常加载该项目了。
6.我们可以添加一个il文件来编写代码了,例如:


- .assembly extern mscorlib {}
- .assembly ILDevelopSamples.ILProject
- {
- .ver 1:0:0:0
- }
- .module ILDevelopSamples.ILProject.dll
- .class public abstract auto ansi sealed beforefieldinit System.IntHelper
- {
- .method public hidebysig static int32 Square(int32 a) cil managed aggressiveinlining
- {
- .maxstack 2
- ldarg.0
- dup
- mul
- ret
- }
- }
7.到此,该项目就可以正常编译并被其他.net项目引用了。
方法2:C#项目混合编译IL
这种方式就是通过自定义msbuild的targets,来实现在某个已有的C#项目中添加并编译.il文件,即.cs和.il两者的混合编译。
目前有一款vs的插件对此进行了支持:ILSupport 但这个插件使用了windows平台独有的ildasm.exe和ilasm.exe,所以无法在非windows环境使用,也无法在rider或者使用dotnet命令进行编译。
不过我们可以使用跨平台版本的ildasm/ilasm,并借鉴它的思路,在此感谢这个插件的作者。
1.我们创建一个netstandard2.0的library项目ILDevelopSamples.ILMixed
2.然后在该项目中创建一个新文件il.targets
, 并填写以下内容


- <?xml version="1.0" encoding="utf-8"?>
- <Project>
- <PropertyGroup>
- <_OSPlatform Condition="$([MSBuild]::IsOSPlatform('windows'))">win</_OSPlatform>
- <_OSPlatform Condition="$([MSBuild]::IsOSPlatform('linux'))">linux</_OSPlatform>
- <_OSPlatform Condition="$([MSBuild]::IsOSPlatform('osx'))">osx</_OSPlatform>
- <_OSPlatform Condition="$([MSBuild]::IsOSPlatform('freebsd'))">freebsd</_OSPlatform>
- <_OSArchitecture>$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)</_OSArchitecture>
- <MicrosoftNetCoreIlasmPackageRuntimeId Condition="'$(MicrosoftNetCoreIlasmPackageRuntimeId)' == ''">$(_OSPlatform)-$(_OSArchitecture.ToLower())</MicrosoftNetCoreIlasmPackageRuntimeId>
- <MicrosoftNETCoreILAsmVersion Condition="'$(MicrosoftNETCoreILAsmVersion)' == ''">5.0.0</MicrosoftNETCoreILAsmVersion>
- <MicrosoftNetCoreIlasmPackageName>runtime.$(MicrosoftNetCoreIlasmPackageRuntimeId).microsoft.netcore.ilasm</MicrosoftNetCoreIlasmPackageName>
- <MicrosoftNetCoreIldasmPackageName>runtime.$(MicrosoftNetCoreIlasmPackageRuntimeId).microsoft.netcore.ildasm</MicrosoftNetCoreIldasmPackageName>
- <!-- If ILAsmToolPath is specified, it will be used and no packages will be restored
- Otherwise packages will be restored and ilasm and ildasm will be referenced from their packages. -->
- <_IlasmDir Condition="'$(ILAsmToolPath)' != ''">$([MSBuild]::NormalizeDirectory($(ILAsmToolPath)))</_IlasmDir>
- <_IldasmDir Condition="'$(ILAsmToolPath)' != ''">$([MSBuild]::NormalizeDirectory($(ILAsmToolPath)))</_IldasmDir>
- <CoreCompileDependsOn Condition="'$(ILAsmToolPath)' == ''">$(CoreCompileDependsOn);ResolveIlAsmToolPaths</CoreCompileDependsOn>
- </PropertyGroup>
- <ItemGroup Condition="'$(ILAsmToolPath)' == ''">
- <_IlasmPackageReference Include="$(MicrosoftNetCoreIlasmPackageName)" Version="$(MicrosoftNETCoreILAsmVersion)" />
- <_IlasmPackageReference Include="$(MicrosoftNetCoreIldasmPackageName)" Version="$(MicrosoftNETCoreILAsmVersion)" />
- <PackageReference Include="@(_IlasmPackageReference)" ExcludeAssets="native" PrivateAssets="all" IsImplicitlyDefined="true" />
- </ItemGroup>
- <ItemGroup>
- <IL Include="**\*.il" Exclude="**\obj\**\*.il;**\bin\**\*.il" />
- </ItemGroup>
- <Target Name="ProcessILAfterCompile" AfterTargets="Compile">
- <CallTarget Targets="ResolveIlAsmToolPaths; InitializeIL; CoreDecompile; CoreCompileIL" />
- </Target>
- <Target Name="ResolveIlAsmToolPaths">
- <ItemGroup>
- <_IlasmPackageReference NativePath="$(NuGetPackageRoot)\%(Identity)\%(Version)\runtimes\$(MicrosoftNetCoreIlasmPackageRuntimeId)\native" />
- <_IlasmSourceFiles Include="%(_IlasmPackageReference.NativePath)\**\*" />
- </ItemGroup>
- <Error Condition="!Exists('%(_IlasmPackageReference.NativePath)')" Text="Package %(_IlasmPackageReference.Identity)\%(_IlasmPackageReference.Version) was not restored" />
- <PropertyGroup>
- <_IlasmDir Condition="'$(_IlasmDir)' == '' and '%(_IlasmPackageReference.Identity)' == '$(MicrosoftNetCoreIlasmPackageName)'">%(_IlasmPackageReference.NativePath)/</_IlasmDir>
- <_IldasmDir Condition="'$(_IldasmDir)' == '' and '%(_IlasmPackageReference.Identity)' == '$(MicrosoftNetCoreIldasmPackageName)'">%(_IlasmPackageReference.NativePath)/</_IldasmDir>
- </PropertyGroup>
- </Target>
- <Target Name="InitializeIL">
- <PropertyGroup>
- <ILFile>@(IntermediateAssembly->'%(RootDir)%(Directory)%(Filename).il', ' ')</ILFile>
- <ILResourceFile>@(IntermediateAssembly->'%(RootDir)%(Directory)%(Filename).res', ' ')</ILResourceFile>
- <ILFileBackup>@(IntermediateAssembly->'%(RootDir)%(Directory)%(Filename).il.bak', ' ')</ILFileBackup>
- <AssemblyFile>@(IntermediateAssembly->'"%(FullPath)"', ' ')</AssemblyFile>
- </PropertyGroup>
- </Target>
- <Target Name="CoreDecompile"
- Inputs="@(IntermediateAssembly)"
- Outputs="$(ILFile)"
- Condition=" Exists ( @(IntermediateAssembly) ) ">
- <PropertyGroup>
- <ILDasm>$(_IldasmDir)ildasm $(AssemblyFile) /OUT="$(ILFile)"</ILDasm>
- </PropertyGroup>
- <!--<Message Text="$(ILDasm)" Importance="high"/>-->
- <Exec Command="$(ILDasm)" ConsoleToMSBuild="true" StandardOutputImportance="Low">
- <Output TaskParameter="ExitCode" PropertyName="_IldasmCommandExitCode" />
- </Exec>
- <Error Condition="'$(_IldasmCommandExitCode)' != '0'" Text="ILDasm failed" />
- <Copy SourceFiles="$(ILFile)" DestinationFiles="$(ILFileBackup)" />
- <ItemGroup>
- <!--MSBuild maintains an item list named FileWrites that contains the files that need to be cleaned.
- This list is persisted to a file inside the obj folder that is referred to as the "clean cache."
- You can place additional values into the FileWrites item list so that they are removed when the project is cleaned up.-->
- <FileWrites Include="$(ILFile)" />
- <FileWrites Include="$(ILResourceFile)" />
- <FileWrites Include="$(ILFileBackup)" />
- </ItemGroup>
- <PropertyGroup>
- <ILSource>$([System.IO.File]::ReadAllText($(ILFile)))</ILSource>
- <Replacement>// method ${method} forwardref removed for IL import</Replacement>
- <Pattern>\.method [^{}]+ cil managed forwardref[^}]+} // end of method (?<method>[^ \r\t\n]+)</Pattern>
- <ILSource>$([System.Text.RegularExpressions.Regex]::Replace($(ILSource), $(Pattern), $(Replacement)))</ILSource>
- <Pattern>\.method [^{}]+ cil managed[^\a]+"extern was not given a DllImport attribute"[^}]+} // end of method (?<method>[^ \r\t\n]+)</Pattern>
- <ILSource>$([System.Text.RegularExpressions.Regex]::Replace($(ILSource), $(Pattern), $(Replacement)))</ILSource>
- </PropertyGroup>
- <WriteLinesToFile File="$(ILFile)" Lines="$(ILSource)" Overwrite="true" />
- <PropertyGroup>
- <ILSource />
- </PropertyGroup>
- <Delete Files="@(IntermediateAssembly)" />
- </Target>
- <Target Name="CoreCompileIL"
- Inputs="$(MSBuildAllProjects);
- @(Compile)"
- Outputs="@(IntermediateAssembly)"
- Returns=""
- DependsOnTargets="$(CoreCompileDependsOn)">
- <PropertyGroup>
- <_OutputTypeArgument Condition="'$(OutputType)' == 'Library'">-DLL</_OutputTypeArgument>
- <_OutputTypeArgument Condition="'$(OutputType)' == 'Exe'">-EXE</_OutputTypeArgument>
- <_KeyFileArgument Condition="'$(KeyOriginatorFile)' != ''">-KEY="$(KeyOriginatorFile)"</_KeyFileArgument>
- <_IlasmSwitches>-QUIET -NOLOGO</_IlasmSwitches>
- <_IlasmSwitches Condition="'$(FoldIdenticalMethods)' == 'True'">$(_IlasmSwitches) -FOLD</_IlasmSwitches>
- <_IlasmSwitches Condition="'$(SizeOfStackReserve)' != ''">$(_IlasmSwitches) -STACK=$(SizeOfStackReserve)</_IlasmSwitches>
- <_IlasmSwitches Condition="'$(DebugType)' == 'Full'">$(_IlasmSwitches) -DEBUG</_IlasmSwitches>
- <_IlasmSwitches Condition="'$(DebugType)' == 'Impl'">$(_IlasmSwitches) -DEBUG=IMPL</_IlasmSwitches>
- <_IlasmSwitches Condition="'$(DebugType)' == 'PdbOnly'">$(_IlasmSwitches) -DEBUG=OPT</_IlasmSwitches>
- <_IlasmSwitches Condition="'$(Optimize)' == 'True'">$(_IlasmSwitches) -OPTIMIZE</_IlasmSwitches>
- <!--<_IlasmSwitches Condition="'$(IlasmResourceFile)' != ''">$(_IlasmSwitches) -RESOURCES=$(IlasmResourceFile)</_IlasmSwitches>-->
- <ILAsm>$(_IlasmDir)ilasm $(_IlasmSwitches) $(_OutputTypeArgument) $(IlasmFlags) -OUTPUT=@(IntermediateAssembly->'"%(FullPath)"', ' ') @(IL->'"%(FullPath)"', ' ')</ILAsm>
- </PropertyGroup>
- <!--<Message Text="$(ILAsm)" Importance="high"/>-->
- <PropertyGroup Condition=" Exists ( '$(ILFile)' ) ">
- <ILAsm>$(ILAsm) "$(ILFile)"</ILAsm>
- </PropertyGroup>
- <Exec Command="$(ILAsm)">
- <Output TaskParameter="ExitCode" PropertyName="_ILAsmExitCode" />
- </Exec>
- <Error Condition="'$(_ILAsmExitCode)' != '0'" Text="ILAsm failed" />
- <ItemGroup>
- <FileWrites Include="@(IntermediateAssembly->'%(RootDir)%(Directory)DesignTimeResolveAssemblyReferencesInput.cache', ' ')" />
- </ItemGroup>
- <Touch Files="$(ILFile)" />
- </Target>
- </Project>
3.修改该项目的.csproj文件为
- <Project Sdk="Microsoft.NET.Sdk">
- <Import Project="il.targets" />
- <PropertyGroup>
- <TargetFramework>netstandard2.0</TargetFramework>
- </PropertyGroup>
- </Project>
4.我们可以添加一个il文件来编写代码了,例如:
- .assembly extern mscorlib {}
- .class public abstract auto ansi sealed beforefieldinit System.ObjectHelper
- {
- .method public hidebysig static int32 SizeOf<T>() cil managed aggressiveinlining
- {
- .maxstack 1
- sizeof !!0
- ret
- }
- }
5.此时该项目里面的方法即ObjectHelper.SizeOf<T>
已经可以被其他项目所使用了。不过,如果我想在本项目中调用这个方法呢?
6.创建与该IL代码中的类System.ObjectHelper
同名的C#类,即命名空间为System
,类名为ObjectHelper
,并填写内容为
- using System.Runtime.CompilerServices;
- namespace System
- {
- public class ObjectHelper
- {
- [MethodImpl(MethodImplOptions.ForwardRef)]
- public static extern int SizeOf<T>();
- }
- }
可以注意到,此处有一个方法和IL代码中的方法签名相同,但是没有body,并且有一个特性即[MethodImpl(MethodImplOptions.ForwardRef)]
7.此时,在本项目中,也可以调用ObjectHelper.SizeOf<T>
这个方法了。
以上这些功能得以实现的奥秘就来自于il.targets
这个文件。其思路如下:
- C#项目并不会编译il文件,所以先令这个项目编译为dll
- 反编译该dll为il文件
- 将标记为ForwardRef的方法注释掉
- 将反编译的il文件和项目中原有的il文件一起编译为dll
方法3:使用InlineIL.Fody
InlineIL.Fody
这个包能让你在编写C#代码时,能够调用其提供的与IL指令的C#方法,在C#项目编译时,这个库又会将这些方法替换成真正的IL指令。
1.我们创建一个netstandard2.0的library项目ILDevelopSamples.Fody
2.修改该项Fody
和InlineIL.Fody
这两个nuget包。
3.我们创建一个新C#类,然后填写以下内容
- using InlineIL;
- using static InlineIL.IL.Emit;
- namespace System
- {
- public class GenericHelper
- {
- public static bool AreSame<T>(ref T a, ref T b)
- {
- Ldarg(nameof(a));
- Ldarg(nameof(b));
- Ceq();
- return IL.Return<bool>();
- }
- }
- }
4.可以看出GenericHelper.AreSame<T>
这个方法内部就是调用了一些在InlineIL.IL.Emit
命名空间下的方法,它们都分别与一条IL指令对应。
方法4:使用ILGenerator动态构建IL
这种方法就是使用ILGenerator在运行时构建IL代码,例如使用DynamicMethod动态构建一个方法。
和上面三种方法的使用场景有所不同,它适合于那些需要在运行时才能确定的代码。
这个项目 AspectCore,在这方面有广泛引用。这是一款卓越的Aop框架。
总结
本文介绍了三种进行IL代码的方法。它们各自有自己优缺点和应用场景,总结如下
方法 | 优点 | 缺点 | 应用场景 |
创建IL项目 | 原生IL | 创建的时候较为复杂 | 较多代码需IL实现 |
C#项目混合编译IL | 原生IL,项目内C#可调用IL方法 | 项目特别大的时候编译可能会慢 | 少量方法或类需IL实现 |
使用InlineIL.Fody | 纯C#编写体验 | 相比原生IL有极其极其轻微的性能损耗 | 少量方法或类需IL实现 |
使用ILGenerator | 运行时生成代码,灵活 | 性能有损耗,需缓存一些对象 | 需运行时生成代码 |
有什么问题欢迎一起探讨~~~
【抬杠.NET】如何进行IL代码的开发的更多相关文章
- 【抬杠.NET】如何进行IL代码的开发(续)
背景 之前写了一篇文 [抬杠.NET]如何进行IL代码的开发 介绍了几种IL代码的开发方式. 创建IL项目 C#项目混合编译IL 使用InlineIL.Fody 使用DynamicMethod(ILG ...
- 【小白学C#】浅谈.NET中的IL代码
一.前言 前几天群里有位水友提问:”C#中,当一个方法所传入的参数是一个静态字段的时候,程序是直接到静态字段拿数据还是从复制的函数栈中拿数据“.其实很明显,这和方法参数的传递方式有关,如果是引用传递的 ...
- 详解.NET IL代码
一.前言 IL是什么? Intermediate Language (IL)微软中间语言 C#代码编译过程? C#源代码通过LC转为IL代码,IL主要包含一些元数据和中间语言指令: JIT编译器把IL ...
- 用ildasm/ilasm修改IL代码
原文地址:http://www.cnblogs.com/dudu/archive/2011/05/17/ildasm_ilasm_il.html 在开发中遇到这样一个场景,需要修改一个dll文件(.N ...
- 认识IL代码---从开始到现在 <第二篇>
·IL代码分析方法 ·IL命令解析 ·.NET学习方法论 1.引言 自从『你必须知道.NET』系列开篇以来,受到大家很多的关注和支持,给予了anytao巨大的鼓励和动力.俱往昔,我发现很多的园友都把目 ...
- IL代码
浅析.NET IL代码 一.前言 IL是什么? Intermediate Language (IL)微软中间语言 C#代码编译过程? C#源代码通过LC转为IL代码,IL主要包含一些元数据和中间语 ...
- IL代码完结篇
读懂IL代码就这么简单(三)完结篇 一 前言 写了两篇关于IL指令相关的文章,分别把值类型与引用类型在 堆与栈上的操作区别详细的写了一遍这第三篇也是最后一篇,之所以到第三篇就结束了,是因为以我现在 ...
- 一、源代码-面向CLR的编译器-托管模块-(元数据&IL代码)
本文脉络图如下: 1.CLR(Common Language Runtime)公共语言运行时简介 (1).公共语言运行时是一种可由多种编程语言一起使用的"运行时". (2).CLR ...
- 详解.NET IL代码(一)
本文主要介绍IL代码,内容大部分来自网上,进行整理合并的. 一.IL简介 为什么要了解IL代码? 如果想学好.NET,IL是必须的基础,IL代码是.NET运行的基础,当我们对运行结果有异议的时候,可以 ...
随机推荐
- 解决两个相邻的span,或者input和button中间有间隙,在css中还看不到
<span id="time"></span><span id="second"></span> <inp ...
- Centos7搭建内网DNS服务器
一.配置阿里云yum源 执行脚本配置阿里云的yum源,已配置yum源的可以忽略 #!/bin/bash # ******************************************** ...
- mapboxgl绘制3D线
最近遇到个需求,使用mapboxgl绘制行政区划图层,要求把行政区划拔高做出立体效果,以便突出显示. 拿到这个需求后,感觉很简单呀,只需要用fill-extrusion方式绘制就可以啦,实现出来是这个 ...
- 7.2、compute节点配置
用于创建虚拟机的节点: 0.配置openstack版本yum源: yum install centos-release-openstack-rocky 1.nova-compute的安装: (1)安装 ...
- Spring官方发布新成员:Spring GraphQL
近日,在GraphQL Java诞生6周年的时候,Spring社区通过博客宣布正式创建全新项目:Spring GraphQL,同时还发布了这个新项目的里程碑1.0版本. 博客原文:https://sp ...
- LAMP——实现phpMyadmin、wordpress及Discuz应用部署
一.环境准备 操作系统:Centos8.3.2011 软件:Apache2.4.37.Mysql8.0.21.PHP7.2.24 二.安装过程 1.安装phpmyadmin 1.1.安装软件包并启动服 ...
- Solon 1.5.11 发布,增加国际化插件
Solon 是一个轻量的Java基础开发框架.强调,克制 + 简洁 + 开放的原则:力求,更小.更快.更自由的体验.支持:RPC.REST API.MVC.Job.Micro service.WebS ...
- 删除有序数组中的重复项II
题目描述 给你一个有序数组 nums ,请你原地删除重复出现的元素,使每个元素最多出现两次,返回删除后数组的新长度. 不要使用额外的数组空间,你必须在原地修改输入数组 并在使用O(1)额外空间的条件下 ...
- AcWing 1143. 联络员
Tyvj已经一岁了,网站也由最初的几个用户增加到了上万个用户,随着Tyvj网站的逐步壮大,管理员的数目也越来越多,现在你身为Tyvj管理层的联络员,希望你找到一些通信渠道,使得管理员两两都可以联络(直 ...
- 暑假自学java第十一天
1,使用java.util.Arrays类处理数组 (1 ) public static void sort(数值类型 [ ] a):对指定的数值型数组按数字升序进行排序.在数组排序中设计一个简单的冒 ...