/// <summary> /// Generate a new DynamicMethod with which you can invoke the previous state. /// If the NativeDetour holds a reference to a managed method, a copy of the original method is returned. /// If the NativeDetour holds a reference to a native function, an "undo-call-redo" trampoline with a matching signature is returned. /// </summary> public MethodBase GenerateTrampoline(MethodBase signature = null) { MethodBase remoteTrampoline = OnGenerateTrampoline?.InvokeWhileNull <MethodBase>(this, signature); if (remoteTrampoline != null) { return(remoteTrampoline); } if (_IsFree) { throw new InvalidOperationException("Free() has been called on this detour."); } if (_BackupMethod != null) { // If we're detouring an IL method and have an IL copy, invoke the IL copy. // Note that this ignores the passed signature. return(_BackupMethod); } /* * if (signature == null) * signature = _BackupMethod; */ if (signature == null) { throw new ArgumentNullException("A signature must be given if the NativeDetour doesn't hold a reference to a managed method."); } // Otherwise, undo the detour, call the method and reapply the detour. MethodBase methodCallable = Method; if (methodCallable == null) { methodCallable = DetourHelper.GenerateNativeProxy(Data.Method, signature); } Type returnType = (signature as MethodInfo)?.ReturnType ?? typeof(void); ParameterInfo[] args = signature.GetParameters(); Type[] argTypes = new Type[args.Length]; for (int i = 0; i < args.Length; i++) { argTypes[i] = args[i].ParameterType; } using (DynamicMethodDefinition dmd = new DynamicMethodDefinition( $"Trampoline:Native<{Method?.GetFindableID(simple: true) ?? ((long) Data.Method).ToString("X16")}>?{GetHashCode()}", returnType, argTypes )) { ILProcessor il = dmd.GetILProcessor(); ExceptionHandler eh = new ExceptionHandler(ExceptionHandlerType.Finally); il.Body.ExceptionHandlers.Add(eh); il.EmitDetourCopy(_BackupNative, Data.Method, Data.Type); // Store the return value in a local as we can't preserve the stack across exception block boundaries. VariableDefinition localResult = null; if (returnType != typeof(void)) { il.Body.Variables.Add(localResult = new VariableDefinition(il.Import(returnType))); } // Label blockTry = il.BeginExceptionBlock(); int instriTryStart = il.Body.Instructions.Count; for (int i = 0; i < argTypes.Length; i++) { il.Emit(OpCodes.Ldarg, i); } if (methodCallable is MethodInfo) { il.Emit(OpCodes.Call, (MethodInfo)methodCallable); } else if (methodCallable is ConstructorInfo) { il.Emit(OpCodes.Call, (ConstructorInfo)methodCallable); } else { throw new NotSupportedException($"Method type {methodCallable.GetType().FullName} not supported."); } if (localResult != null) { il.Emit(OpCodes.Stloc, localResult); } il.Emit(OpCodes.Leave, (object)null); Instruction instrLeave = il.Body.Instructions[il.Body.Instructions.Count - 1]; // il.BeginFinallyBlock(); int instriTryEnd = il.Body.Instructions.Count; int instriFinallyStart = il.Body.Instructions.Count; // Reapply the detour even if the method threw an exception. il.EmitDetourApply(Data); // il.EndExceptionBlock(); int instriFinallyEnd = il.Body.Instructions.Count; Instruction instrLeaveTarget = null; if (localResult != null) { il.Emit(OpCodes.Ldloc, localResult); instrLeaveTarget = il.Body.Instructions[il.Body.Instructions.Count - 1]; } il.Emit(OpCodes.Ret); instrLeaveTarget = instrLeaveTarget ?? il.Body.Instructions[il.Body.Instructions.Count - 1]; instrLeave.Operand = instrLeaveTarget; // TODO: Are the exception handler indices correct? eh.TryStart = il.Body.Instructions[instriTryStart]; eh.TryEnd = il.Body.Instructions[instriTryEnd]; eh.HandlerStart = il.Body.Instructions[instriTryEnd]; eh.HandlerEnd = il.Body.Instructions[instriFinallyEnd]; return(dmd.Generate().Pin()); } }