Skip to content

Repository files navigation

Shared library for generic reflection tools for CLR implementations. Tested on .NET Framework, .NET, and Mono.

NuGet Package: DanielWillett.ReflectionTools

Reflection Tools

Base module for ReflectionTools.

OpCode Emitters

IOpCodeEmitter is an abstraction based off ILGenerator.

DebuggableEmitter implements this interface fully and logs any emitted instructions to Accessor.Logger (or the specified accessor).

It also has tools for adding 'breakpoints' to the method, which log every instruction to the logger as it executes in real-time.

DynamicMethoddynMethod=newDyanmicMethod(...);IOpCodeEmitteremitter=dynMethod.AsEmitter(debuggable:true,addBreakpoints:false).WithLogSource("...");// also see EmitterExtensions belowemitter.Emit(OpCodes.Ldarg_0);// ...emitter.Emit(OpCodes.Ret);

EmitterExtensions

Extensions that allow a safer and more readable way to emit instructions.

DynamicMethodInfo<Func<int,int,int>>dynMethod=DynamicMethodHelper.Create<Func<int,int,int>>("TryDivide");IOpCodeEmitteremit=dynMethod.GetEmitter();// *Add* Local and Label so you no longer have to mix up Declare and Defineemit.AddLocal<int>(outLocalBuilderlclResult).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();
OpCodeExtension Method
constrained.Invoke
no.unsupported
readonly.LoadArrayElementAddress
tail.Invoke
unaligned.Most load-value functions
volatile.Most load-value functions
try, leaveTry
catch, leaveTry(..).Catch
filter, endfilterTry(..).CatchWhen
filter handler, leaveTry(..).CatchWhen(..).OnPass
finally, endfinallyTry(..).Finally
fault, endfaultTry(..).Fault
end try, leaveTry(..).Handler(..).End()
define labelAddLabel, AddLazyLabel (Lazy<Label>)
declare localAddLocal
mark labelMarkLabel (Label, Lazy<Label>, or Label?)
addAdd
add.ovfAddChecked
add.ovf.unAddUnsignedChecked
andAnd
arglistLoadArgList
brBranch
leaveLeave
beqBranchIfEqual
bgeBranchIfGreaterOrEqual
bge.unBranchIfGreaterOrEqualUnsigned
bgtBranchIfGreater
bgt.unBranchIfGreaterUnsigned
bleBranchIfLessOrEqual
ble.unBranchIfLessOrEqualUnsigned
bltBranchIfLess
blt.unBranchIfLessUnsigned
bne.unBranchIfNotEqual
brfalseBranchIfFalse
brtrueBranchIfTrue
ceqLoadIfEqual
cgtLoadIfGreater
cgt.unLoadIfGreaterUnsigned
clt -> ldc.i4.0 -> ceqLoadIfGreaterOrEqual
clt.un -> ldc.i4.0 -> ceqLoadIfGreaterOrEqualUnsigned
cltLoadIfLess
clt.unLoadIfLessUnsigned
cgt -> ldc.i4.0 -> ceqLoadIfLessOrEqual
cgt.un -> ldc.i4.0 -> ceqLoadIfLessOrEqualUnsigned
ckfiniteCheckFinite
boxBox
call, callvirtInvoke
castclassCastReference
conv.iConvertToNativeInt
conv.ovf.iConvertToNativeIntChecked
conv.ovf.i.unConvertToNativeIntUnsignedChecked
conv.uConvertToNativeUInt
conv.ovf.uConvertToNativeUIntChecked
conv.ovf.u.unConvertToNativeUIntUnsignedChecked
conv.i1ConvertToInt8
conv.ovf.i1ConvertToInt8Checked
conv.ovf.i1.unConvertToInt8UnsignedChecked
conv.u1ConvertToUInt8
conv.ovf.u1ConvertToUInt8Checked
conv.ovf.u1.unConvertToUInt8UnsignedChecked
conv.i2ConvertToInt16
conv.ovf.i2ConvertToInt16Checked
conv.ovf.i2.unConvertToInt16UnsignedChecked
conv.u2ConvertToUInt16
conv.ovf.u2ConvertToUInt16Checked
conv.ovf.u2.unConvertToUInt16UnsignedChecked
conv.i4ConvertToInt32
conv.ovf.i4ConvertToInt32Checked
conv.ovf.i4.unConvertToInt32UnsignedChecked
conv.u4ConvertToUInt32
conv.ovf.u4ConvertToUInt32Checked
conv.ovf.u4.unConvertToUInt32UnsignedChecked
conv.i8ConvertToInt64
conv.ovf.i8ConvertToInt64Checked
conv.ovf.i8.unConvertToInt64UnsignedChecked
conv.u8ConvertToUInt64
conv.ovf.u8ConvertToUInt64Checked
conv.ovf.u8.unConvertToUInt64UnsignedChecked
conv.r4ConvertToSingle
conv.r8ConvertToDouble
conv.r.un -> conv.r4ConvertToSingleUnsigned
conv.r.un -> conv.r8ConvertToDoubleUnsigned
cpblkCopyBytes
cpobjCopyValue
divDivide
div.unDivideUnsigned
dupDuplicate
initblkSetBytes
initobjSetDefaultValue, SetLocalToDefaultValue
isinstLoadIsAsType
jmpJumpTo
ldargLoadArgument
ldargaLoadArgumentAddress
ldc.i4LoadConstant[U]Int[8,16,32]
ldc.i4LoadConstantCharacter
ldc.i4.[0,1]LoadConstantBoolean
ldc.i4.[0,1]FailFilter, PassFilter (in filter only)
ldc.i8LoadConstant[U]Int64
ldc.r4LoadConstantSingle
ldc.r8LoadConstantDouble
newobj decimal(,,,,)LoadConstantDecimal
ldstr, ldnullLoadConstantString
ldelem*LoadArrayElement
ldelema*LoadArrayElementAddress
ldfldLoadInstanceFieldValue, LoadFieldValue
ldfldaLoadInstanceFieldAddress, LoadFieldAddress
ldsfldLoadStaticFieldValue, LoadFieldValue
ldsfldaLoadStaticFieldAddress, LoadFieldAddress
ldftnLoadFunctionPointer
ldvirtftnLoadFunctionPointerVirtual
ldind, ldobjLoadAddressValue
ldlen*LoadArrayLength
ldlocLoadLocalValue
ldlocaLoadLocalAddress
ldnullLoadNullValue
ldtokenLoadToken
ldtokenLoadTypeOf (loads Type object)
locallocStackAllocate, StackAllocate<T>
mkrefanyMakeTypedReference
refanytypeLoadTypedReferenceTypeToken
refanytypeLoadTypedReferenceType (loads Type object)
refanyvalLoadTypedReferenceAddress
refanyvalLoadTypedReferenceValue (loads value)
mulMultiply
mul.ovfMultiplyChecked
negNegate
notBitwiseNot
ldc.i4.0 -> ceqNot
newarr*CreateArray
newobjCreateObject
nopNoOperation
orOr
popPopFromStack
remModulo
rem.unModuloUnsigned
retReturn
rethrowRethrow
shlShiftLeft
shrShiftRight
shr.unShiftRightUnsigned
sizeofLoadSizeOf
stargSetArgument
stelem*SetArrayElement
stind, stobjSetAddressValue
stfldSetInstanceFieldValue, SetFieldValue
stsfldSetStaticFieldValue, SetFieldValue
stlocSetLocalValue
subSubtract
sub.ovfSubtractChecked
sub.ovf.unSubtractUnsignedChecked
switchSwitch
throwThrow
unbox.anyLoadUnboxedValue
unboxLoadUnboxedAddress
xorXor

