背景

之前写了一篇文 【抬杠.NET】如何进行IL代码的开发 介绍了几种IL代码的开发方式。

  • 创建IL项目
  • C#项目混合编译IL
  • 使用InlineIL.Fody
  • 使用DynamicMethod(ILGenerator)

我个人比较喜欢IL和C#在同一个项目的方式(毕竟单单为了一点点IL代码新建一个IL项目也挺麻烦的),所以一直在用InlineIL.Fody。后来在使用过程中发现了一些它的限制,而如果转而使用混合编译的方式呢,又无法对C#代码进行debug了(因为最终的pdb文件实际上是根据IL源码生成的)。
因此,我使用Fody编写了一个插件,叫做MixedIL.Fody,彻底解决了这些问题。

InlineIL.Fody的限制:如何为无公共setter的自动属性赋值

AssemblyKeyNameAttribute为例,这是.Net类库里的一个特性。它有个无公共setter的属性Name,那么如何为这个属性赋值呢。

namespace System.Reflection
{
[AttributeUsage(AttributeTargets.Assembly, Inherited = false)]
public sealed class AssemblyKeyNameAttribute : Attribute
{
public AssemblyKeyNameAttribute(string keyName)
{
KeyName = keyName;
} public string KeyName { get; }
}
}

我们知道,自动属性会有个编译器生成的字段。所以可以用反射获取到该字段,然后赋值即可,如下:

