Skip to content

ReflectionTools v4.0.0

Latest
Compare
Choose a tag to compare
@DanielWillett DanielWillett released this 08 Sep 06:40
ab5cf08

and ReflectionTools.Harmony v4.0.0

  • Added DynamicMethodHelper.Create to create DynamicMethod objects around a delegate type.
  • Added EmitterExtensions with type-safe extensions for emitting opcodes.
  • Added ExceptionBlockBuilder for more readable exception block emitting.
  • Added AddLocal and AddLabel extensions so you don't have to remember which starts with 'Define' and which starts with 'Declare'.
  • Added AddLazyLabel which returns a label that isn't declared until it's first use (Lazy<Label>). It can also be marked using MarkLabel.
DynamicMethodInfo<Func<int, int, int>> dynMethod = DynamicMethodHelper.Create<Func<int, int, int>>("TryDivide");

IOpCodeEmitter emit = dynMethod.GetEmitter();

emit.AddLocal<int>(out LocalBuilder lclResult)
    .Try(emit =>
    {
        emit.LoadArgument(0)
            .LoadArgument(1)
            .Divide()
            .SetLocalValue(lclResult);
    })
    .Catch<DivideByZeroException>(emit =>
    {
        emit.PopFromStack() // pop exception object
            .SetLocalToDefaultValue<int>(lclResult);
    })
    .End()
    .LoadLocalValue(lclResult)
    .Return();
  • Added Operators class with utilities for working with user-defined operators and conversions.
// math and logic operatorse
//   BigInteger + BigInteger operator
MethodInfo? addOperator = Operators.Find<BigInteger>(OperatorType.Addition, preferCheckedOperator: true);
//   BigInteger == long operator
MethodInfo? equalsInt64Operator = Operators.Find<BigInteger, long>(OperatorType.Equality);

// Operator data structure
//   gets basic info about op_LessThan '<', see below.
Operator subtractInfo = Operators.GetOperator(OperatorTypes.LessThan);
//   returns "op_UnaryNegation"
string negateOperatorMethodName = Operators.UnaryNegation.MethodName;
//   returns true
bool hasOneArgument = Operators.Decrement.IsUnary;
//   returns true
bool canBeChecked = Operators.GetOperator(OperatorTypes.Addition).CanDefineCheckedVariant;

// conversion operators
//   conversion of Span<byte> to ReadOnlySpan<byte>
MethodInfo? spanToReadOnlySpanCast = Operators.FindCast<Span<byte>, ReadOnlySpan<byte>>();
//   conversion of string to ReadOnlySpan<char>
MethodInfo? stringToReadOnlySpanCast = Operators.FindCast<string, ReadOnlySpan<char>>();
//   conversion of Int128 to byte with overflow check (if it exists, otherwise the unchecked one is returned)
MethodInfo? ovfInt128ToByteCast = Operators.FindCast<Int128, byte>(preferCheckedOperator: true);
  • Operator data structure
{ 
    // enum value
    "Type": LessThan,
    // this operator must also be present if LessThan is present
    "RequiredPair": GreaterThan,
    // only has one argument
    "IsUnary": false,
    // only has two arguments
    "IsBinary": true,
    // is the 'checked' keyword valid on this operator
    "CanDefineCheckedVariant": false,
    // name of the underlying method
    "MethodName": "op_LessThan"
}
  • Changes to TranspileContext
    • Use Emit[After/Below/BelowLast](Action<IOpCodeEmitter>) or Replace(int, Action<IOpCodeEmitter>) functions instead now.
      • EmitBelowLast is the same as the old Emit(OpCode) methods, where the current instruction is pushed down and it keeps it's labels and blocks.
      • EmitAfter moves the current instruction's labels to the first emitted instruction and includes it in it's block.
      • EmitBelow is new and inserts instructions after the current instruction, leaving it where it is.
      • Older Emit(OpCode) methods were marked obsolete in favor of these new methods.
    • Skips over prefix instructions, plus has a Prefixes property now which enumerates all the prefixes on the current instruction (usually none).
    • Handles blocks better.
      • Blocks are extended to include prefixes on the starting instruction and the prefixed instruction if the ending instruction is a prefix.
public void TranspilerTarget()
{
    if (1 == int.Parse("2"))
        return;

    Console.WriteLine("Test {0}", "Test2");
}

public static IEnumerable<CodeInstruction> WriteInstructions(IEnumerable<CodeInstruction> instructions, MethodBase method, ILGenerator generator)
{
    TranspileContext ctx = new TranspileContext(method, generator, instructions);

    MethodInfo? logInfo = typeof(IReflectionToolsLogger).GetMethod("LogInfo", BindingFlags.Public | BindingFlags.Instance, null, [ typeof(string), typeof(string) ], null);
    if (logInfo == null)
    {
        return ctx.Fail(new MethodDefinition("LogInfo")
            .DeclaredIn<IReflectionToolsLogger>(false)
            .WithParameter<string>("source")
            .WithParameter<string>("message")
        );
    }

    MethodInfo? getLogger = typeof(Accessor).GetProperty("Logger", BindingFlags.Public | BindingFlags.Static)?.GetMethod;
    if (getLogger == null)
    {
        return ctx.Fail(new PropertyDefinition("Logger")
            .DeclaredIn(typeof(Accessor), true)
            .WithNoSetter()
        );
    }

    while (ctx.MoveNext())
    {
        if (PatchUtility.TryReplacePattern(ctx,
            emit =>
            {
                emit.Invoke(getLogger)
                    .LoadConstantString("Test Source")
                    .LoadConstantString("Test Value")
                    .Invoke(logInfo);
            },
            new PatternMatch[]
            {
                x => x.LoadsConstant("Test {0}"),
                x => x.LoadsConstant("Value"),
                null
            }
        ))
        {
            ctx.LogDebug("Patched arguments to LogInfo.");
        }
    }

    return ctx;
}