* Non-SZ arrays (multi-dimensional or non-zero bound arrays) use their type's Get, Set, and Address functions, Length properties, or constructors instead of the native instruction which only works for vectors.

TranspileContext from DanielWillett.ReflectionTools.Harmony also implements IOpCodeEmitter.

Dependency Injection

Use the AddReflectionTools extension for IServiceCollection to add IReflectionToolsLogger, IOpCodeFormatter, and IAccessor as services. Configures logging from the registered ILoggerFactory service.

Formatting

Accessor.Formatter has methods for formatting members into strings efficiently and accurately. Can be swapped out for custom implementations made either from scratch or derived from DefaultOpCodeFormatter.

Formatting methods, fields, properties, types, parameters, and OpCodes.

publicclassC{publicstaticvoidM<T>(scopedinTp,paramsint[]p2)whereT:struct{}}// elsewhereMethodInfomethod=typeof(C).GetMethod("M",BindingFlags.Public|BindingFlags.Static);stringmethodAsString=Accessor.Formatter.Format(method);/* * Value: 'static void C.M<T>(scoped in T p, params int[] p2)' */

Formatting member definitions

MethodDefinitionmethod=newMethodDefinition("M").DeclaredIn<C>(isStatic:false).WithGenericParameterDefinition("T").WithParameter<int>("p").ReturningUsingGeneric("T",elements: builder =>{builder.AddArray(2);});stringmethodAsString=Accessor.Formatter.Format(method);/* * Value: 'T[,] C.M<T>(int p)' */

See also Accessor.ExceptionFormatter, which is used for exceptions.

StopwatchExtensions

Contains an extension method for getting the milliseconds elapsed in a decimal form (not easily doable through normal methods) named GetElapsedMilliseconds.

Stopwatchsw=Stopwatch.StartNew();// do stuffsw.Stop();Console.WriteLine($"Elapsed time: {sw.GetElapsedMilliseconds():F2} ms");

Accessor

Expandable class filled with utilities for reflection.

Generator Methods

Access private members quickly and easily using delegates.

  • GenerateInstanceSetter
  • GenerateInstanceGetter
  • GenerateInstancePropertySetter
  • GenerateInstancePropertyGetter
  • GenerateStaticSetter
  • GenerateStaticGetter
  • GenerateStaticPropertySetter
  • GenerateStaticPropertyGetter
  • GenerateInstanceCaller
  • GenerateStaticCaller

Getting the value of static properties.

publicclassC{privatestaticintP{get;privateset;}}// elsewhereStaticGetter<int>getter=Accessor.GenerateStaticPropertyGetter<C,int>("P",throwOnError:true)!;intvalue=getter();

Setting instance fields, where the value and instance must be boxed.

internalstructC{privateCF;}// elsewhere// creates a delegate that accesses field 'F', working with boxed instances and values.TypeprivateStruct=Type.GetType("C, A");InstanceSetter<object,object?>setter=Accessor.GenerateInstanceSetter<object?>(privateStruct,"F",throwOnError:true)!;objectinstance=Activator.CreateInstance(privateStruct);objectvalue=Activator.CreateInstance(privateStruct);setter(instance,value);

Calling private methods.

publicclassC{privateintM(){/* ... */}}Func<C,int>caller=Accessor.GenerateInstanceCaller<C,Func<C,int>>("M",throwOnError:true,parameters:Type.EmptyTypes)Cinstance=newC();intreturnValue=caller(instance);

