ILWeaver Best Practices
This article teaches the best practices with ILWeaver. However, some of these apply for ILHooks in general.
When to ILHook
Section titled “When to ILHook”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.
Reliability
Section titled “Reliability”Advice on writing reliable ILHooks.
Unknown Instructions
Section titled “Unknown Instructions”❌ 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: retAnd 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!");}Compatibility With Other ILHooks
Section titled “Compatibility With Other ILHooks”A list of things to keep in mind in order to not break other people’s ILHooks.
Inserting Instructions
Section titled “Inserting Instructions”❌ 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) );}Replacing Instructions
Section titled “Replacing Instructions”❌ 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) );}