Inject method call to all field setters marked with attribute using Mono.Cecil

42 Views Asked by At

I have class with the method:

class NetworkObject {

  protected void LocalNetworkVariableChange(object value) {
    // Do some stuff
  }

}

I also have Mono.Cecil setup so that it injects a method call to this method in properties marked with a [NetworkVariable] attribute:

class Foo : NetworkObject {

  public String Data {
  get {
    return ...
  }
  set {
    // existing code here
    ...  

    // Injected call
    this.LocalNetworkVariableChange(value);
  }

}

This works fine using this code:

var propertySetMethod = property.SetMethod;
ILProcessor processor = propertySetMethod.Body.GetILProcessor();
var lastInstruction = propertySetMethod.Body.Instructions.Last();
var valueOperand = (FieldDefinition)propertySetMethod.Body.Instructions
  .First(i => i.OpCode == OpCodes.Stfld)
  .Operand;

var onLocalChangeMethod = assembly.MainModule
  .Types.Single(td => td.FullName == typeof(NetworkObject).FullName)
  .Methods.Single(md => md.Name == "LocalNetworkVariableChange")
  .Resolve();

var newInstructions = new List<Instruction> {
  // Load this on the evaluation step
  Instruction.Create(OpCodes.Ldarg_0),

  // Load to the stack the index of network variable
  Instruction.Create(OpCodes.Ldc_I4, index),

  // Loads the local variable at index 0 onto the evaluation stack.
  Instruction.Create(OpCodes.Ldloc_0),

  // Finds the value of a field in the object whose reference is currently on the evaluation stack.
  Instruction.Create(OpCodes.Ldfld, valueOperand),

  // Call the method
  Instruction.Create(OpCodes.Callvirt, onLocalChangeMethod),

  // Insert Nop before return
  Instruction.Create(OpCodes.Nop)
};

foreach (var newInstruction in newInstructions) {
  processor.InsertBefore(lastInstruction, newInstruction);
}

So whenever the setter is called, my method is called after. Now I need the same for fields, additionally to the properties.

But what can I do? Any idea? Can I convert the field to property and how?

1

There are 1 best solutions below

0
Sebastian Barth On

The solution was quite simple: Every setter to the field is prolonged with a call to the method. I loop over all instructions of all members of all classes of all assemblies and search for OpCodes.StdFld instructions that set the field. Than right after the setter I inject the following calls.

var injections = new List<Instruction> {
  // Load this on the evaluation step
  Instruction.Create(OpCodes.Ldarg_0),

  // Load to the stack the index of network variable
  Instruction.Create(OpCodes.Ldc_I4, index),

  // Load this on the evaluation step to be able to fetch field value next
  Instruction.Create(OpCodes.Ldarg_0),

  // Load the field value again
  Instruction.Create(OpCodes.Ldfld, (FieldReference)instruction.Operand),
};

if (operand.FieldType.IsValueType) {
  // Box the value because the argument is an object
  injections.Add(Instruction.Create(OpCodes.Box, operand.FieldType));
}

// Call the method
injections.Add(Instruction.Create(OpCodes.Call, onLocalChangeMethod));

The boxing here is one part that I forgot in my initial question.