var attribute = new AssemblyKeyNameAttribute("name");
var field = typeof(AssemblyKeyNameAttribute).GetField("<KeyName>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic);
field.SetValue(attribute, "newName");

那如果不用发射呢?可以使用IL代码实现:

.class public abstract sealed auto ansi beforefieldinit System.ObjectExtensions
{
.method public hidebysig static void SetKeyName(class [System.Runtime]System.Reflection.AssemblyKeyNameAttribute attribute, string keyName) cil managed
{
.maxstack 8
ldarg.0
ldarg.1
stfld string [System.Runtime]System.Reflection.AssemblyKeyNameAttribute::'<KeyName>k__BackingField'
ret
}
}

上面的IL代码相当于实现了一个静态方法:

public static class ObjectExtensions
{
public static void SetKeyName(AssemblyKeyNameAttribute attribute, string keyName);
}

所以用InlineIL.Fody实现如下:

public static void SetKeyName(AssemblyKeyNameAttribute attribute, string keyName)
{
IL.Emit.Ldarg(nameof(attribute));
IL.Emit.Ldarg(nameof(keyName));
IL.Emit.Stfld(FieldRef.Field(TypeRef.Type<AssemblyKeyNameAttribute>(), "<KeyName>k__BackingField"));
}

然而编译的时候会报错,Fody/InlineIL: Field '<KeyName>k__BackingField' not found。原因在于AssemblyKeyNameAttribute虽然是个公共类,但是和上面写的SetKeyName方法不在同一个程序集,而私有字段在跨程序集访问时会多一些额外的限制(反射没有这方面的限制)。例如,如果使用DynamicMethod实现上述IL代码,需要指定其构造方法的一个参数skipVisibilitytrue。此外,使用Expression甚至无法绕过改限制。

实现MixedIL.Fody

MixedIL.Fody是一款基于Fody的插件,其原理很简单,就是使用MSBuild增加编译步骤:用Microsoft.NETCore.ILAsm编译IL代码文件,然后将这步生成的dll内的各个方法的il指令填充到C#代码生成的dll内即可。相比上篇文章里介绍的混合编译,使用这个这种方法,项目内C#代码也可以正常调试。该插件的使用方法可以参考MixedIL.Fody的项目介绍。

上一节的需求可以使用此类库实现如下:

  • 编写C#函数桩,无方法体。
using System.Reflection;
using MixedIL; namespace System; public static class ObjectExtensions
{
/// <summary>
/// Set the property <see cref="AssemblyKeyNameAttribute.KeyName"/> that doesn't have setter by its BackingField.
/// </summary>
/// <param name="attribute"></param>
/// <param name="keyName"></param>
[MixedIL]
public static extern void SetKeyName(this AssemblyKeyNameAttribute attribute, string keyName);
}
  • 在这个项目内,创建一个.il文件,将上节中的il代码写入这个文件。
  • il代码访问其他程序集的私有字段也需要绕开限制,所以还需要为该程序集增加一个特性[assembly: IgnoresAccessChecksTo("System.Private.CoreLib")]IgnoresAccessChecksToAttribute这个特性已经包含在MixedIL.Fody内了。
  • 最后编译这个程序集即可。

这个例子可以在这里找到:MixedIL.Example

总结

本文由一个InlineIL.Fody的限制,引出了MixedIL.Fody这个类库的创建动机和介绍。

最后我重新总结一下IL开发的各种方法的优缺点。

方法 优点 缺点 应用场景
创建IL项目 原生IL 创建的时候较为复杂 较多代码需IL实现
C#项目混合编译IL 原生IL 无法调试项目内的C#代码 少量方法需IL实现
使用InlineIL.Fody 纯C#编写体验 某些场景不支持 少量方法需IL实现
使用DynamicMethod 运行时生成代码,灵活 性能有损耗,需缓存一些对象 需运行时生成代码
使用MixedIL.Fody 原生IL - 少量方法需IL实现

【抬杠.NET】如何进行IL代码的开发(续)的更多相关文章

  1. 【抬杠.NET】如何进行IL代码的开发

    背景 在有些时候,由于C#的限制,或是追求更高的性能,我们可以编写IL代码来达到我们的目的.本文将介绍几种IL代码开发的几种方式,环境为visual studio 2019 + net5.0 sdk. ...

  2. 【小白学C#】浅谈.NET中的IL代码

    一.前言 前几天群里有位水友提问:”C#中,当一个方法所传入的参数是一个静态字段的时候,程序是直接到静态字段拿数据还是从复制的函数栈中拿数据“.其实很明显,这和方法参数的传递方式有关,如果是引用传递的 ...

  3. 详解.NET IL代码

    一.前言 IL是什么? Intermediate Language (IL)微软中间语言 C#代码编译过程? C#源代码通过LC转为IL代码,IL主要包含一些元数据和中间语言指令: JIT编译器把IL ...

  4. 用ildasm/ilasm修改IL代码

    原文地址:http://www.cnblogs.com/dudu/archive/2011/05/17/ildasm_ilasm_il.html 在开发中遇到这样一个场景,需要修改一个dll文件(.N ...

  5. 认识IL代码---从开始到现在 <第二篇>

    ·IL代码分析方法 ·IL命令解析 ·.NET学习方法论 1.引言 自从『你必须知道.NET』系列开篇以来,受到大家很多的关注和支持,给予了anytao巨大的鼓励和动力.俱往昔,我发现很多的园友都把目 ...

  6. IL代码

    浅析.NET IL代码   一.前言 IL是什么? Intermediate Language (IL)微软中间语言 C#代码编译过程? C#源代码通过LC转为IL代码,IL主要包含一些元数据和中间语 ...

  7. IL代码完结篇

    读懂IL代码就这么简单(三)完结篇   一 前言 写了两篇关于IL指令相关的文章,分别把值类型与引用类型在 堆与栈上的操作区别详细的写了一遍这第三篇也是最后一篇,之所以到第三篇就结束了,是因为以我现在 ...

  8. 一、源代码-面向CLR的编译器-托管模块-(元数据&IL代码)

    本文脉络图如下: 1.CLR(Common Language Runtime)公共语言运行时简介 (1).公共语言运行时是一种可由多种编程语言一起使用的"运行时". (2).CLR ...

  9. 详解.NET IL代码(一)

    本文主要介绍IL代码,内容大部分来自网上,进行整理合并的. 一.IL简介 为什么要了解IL代码? 如果想学好.NET,IL是必须的基础,IL代码是.NET运行的基础,当我们对运行结果有异议的时候,可以 ...

随机推荐

  1. echarts中boundaryGap属性

    boundaryGap:false boundaryGap:true 代码处: xAxis: { type: "category", data: ["06-01" ...

  2. Ubuntu安装开发者平台Backstage

    Ubuntu安装开发者平台Backstage 什么是Backstage? Backstage是一个构建开发者门户的开源平台.通过支持一个集中的软件分类,Backstage可以保存并发布你的微服务和基础 ...

  3. spring-注入list集合对象(值是对象)

    1.创建stu类 public class Stu { // //1.数组类型 // private String[] courses; // // //2.list集合属性 // private L ...

  4. SpringMVC的数据响应方式-页面跳转

    1.返回字符串形式 直接返回字符串:此种方式会返回字符串与视图解析器的前后缀拼接后跳转 有关视图解析器的拼接请访问此地址 注意:WEB-INF下的资源一般不能访问,因为转发是服务器的操作所以可以访问到 ...

  5. mysql HikariCP连接池配置

    #连接池配置 #最小空闲连接,默认值10,小于0或大于maximum-pool-size,都会重置为maximum-pool-size spring.datasource.hikari.minimum ...

  6. GRPC-go版本

    GRPC-go版本 1.安装GO,protobuf 只适合有梯子的 GO的安装没必要说了 protobuf :https://github.com/protocolbuffers/protobuf/r ...

  7. 为vscode开发一款svn右键菜单扩展

    在我平时的工作中会经常用到svn blame这个命令,但是vscode现有的svn扩展普遍都不能自定义右键菜单. 所以我产生一个想法:自己动手为vscode开发一款svn的扩展来定制右键菜单,本文记录 ...

  8. 小米手环解锁MacOS系统笔记本MacBookPro

    通过小米手环解锁笔记本 官方windows是提供了方法的. 我目前用的MacBookPro,所以说下苹果笔记本的解锁方式. 安装软件BLEUnlock 库 安装方式: brew 安装 brew ins ...

  9. React 日常记录

    以下是学习重点 原文地址 浏览器环境 JS解析和执行.绘制.事件处理.静态资源加载和处理 GUI渲染线程和Javascript线程 调度策略 先到先得(FCFS) 对短进程不利 对I/O密集不利 单处 ...

  10. 2021.07.19 P2624 明明的烦恼(prufer序列,为什么杨辉三角我没搞出来?)

    2021.07.19 P2624 明明的烦恼(prufer序列,为什么杨辉三角我没搞出来?) [P2624 HNOI2008]明明的烦恼 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn ...