免责声明:我这样做是为了学习目的,不会在代码中使用。
我正在尝试理解泛型方法表的结构,我想在运行时动态添加方法。我发现一个非常有用的堆栈溢出问题的参考资料,让我开始了解。
我有一个简单的控制器,我正在使用它作为测试来验证我的方法是否交换:
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