【抬杠.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中"Static块"是怎么回事,怎么用的,有什么意义
6.Static块 Static块:该类的任何方法被首次触碰到时(马克-to-win: when you touch Test的main方法时),Static块被运行.可以在里面初始化你的stati ...
- 在create-react-app使用less与antd按需加载
使用antd按需加载 使用react-app-rewired对 create-react-app 的默认配置进行自定义 yarn add react-app-rewired --dev /* pack ...
- JavaScript中数组的方法和字符串方法总结
数组是首先的一个对象, 可以通过Array构造器创建一个数组,数组方法总结如下 cacat() 链接两个数组 join() 将数组链接成字符串 pop() 删除最后一个元素 shift() 删 ...
- 文档声明(Doctype)和<!Doctype html>有何作用? 严格模式与混杂模式如何区分?它们有何意义?
文档声明的作用: 文档声明是为了告诉浏览器,当前HTML文档使用什么版本的HTML来写的,这样浏览器才能按照声明的版本来正确的解析. <!doctype html> 的作用就是让浏览器进入 ...
- Unable to negotiate with xx.xxx.xxxx port 22: no matching host key type found. Their offer: ssh-rsa(解决的两种方式)
异常问题: 下班之前升级了一下Git的版本,结果第二天过来拉取远程最新代码的时候就提示了下面的异常问题: Unable to negotiate with xx.xxx.xxxx port 22: n ...
- C++---初识C++
C和C++的关系 C语言是结构化和模块化的语言, 面向过程. C++是在C语言的基础上, 增加了面向对象的机制, 并对C语言的功能进行了扩充. 变量的定义可以出现在程序中的任何行 提供了标准输入输出流 ...
- spring原始注解开发-01
我们使用xml-Bean标签的配置方式和注解做对比理解 1.创建UserDao接口以及UserDao的实现类UserDaoImpl(接口代码省略) public class UserDaoImpl i ...
- Unity—2D边缘检测(描边效果)
一.ShaderLab 1.Alpha值边缘检测 根据图片的Alpha值边缘判定,向内扩一段距离做边缘,颜色设置未描边颜色: 片元着色阶段,向上下左右四个方向做检测,有一个点的透明度为0,判定为边缘: ...
- Blazor Bootstrap 组件库语音组件介绍
Speech 语音识别与合成 通过麦克风语音采集转换为文字(STT),或者通过文字通过语音朗读出来(TTS) 本组件依赖于 BootstrapBlazor.AzureSpeech,使用本组件时需要引用 ...
- 2021.12.21 eleveni的刷题记录
2021.12.21 eleveni的刷题记录 0. 有意思的题 P6701 [POI1997] Genotype https://www.luogu.com.cn/problem/P6701 状压优 ...