【抬杠.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运行的基础,当我们对运行结果有异议的时候,可以 ...
随机推荐
- SQLite实现数据库的储存2+SQLite数据库可视化工具SQLite Stadio
今日所学 SQLite实现数据库的储存 查看数据库的两种方法 Android 中 SQLite 数据库的查看 - woider - 博客园 SQLite Studio安装教程 [SQLite]可视化工 ...
- Python入门-系统模块time
1.time模块 时间戳:1970年,1月1日开始时间元祖:包含日期,时间,保存日期结构的元祖对象格式化时间日期:按照指定的标记进行格式化处理 时间戳 import time time_num = t ...
- Struts2-EL表达式为什么能获取值栈数据
1.EL表达式能获取域对象值 2.向域对象里面放值使用setAttribute方法,获取使用getAttribute方法 3.底层增强request对象里面的方法getAttribute方法 (1)首 ...
- 使用java生成备份sqlserver数据表的insert语句
针对sqlserver数据表的备份工具很多,有时候条件限制需要我们自己生成insert语句,以便后期直接执行这些插入语句.下面提供了一个简单的思路,针对mysql或oracle有兴趣的以后可以试着修改 ...
- JavaScript学习总结4-规范
昨天学习了JS的严格检查模式,今天做一点补充 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 & ...
- Fastjson JdbcRowSetImpl利用链学习
JdbcRowSetImpl 接着继续学习fastjson的第二条链JdbcRowSetImpl,主要是利用jndi注入达到的攻击,而且没有什么利用限制,而且其原理就是setter的自动调用,具体se ...
- 缓存中间件-Redis(二)
在上一篇中我们简单总结和介绍了Redis的几个方面 1.使用Redis背景 2.Redis通信多路复用的基本原理 3.Redis基本数据结构 4.Redis持久化方式 这一篇我们使用简单的业务场景来介 ...
- CUDA02 - 访存优化和Unified Memory
CUDA02 - 的内存调度与优化 前面一篇(传送门)简单介绍了CUDA的底层架构和一些线程调度方面的问题,但这只是整个CUDA的第一步,下一个问题在于数据的访存:包括数据以何种形式在CPU/GPU之 ...
- 【CSAPP】Shell Lab 实验笔记
shlab这节是要求写个支持任务(job)功能的简易shell,主要考察了linux信号机制的相关内容.难度上如果熟读了<CSAPP>的"异常控制流"一章,应该是可以不 ...
- 【计算机网络】Stanford CS144 Lab Assignments 学习笔记
本文为我的斯坦福计算机网络课的编程实验(Lab Assignments)的学习总结.课程全称:CS 144: Introduction to Computer Networking. 事情发生于我读了 ...