Other Utilities in Accessor

  • Generator Methods
  • GetVisibility
    • Returns a simplified enum-style visibility for members.
    • GetHighestVisibility
      • Returns the most visible method among the list of methods, used for accessors usually.
  • AssemblyGivesInternalAccess
    • Check if an assembly has the InternalsVisibleToAttribute for a given assembly name.
  • IsExtern
    • Check if a member is defined as extern.
  • IsDefinedSafe
  • HasAttributeSafe
  • GetAttributeSafe
  • GetAttributesSafe
  • TryGetAttributeSafe
    • Error-safe, optionally generic, methods for looking for attributes on members.
  • HasCompilerAttributeSafe
  • IsCompilerAttributeDefinedSafe
    • Check if a compiler-generated attribute is defined that may not be available in the given runtime, like IsByRefLikeAttribute.
  • IsReadOnly
    • Checks if a field, struct, or method is readonly.
  • IsByRefLikeType
    • Checks if a struct is a ref struct.
  • IsIgnored
    • Checks if a member has the IgnoreAttribute.
  • GetPriority
    • Gets the priority of a member from the PriorityAttribute, defaulting to 0.
  • GetMethod
    • Easily get a method from a method group using implicit delegate casting.
  • GetDefaultDelegate
    • Get the default variant of Action or Func for the given parameter info.
  • ForEachBaseType
    • Executes a callback for each base type, optionally including object.
  • GetTypesSafe
    • Gets a list of all types in the given assembly, properly catching and handling ReflectionTypeLoadException.
  • GetImplementedMethod
    • Given an interface method, gets the implementation in the parent type.
  • GetDelegateSignature
    • Get signature information about a delegate type.
  • GetReturnType
    • Gets the return type of a delegate type.
  • GetReturnParameter
    • Gets the return parameter of a delegate type.
  • GetParameters
    • Gets the parameters of a delegate type.
  • GetInvokeMethod
    • Gets the Invoke(...) method of a delegate type.
  • GetMemberType
    • Gets the generic 'type' of a member, ex. FieldType, ReturnType, PropertyType, etc.
  • GetIsStatic
    • Checks if a member is static, be it a type, method, field, property, etc.
  • ShouldCallvirtRuntime
    • Decide if a method should be callvirt'd instead of call'd at runtime. Doesn't account for future changes.
  • ShouldCallvirt
    • Decide if a method should be callvirt'd instead of call'd.
  • GetCallRuntime
    • Extension method for getting the proper call for a method at runtime. Doesn't account for future changes.
  • GetCall
    • Extension method for getting the proper call for a method.
  • [Try]GetUnderlyingArray
    • Quickly get the underlying array of a list.
  • [Try]GetListVersion
    • Quickly get the underlying version of a list.
  • CouldBeAssignedTo
    • If it's possible that an object of a type could be in a variable of another type.
  • AsEmitter
    • Creates a generic IOpCodeEmitter from an ILGenerator, DynamicMethod, or MethodBuilder.

Variables

IVariable abstraction for fields and properties.

Both IAccessor and Variables contain methods for getting variables. The ones in IAccessor should be used in a DI environment, otherwise the ones in Variables will do.

// looks for an instance variable named "F" in class "C", returning a type-safe variableIInstanceVariable<C,int>?variable=Variables.FindInstance<C,int>("F");// looks for a static variable named "F" in class "C", returning a type-safe variableIStaticVariable<int>?variable=Variables.FindStatic<C,int>("F");// looks for a variable named "F" in class "C"IVariable?variable=Variables.Find<C,int>("F");

Operators

The Operators class has utilities for finding operator methods in types.

  • Find<TDeclaringType>(OperatorType op, bool preferCheckedOperator = false)
    • Finds the best-matching unary or binary operator where all parameters are the declaring type.
  • Find<TLeft, TRight>(OperatorType op, bool preferCheckedOperator = false)
    • Finds the best-matching binary operator for the two types.
  • FindCast<TFrom, TTo>(OperatorType op, bool preferCheckedOperator = false)
    • Finds the best-matching MethodInfo for the conversion operator from one type to another.
  • AllOperators { get; }
    • Gets a list of all unary and binary operators in the CLI (including unsupported ones).
  • GetOperator(OperatorType)
  • <OperatorName> { get; }
    • These two return a data structure (Operator) that has some basic information about the operator type.

Tools for Lib.Harmony 2.3.3+

Requires Lib.Harmony 2.3.3+. NuGet Package: DanielWillett.ReflectionTools.Harmony Relies on DanielWillett.ReflectionTools

Lib.Harmony module for ReflectionTools. In versions before 3.0.0-prerelease1, this was part of the primary module.

HarmonyLog

HarmonyLog helps you keep an auto-clearing file log:

