【抬杠.NET】如何进行IL代码的开发(续)
背景
之前写了一篇文 【抬杠.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代码,需要指定其构造方法的一个参数skipVisibility为true。此外,使用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代码的开发(续)的更多相关文章
- 【抬杠.NET】如何进行IL代码的开发
背景 在有些时候,由于C#的限制,或是追求更高的性能,我们可以编写IL代码来达到我们的目的.本文将介绍几种IL代码开发的几种方式,环境为visual studio 2019 + net5.0 sdk. ...
- 【小白学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运行的基础,当我们对运行结果有异议的时候,可以 ...
随机推荐
- java中什么叫覆盖Override?请给实例
5.覆盖(Override) 马克-to-win:方法的覆盖(Override)是指子类重写从父类继承来的一个同名方法(参数.返回值也同). 例1.5.1-- class AAAMark_to_win ...
- java中当static块和构造函数同时出现,顺序是?
静态块先于构造函数执行 class Student { int age; String name; static int count; public Student() { ...
- spring-注入list集合对象(值是对象)
1.创建stu类 public class Stu { // //1.数组类型 // private String[] courses; // // //2.list集合属性 // private L ...
- oracle sqlplus不支持上下键查看历史记录问题
我们需要安装rlwrap软件 系统采样: [root@rac-2 src]# su - oracle Last login: Fri Mar 18 23:36:25 CST 2022 on pts/0 ...
- re模块、collections模块、time模块、datetime模块
正则表达式之re模块 re.findall用法(重要) re.findall( '正则表达式' , '待匹配的字符' ) 找出所有的目标字符,用列表的形式展现,如果找不到返回空列表. import r ...
- C++五子棋(三)——判断鼠标有效点击
分析 在鼠标左键点击时,我们不能让新棋子在已有棋子的位置落下,同时我们还要让棋子在规定位置落下--棋盘线的交点处. 功能实现 创建数据类型 创建头文件chessData.h和源文件chessData. ...
- 【直播回顾】OpenHarmony知识赋能第四期第三课——I2C驱动开发
3月24日晚上19点,知识赋能第四期直播的第三节课<OpenHarmony标准系统HDF框架之I2C驱动开发>,在OpenHarmony开发者成长计划社群内成功举办. 本期课程,由拓 ...
- MQ系列:消息中间件执行原理
1 关于消息中间件 1.1 什么是消息中间件? 消息中间件是指在分布式系统中完成消息的发送和接收的基础软件. 消息中间件也可以称消息队列(Message Queue / MQ),用高效可靠的消息传递机 ...
- Angular中directive——scope选项与绑定策略,这个也经常迷惑的。
开门见山地说,scope:{}使指令与外界隔离开来,使其模板(template)处于non-inheriting(无继承)的状态,当然除非你在其中使用了transclude嵌入,这点之后的笔记会再详细 ...
- 我的树莓派 Raspberry Pi Zero 2W 折腾笔记,记录一些遇到的问题和解决办法
0x00 前言说明 最近买了一块Raspberry Pi Zero 2W来玩,目的是想搭建一台远程运行的个人服务器,上面放个博客.点个灯啥的.于是就有了这篇文章. 树莓派官网地址:https://ww ...