Skip to content

ILWeaver Best Practices

This article teaches the best practices with ILWeaver. However, some of these apply for ILHooks in general.

ILHooking should usually be only done if it’s the only sensible option left. For example, if you need to modify the behavior of a method somewhere in the middle, and the behavior you are looking for can’t be replicated with a simple prefix or postfix (that does not include copy pasting logic from the target method), then ILHooking is usually the best option.

Otherwise you probably shouldn’t use an ILHook, as they are much more fragile than other hook types.

Advice on writing reliable ILHooks.

❌ Never assume the locations of instructions.
✅ Always do instruction matching to locate your target instructions.

Other people can apply their ILHooks on the same method you are targeting which means the instructions of the target method may not be exactly as you saw in your C# decompiler.

Additionally, if the target method is updated, assuming the locations of instructions will almost definitely break your ILHook.

Let’s take these target method instructions from Introduction to ILHooking for example:

IL_0000: ldstr "Hello, World!"
IL_0005: ret

And let’s say you want to change ldstr "Hello, World!" to ldstr "ILHooked!":

static void ILHook_UnknownInstructions(ILManipulationInfo info)
{
ILWeaver w = new(info);
// ❌ Do not assume target instructions' locations:
w.ReplaceOperand(w.First, "ILHooked!");
// ✅ Do use instruction matching to find target instructions:
ILWeaverResult result = w.MatchRelaxed(
x => x.MatchLdstr("Hello, World!") && w.SetCurrentTo(x)
);
result.ThrowIfFailure();
w.ReplaceCurrentOperand("ILHooked!");
}

A list of things to keep in mind in order to not break other people’s ILHooks.

❌ Do not insert a duplicate instruction instance in a method body.
✅ Do create a copy of an instruction you insert if it already exists on the method body.

Duplicate instruction instances in a method body may break the whole method compilation for example in cases where they are the target of a branch instruction.

Additionally, Mono.Cecil’s Instruction type has Previous and Next properties which are updated when the instruction is inserted into the instruction list. Inserting a duplicate will make those properties point to the newly inserted duplicate’s neighbors, which is false the instruction instance which existed earlier.

static void ILHook_Insert_Example(ILManipulationInfo info)
{
ILWeaver w = new(info);
ILWeaverResult result = w.MatchRelaxed(
x => x.MatchLdsfld<Foo>(nameof(Foo.Baz)) && w.SetCurrentTo(x),
x => x.MatchCall<Foo>(nameof(Foo.Bar))
);
result.ThrowIfFailure();
// ❌ Do not insert duplicate instruction instance:
w.InsertBeforeCurrent(
w.Current,
w.CreateDelegateCall(MyMethod)
);
// ✅ Do insert a copy of the instruction:
w.InsertBeforeCurrent(
w.Current.Clone(),
w.CreateDelegateCall(MyMethod)
);
}

❌ Do not modify an existing instruction instance’s OpCode or Operand.
✅ Do replace the instruction with a copy you modify.

Not modifying instruction instances already in the method body will not harm ILWeaver’s “Relaxed” instruction matching methods’ ability to match against the real original state of the target method CIL instructions, as the “original” instruction instances are the same as the real current instructions of the method.

static void ILHook_Replace_Example(ILManipulationInfo info)
{
ILWeaver w = new(info);
ILWeaverResult result = w.MatchRelaxed(
x => x.MatchCall<Foo>(nameof(Foo.Bar)) && w.SetCurrentTo(x)
);
result.ThrowIfFailure();
// ❌ Do not modify existing instruction instance:
w.Current.Operand = myFooBarReplacementMethod;
// ✅ Do replace the instruction with a copy that is modified:
w.ReplaceCurrentOperand(myFooBarReplacementMethod);
// ✅ Do replace the instruction:
w.ReplaceCurrent(
w.CreateDelegateCall(MyFooBarReplacement)
);
}