Mono.Cecil: 在方法的开头插入一条日志语句

Mono.Cecil: Insert a log statement in method's beginning

我正在使用 Mono.Cecil 编辑我的目标方法的 IL 代码,以便我可以记录该方法的入口点,而无需编辑实际代码。 我能够将调用指令插入到可以执行日志记录操作的方法中。 但我不知道如何记录我的目标方法的输入参数。 简而言之,我想在目标方法中插入一条指令,方法是更改​​它的 IL 代码以执行日志或打印操作以记录传递给该方法的输入参数值。

我尝试了一个基本程序作为示例。

public class Target
{
    // My target method. 
    public void Run(int arg0, string arg1)
    {
        Console.WriteLine("Run method body");
    }


}

public static class Trace{

// This is my log method, which i want to call in begining of Run() method. 
    public void LogEntry(string methodName, object[] params)
    {
        System.Console.WriteLine("******Entered in "+ methodName+" method.***********")
    // With params :......
    //
    }
}

源程序。

public class Sample 
{
    private readonly string _targetFileName;
    private readonly ModuleDefinition _module;

    public ModuleDefinition TargetModule { get { return _module; } }

    public Sample(string targetFileName)
    {
        _targetFileName = targetFileName;

        // Read the module with default parameters
        _module = ModuleDefinition.ReadModule(_targetFileName);
    }

    public void Run(string type, string method)
    {

        // Retrive the target class. 
        var targetType = _module.Types.Single(t => t.Name == type);

        // Retrieve the target method.
        var runMethod = targetType.Methods.Single(m => m.Name == method);

        // Get a ILProcessor for the Run method
        var processor = runMethod.Body.GetILProcessor();

        // get log entry method ref to create instruction
        var logEntryMethodReference = targetType.Methods.Single(m => m.Name == "LogEntry");

   // Import ..
    //
        var newInstruction = processor.Create(OpCodes.Call, logEntryMethodReference);

        var firstInstruction = runMethod.Body.Instructions[0];

        processor.InsertBefore(firstInstruction, newInstruction);

        // Write the module with default parameters
        _module.Write(_targetFileName);
    }
}

嗯,这很有趣 :) 这是我的工作示例(代码中的注释,如果不清楚,请随时提问):

修改示例(实际写出参数):

public class Target
{
    // My target method. 
    public void Run(int arg0, string arg1)
    {
        Console.WriteLine("Run method body");
    }

}

public static class Trace
{

    // This is my log method, which i want to call in begining of Run() method. 
    public static void LogEntry(string methodName, object[] parameters)
    {
        Console.WriteLine("******Entered in " + methodName + " method.***********");
        Console.WriteLine(parameters[0]);
        Console.WriteLine(parameters[1]);
    }
}

处理IL注入的源程序:

public class Sample
{
    private readonly string _targetFileName;
    private readonly ModuleDefinition _module;

    public ModuleDefinition TargetModule { get { return _module; } }

    public Sample(string targetFileName)
    {
        _targetFileName = targetFileName;

        // Read the module with default parameters
        _module = ModuleDefinition.ReadModule(_targetFileName);
    }

    public void Run(string type, string method)
    {

        // Retrive the target class. 
        var targetType = _module.Types.Single(t => t.Name == type);

        // Retrieve the target method.
        var runMethod = targetType.Methods.Single(m => m.Name == method);

        // Get a ILProcessor for the Run method


        // get log entry method ref to create instruction
        var logEntryMethodReference = _module.Types.Single(t => t.Name == "Trace").Methods.Single(m => m.Name == "LogEntry");


        List<Instruction> newInstructions = new List<Instruction>();


        var arrayDef = new VariableDefinition(new ArrayType(_module.TypeSystem.Object)); // create variable to hold the array to be passed to the LogEntry() method            
        runMethod.Body.Variables.Add(arrayDef);  // add variable to the method          

        var processor = runMethod.Body.GetILProcessor();

        newInstructions.Add(processor.Create(OpCodes.Ldc_I4, runMethod.Parameters.Count));  // load to the stack the number of parameters                      
        newInstructions.Add(processor.Create(OpCodes.Newarr, _module.TypeSystem.Object)); // create a new object[] with the number loaded to the stack           
        newInstructions.Add(processor.Create(OpCodes.Stloc, arrayDef)); // store the array in the local variable

        // loop through the parameters of the method to run
        for (int i = 0; i < runMethod.Parameters.Count; i++)
        {
            newInstructions.Add(processor.Create(OpCodes.Ldloc, arrayDef)); // load the array from the local variable
            newInstructions.Add(processor.Create(OpCodes.Ldc_I4, i)); // load the index
            newInstructions.Add(processor.Create(OpCodes.Ldarg, i+1)); // load the argument of the original method (note that parameter 0 is 'this', that's omitted)

            if (runMethod.Parameters[i].ParameterType.IsValueType)
            {
                newInstructions.Add(processor.Create(OpCodes.Box, runMethod.Parameters[i].ParameterType)); // boxing is needed for value types
            }
            else
            { 
                newInstructions.Add(processor.Create(OpCodes.Castclass, _module.TypeSystem.Object)); // casting for reference types
            }
            newInstructions.Add(processor.Create(OpCodes.Stelem_Ref)); // store in the array
        }

        newInstructions.Add(processor.Create(OpCodes.Ldstr, method)); // load the method name to the stack
        newInstructions.Add(processor.Create(OpCodes.Ldloc, arrayDef)); // load the array to the stack
        newInstructions.Add(processor.Create(OpCodes.Call, logEntryMethodReference)); // call the LogEntry() method


        foreach (var newInstruction in newInstructions.Reverse<Instruction>()) // add the new instructions in referse order
        {
            var firstInstruction = runMethod.Body.Instructions[0];
            processor.InsertBefore(firstInstruction, newInstruction);
        }

        // Write the module with default parameters
        _module.Write(_targetFileName);
    }
}