publicstaticvoidMain(string[]args){// reset the log on startup and configure Harmony to use the log.stringlogFilePath=Path.Combine(Environment.CurrentDirectory,"harmony.log");HarmonyLog.Reset(logFilePath);}

It will be cleared on startup (not deleted, allowing any file editors to stay open).

You can also use HarmonyLog.ResetConditional, which is ignored if the compiler flag REFLECTION_TOOLS_ENABLE_HARMONY_LOG is not defined.

PatchUtility

PatchUtility contains many helper methods for transpiling with a List<CodeInstruction> or a TranspilerContext object.

  • ContinueUntil/ContinueWhile
    • Skips instructions until/while a given pattern matches.
  • CopyWithoutSpecial
    • Copies an instruction without labels or blocks.
  • FindLabelDestinationIndex
    • Finds an instruction with the given label.
  • FollowPattern
    • Advances the current index to directly after a matched pattern.
  • GetLocal
    • Gets the LocalBuilder or index of the local variable in an instruction.
  • GetLocalIndex
    • Gets the index of the local variable in an instruction.
  • GetNextBranchTarget
    • Get the label of the next branch instruction.
  • IsBeginBlockType
    • Extension method for ExceptionBlockType, returning whether the type starts an exception block.
  • IsEndBlockType
    • Extension method for ExceptionBlockType, returning whether the type ends an exception block.
  • LabelNext[OrReturn]
    • Get or add a label to the next instruction that matches a pattern.
  • LoadConstantI4
    • Gets a code instruction that loads an int32 constant, using shorter forms when possible.
  • MatchPattern
    • Matches a set of delegates to the current instruction index.
  • MoveBlocksAndLabels
    • Cut and pastes all labels and blocks to the target instruction.
  • [Try]RemovePattern
    • Removes the next match to a given set of patterns.
  • ReturnIfFalse
    • Takes a static function and inserts instructions calling that function and returning or branching if it returns false.
  • Throw
    • Returns a list of instructions that throw an error with an optional message.
  • TransferStartingInstructionNeeds
    • Moves instructions that would need to stay at the start of a logical instruction block from one instruction to another.
  • TransferEndingInstructionNeeds
    • Moves instructions that would need to stay at the end of a logical instruction block from one instruction to another.
  • WithStartBlocksFrom
    • Chainable version of TransferStartingInstructionNeeds
  • WithEndBlocksFrom
    • Chainable version of TransferEndingInstructionNeeds
  • LoadLocalValue
    • Create CodeInstruction to load a local variable.
  • SetLocalValue
    • Create CodeInstruction to set a local variable.
  • LoadLocalAddress
    • Create CodeInstruction to load a local variable's address.
  • LoadArgument
    • Create CodeInstruction to load a parameter value.
  • SetArgument
    • Create CodeInstruction to set a parameter value.
  • LoadArgumentAddress
    • Create CodeInstruction to load a parameter's address.
  • ToLocalReference
    • Extension for CodeInstruction to get the corresponding LocalReference.

TranspilerContext

TranspilerContext can be used with PatchUtility in transpilers to simplify modifying methods and fetching existing members with reflection.

Partially implements IOpCodeEmitter.

The following transpiler replaces Console.WriteLine("Test {0}", "Value") with Accessor.Logger.LogInfo("Test Source", "Test Value").

publicvoidTranspilerTarget(){if(1==int.Parse("2"))return;Console.WriteLine("Test {0}","Test2");}publicstaticIEnumerable<CodeInstruction>WriteInstructions(IEnumerable<CodeInstruction>instructions,MethodBasemethod,ILGeneratorgenerator){TranspileContextctx=newTranspileContext(method,generator,instructions);MethodInfo?logInfo=typeof(IReflectionToolsLogger).GetMethod("LogInfo",BindingFlags.Public|BindingFlags.Instance,null,[typeof(string),typeof(string)],null);if(logInfo==null){returnctx.Fail(newMethodDefinition("LogInfo").DeclaredIn<IReflectionToolsLogger>(false).WithParameter<string>("source").WithParameter<string>("message"));}MethodInfo?getLogger=typeof(Accessor).GetProperty("Logger",BindingFlags.Public|BindingFlags.Static)?.GetMethod;if(getLogger==null){returnctx.Fail(newPropertyDefinition("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);},newPatternMatch[]{
x =>x.LoadsConstant("Test {0}"),
x =>x.LoadsConstant("Value"),null})){ctx.LogDebug("Patched arguments to LogInfo.");}}returnctx;}

Legal

Versions including and before v4.0.0 are licensed under GNU General Public License v3.0 only
Versions including and after v4.1.0 are licensed under GNU Lesser General Public License v3.0 or later.

About

Shared library for various reflection tools for CLR implementations.

Topics

Resources

Stars

5 stars

Watchers

1 watching

Forks

Releases

Used by

Contributors

Languages