【发布时间】:2020-05-03 18:57:08
【问题描述】:
免责声明:我这样做是为了学习。这不会在代码中使用。
我试图了解方法表是如何用于泛型的结构,我想在运行时动态附加到方法。我找到了一个非常有用的 stack overflow question 参考来帮助我入门。
我有一个简单的控制器,我用它来验证我的方法是否正在交换:
public class ValuesController : ControllerBase
{
static ValuesController() {
var methodToReplace = typeof(ValuesController).GetMethod(nameof(ValuesController.Seven),
BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
var methodToAppend = typeof(ValuesController).GetMethod(nameof(ValuesController.Eight),
BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
new Initializer(methodToReplace, methodToAppend);
}
[HttpGet("Seven")]
public int Seven(string id)
{
return 7;
}
[HttpGet("Eight")]
public int Eight(string id)
{
return 8;
}
}
我有一个类 Initializer 负责处理附加到方法。
public class Initializer
{
public Initializer(MethodInfo methodToReplace, MethodInfo methodToAppend)
{
var dummyMethod = typeof(Initializer).GetMethod(nameof(Dummy),
BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
var proxyMethod = typeof(Initializer).GetMethod(nameof(Proxy),
BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
var appendedMethod = typeof(Initializer).GetMethod(nameof(Appended),
BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
dummyMethod.OneWayReplace(methodToReplace);
methodToReplace.OneWayReplace(proxyMethod);
appendedMethod.OneWayReplace(methodToAppend);
}
public int Proxy(string id)
{
Dummy(id);
return Appended(id);
}
public int Dummy(string id)
{
return 0;
}
public int Appended(string id)
{
return 0;
}
}
然后我得到了从原始 stackoverflow 问题中获得的扩展:
public static class InjectionExtensions
{
// Note: This method replaces methodToReplace with methodToInject
// Note: methodToInject will still remain pointing to the same location
public static unsafe MethodReplacementState OneWayReplace(this MethodInfo methodToReplace, MethodInfo methodToInject)
{
//#if DEBUG
RuntimeHelpers.PrepareMethod(methodToReplace.MethodHandle);
RuntimeHelpers.PrepareMethod(methodToInject.MethodHandle);
//#endif
MethodReplacementState state;
IntPtr tar = methodToReplace.MethodHandle.Value;
var inj = methodToInject.MethodHandle.Value + 8;
if (!methodToReplace.IsVirtual)
tar += 8;
else
{
var index = (int)(((*(long*)tar) >> 32) & 0xFF);
var classStart = *(IntPtr*)(methodToReplace.DeclaringType.TypeHandle.Value + (IntPtr.Size == 4 ? 40 : 64));
tar = classStart + IntPtr.Size * index;
}
#if DEBUG
tar = *(IntPtr*)tar + 1;
inj = *(IntPtr*)inj + 1;
state.Location = tar;
state.OriginalValue = new IntPtr(*(int*)tar);
*(int*)tar = *(int*)inj + (int)(long)inj - (int)(long)tar;
return state;
#else
state.Location = tar;
state.OriginalValue = *(IntPtr*)tar;
* (IntPtr*)tar = *(IntPtr*)inj;
return state;
#endif
}
}
注意:使用当前设置一切正常。但是,第二次我将 Initializer 类更改为泛型类 Initializer<T> 我得到内存冲突:
System.AccessViolationException: '试图读取或写入受保护的内存。这通常表明其他内存已损坏。'
我的猜测是methodToReplace.DeclaringType.TypeHandle.Value 计算对于泛型不同,或者因为编译器是生成它写入受保护内存的泛型类的那个?
编辑 我发现了更多信息,我需要在使用泛型参数时正确准备方法,例如:
RuntimeHelpers.PrepareMethod(methodToInject.MethodHandle, new[] { typeof(T).TypeHandle });
然而,要让这项工作发挥作用,还有一些难题要解决。
编辑
有一些开源项目,例如harmony 做类似的事情,但看起来他们发出自己的程序集。虽然我已经考虑过这个选项,但我仍然希望了解我的方法表如何与泛型一起使用
如何附加到泛型类中的方法?
【问题讨论】:
-
DeclaringType在这种情况下将是泛型类型,对吧?您是否需要像 SO answer 那样重建方法签名? -
@timur,DeclaringType 是泛型的,但是,该方法没有引用泛型类型。泛型显然对方法交换有一些影响。但是我没有足够的信息来理解,我的猜测是声明类型修改了类开始的地址计算。所以我正在写入受保护的内存,因为我正在覆盖内存屏障。我对重建方法签名的意思有点困惑,我不是通过反射调用方法,我只是交换指针
标签: c# methods dynamic reflection runtime