如何将方法参数替换为具有Mono.Cecil的变量



我正在为我的应用程序制作一个代码生成实用程序,但我遇到了一个问题——我不知道如何用在其中创建的变量替换方法的参数。

示例:

a( 代码生成前的代码:

public void SomeMethod(Foo foo)
{
DoSomethingWithFoo(foo);
int someInfo = foo.ExamleValue * 12;
// etc
}

b( 代码生成后的预期代码:

// BitwiseReader class is deserializing byte array received from UDP stream into types
public void SomeMethod(BitwiseReader reader)
{
Foo foo = reader.ReadFoo();
DoSomethingWithFoo(foo);
int someInfo = foo.ExamleValue * 12;
// etc
}

我尝试了制作第二个方法,将BitwiseReader转换为Foo,并将其传递给实际的SomeMethod(Foo)方法。但我正在制作一个高性能的应用程序,第二种方法明显地增加了处理时间。

最大的问题是Mono.Cecil处理Parameters&变量非常不同&我不知道如何将param替换为生成的变量。

常见问题解答;微优化是坏的(TM(";伙计们:

我正在制作一个非常高性能的应用程序,每秒处理数万次操作。正如我所说,我使用第二种方法的变通方法以明显的方式降低了性能。

如果你查看原始的IL代码,你会看到这样的东西:

.method public hidebysig 
instance void SomeMethod (
class Foo foo
) cil managed 
{
// Method begins at RVA 0x2360
// Code size 20 (0x14)
.maxstack 2
.locals init (
[0] int32
)
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldarg.1
IL_0003: call instance void Driver::DoSomethingWithFoo(class Foo)
IL_0008: nop
IL_0009: ldarg.1
IL_000a: ldfld int32 Foo::ExamleValue
IL_000f: ldc.i4.s 12
IL_0011: mul
IL_0012: stloc.0
IL_0013: ret
} // end of method Driver::SomeMethod

基本上你需要的是:

  1. 用BitwiseReader 替换参数类型Foo

  2. 查找正在加载第一个参数(上面的IL_0002(的指令,即以前的foo,现在的reader

  3. 在上一步中找到的指令之后添加对ReadFoo()的调用。

在这些步骤之后,您的IL将看起来像:

.method public hidebysig 
instance void SomeMethod (
class BitwiseReader reader
) cil managed 
{
// Method begins at RVA 0x2360
// Code size 25 (0x19)
.maxstack 2
.locals init (
[0] int32
)
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldarg.1
IL_0003: call instance class Foo BitwiseReader::ReadFoo()
IL_0008: call instance void Driver::DoSomethingWithFoo(class Foo)
IL_000d: nop
IL_000e: ldarg.1
IL_000f: ldfld int32 Foo::ExamleValue
IL_0014: ldc.i4.s 12
IL_0016: mul
IL_0017: stloc.0
IL_0018: ret
} // end of method Driver::SomeMethod

***警告***

下面的代码高度依赖于这样一个事实,即SomeMethod()采用单个Foo参数,并且它执行的某些操作期望该引用位于堆栈的顶部(在本例中,调用DoSomethingWithFoo()(

如果更改SomeMethod()实现,很可能还需要调整Cecil代码,以更改其签名/实现。

还要注意,为了简单起见,我在同一个程序集中定义了BitwiseReader;如果它是在不同的程序集中声明的,您可能需要更改找到该方法的代码(另一种选择是手动构造MethodReference实例(

using Mono.Cecil;
using Mono.Cecil.Cil;
using System.Linq;
class Driver
{
public static void Main(string[] args)
{
if (args.Length == 1 && args[0] == "run")
{
ProofThatItWorks();
return;
}
using var assembly = AssemblyDefinition.ReadAssembly(typeof(Foo).Assembly.Location);
var driver = assembly.MainModule.Types.Single(t => t.Name == "Driver");
var someMethod = driver.Methods.Single(m => m.Name == "SomeMethod");
var bitwiseReaderType = assembly.MainModule.Types.Single(t => t.Name == "BitwiseReader");
var paramType = someMethod.Parameters[0].ParameterType;        

// 1.
someMethod.Parameters.RemoveAt(0); // Remove Foo parameter
someMethod.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None,  bitwiseReaderType)); // Add reader parameter
var ilProcessor = someMethod.Body.GetILProcessor();

// 2.
var loadOldFooParam = ilProcessor.Body.Instructions.FirstOrDefault(inst => inst.OpCode == OpCodes.Ldarg_1);

// 3.
var readFooMethod = bitwiseReaderType.Methods.Single(m => m.Name == "ReadFoo");
var callReadFooMethod = ilProcessor.Create(OpCodes.Call, readFooMethod);
ilProcessor.InsertAfter(loadOldFooParam, callReadFooMethod);
// Save the modified assembly alongside a .runtimeconfig.json file to be able to run it through 'dotnet'
var originalAssemblyPath = typeof(Driver).Assembly.Location;
var outputPath = Path.Combine(Path.GetDirectoryName(originalAssemblyPath), "driver_new.dll");
var originalRuntimeDependencies = Path.ChangeExtension(originalAssemblyPath, "runtimeconfig.json");
var newRuntimeDependencies = Path.ChangeExtension(outputPath, "runtimeconfig.json");
File.Copy(originalRuntimeDependencies, newRuntimeDependencies, true);
System.Console.WriteLine($"nWritting modified assembly to {outputPath}");
Console.ForegroundColor = ConsoleColor.Magenta;
System.Console.WriteLine($"execute: 'dotnet {outputPath} run'  to test.");
assembly.Name.Name = "driver_new";
assembly.Write(outputPath);
}
static void ProofThatItWorks()
{
// call through reflection because the method parameter does not mach 
// during compilation...
var p = new Driver();
var m = p.GetType().GetMethod("SomeMethod");
System.Console.WriteLine($"Calling {m}");
m.Invoke(p, new [] { new BitwiseReader() });
}
public void SomeMethod(Foo foo)
{
DoSomethingWithFoo(foo);
int someInfo = foo.ExamleValue * 12;
// etc
}
void DoSomethingWithFoo(Foo foo) {}
}
public class Foo 
{
public int ExamleValue;
}
public class BitwiseReader
{
public Foo ReadFoo() 
{
System.Console.WriteLine("ReadFoo called...");
return new Foo();
}
}

最后,一些你可以使用的好工具可能会在测试Mono时发现有用。Cecil/IL/C#:

  1. https://sharplab.io
  2. https://cecilifier.me(免责声明,我是这篇文章的作者(

最新更新