c# - 从 MethodInfo 获取具有私有(不可访问)类型的 Func<>
问题描述
考虑以下代码:
private class ThirdPartyClass {
private class InternalPrivateClass { }
private static InternalPrivateClass Init() {
return new InternalPrivateClass();
}
private static int DoSomething(InternalPrivateClass t1) {
return 0;
}
}
假设我无法控制ThirdPartyClass
并以任何方式对其进行逆向工程是成本高昂的。我希望能够在DoSomething
没有反射性能开销的情况下快速调用。所以我到目前为止:
Type t = typeof(ThirdPartyClass);
object context = t.GetMethod("Init", BindingFlags.NonPublic | BindingFlags.Static).Invoke(null, null);
MethodInfo mi = t.GetMethod("DoSomething", BindingFlags.NonPublic | BindingFlags.Static);
// ...now what?
- 调用
mi.Invoke(null, new object[]{context})
当然很慢,因为它使用了反射。 Delegate.CreateDelegate(typeof(Func<object, int>), mi);
失败,因为 Func 签名必须完全匹配并且 ( ) 与 MethodInfo 的签名 ( )object, int
不匹配ThirdPartyClass.InternalPrivateClass, int
- 通过反射构造一个正确类型的委托(参见https://stackoverflow.com/a/40579063/2692950)只让我调用
.DynamicInvoke(context)
它仍然很慢。我不能将此委托转换为 Func 以便能够直接调用它,因为签名不匹配。 - 我不能写
Func<ThirdPartyClass.InternalPrivateClass, int>
- 它不会编译,因为它InternalPrivateClass
是私有的。
解决了!(https://stackoverflow.com/a/52652398/2692950)
ᅟ</p>
为什么我需要这个的例子:
看看这个 MD4 哈希实现:https ://stackoverflow.com/a/46821287/2692950 (缩短版:https ://stackoverflow.com/a/52640221/2692950 )
这很好用,除了每个散列操作都通过反射调用方法!
在这个例子中,我们通过反射调用一个不可访问的私有函数System.Security.Cryptography.Utils.HashEnd(SafeProvHandle h)
,将 aSafeHandle
作为参数传递。这是有效的,因为SafeProvHandle
继承自SafeHandle
. SafeProvHandle
不能直接引用,因为它是私有的,因此似乎无法直接调用此函数。
(我最感兴趣的是问题顶部的一般情况是否存在解决方案,但如果有人知道更好的方法来实现直接通过 获取加密服务提供商ALG_ID
,我会全神贯注 :)
解决方案
这有点棘手,但可以使用 System.Reflection.Emit 命名空间中的 DynamicMethod 来完成。它允许我们在运行时发出调用这些方法的 IL,而无需在我们的代码中引用有效的、可见的标识符。此类能够使用的技巧之一是跳过我们通过构造函数中的参数设置的各种安全性和可见性检查。从示例中,我们需要替换Utils
类。这是使用 DynamicMethod 创建委托的重写:
internal static class DelegateUtils
{
private static readonly Type UtilsType = Type.GetType("System.Security.Cryptography.Utils");
private static readonly Func<int, SafeHandle> CreateHashDel;
private static readonly Action<SafeHandle, byte[], int, int> HashDataDel;
private static readonly Func<SafeHandle, byte[]> EndHashDel;
static DelegateUtils()
{
CreateHashDel = CreateCreateHashDelegate();
HashDataDel = CreateHashDataDelegate();
EndHashDel = CreateEndHashDelegate();
}
internal static SafeHandle CreateHash(int algid)
{
return CreateHashDel(algid);
}
internal static void HashData(SafeHandle h, byte[] data, int ibStart, int cbSize)
{
HashDataDel(h, data, ibStart, cbSize);
}
internal static byte[] EndHash(SafeHandle h)
{
return EndHashDel(h);
}
private static Func<int, SafeHandle> CreateCreateHashDelegate()
{
var prop = UtilsType.GetProperty("StaticProvHandle", BindingFlags.NonPublic | BindingFlags.Static);
var createHashMethod = UtilsType.GetMethods(BindingFlags.NonPublic | BindingFlags.Static)
.FirstOrDefault(mi => mi.Name == "CreateHash" && mi.GetParameters().Length == 2);
var createHashDyn = new DynamicMethod("CreateHashDyn", typeof(SafeHandle), new[] { typeof(int) }, typeof(object), true);
var ilGen = createHashDyn.GetILGenerator();
ilGen.Emit(OpCodes.Call, prop.GetGetMethod(true));
ilGen.Emit(OpCodes.Ldarg_0);
ilGen.Emit(OpCodes.Call, createHashMethod);
ilGen.Emit(OpCodes.Ret);
var del = (Func<int, SafeHandle>)createHashDyn.CreateDelegate(typeof(Func<int, SafeHandle>));
return del;
}
private static Action<SafeHandle, byte[], int, int> CreateHashDataDelegate()
{
var hashDataMethod = UtilsType.GetMethods(BindingFlags.NonPublic | BindingFlags.Static)
.FirstOrDefault(mi => mi.Name == "HashData" && mi.GetParameters().Length == 4);
var hashDataDyn = new DynamicMethod("HashDataDyn", typeof(void), new[] { typeof(SafeHandle), typeof(byte[]), typeof(int), typeof(int) }, typeof(object), true);
var ilGen = hashDataDyn.GetILGenerator();
ilGen.Emit(OpCodes.Ldarg_0);
ilGen.Emit(OpCodes.Ldarg_1);
ilGen.Emit(OpCodes.Ldarg_2);
ilGen.Emit(OpCodes.Ldarg_3);
ilGen.Emit(OpCodes.Call, hashDataMethod);
ilGen.Emit(OpCodes.Ret);
var del = (Action<SafeHandle, byte[], int, int>)hashDataDyn.CreateDelegate(typeof(Action<SafeHandle, byte[], int, int>));
return del;
}
private static Func<SafeHandle, byte[]> CreateEndHashDelegate()
{
var endHashMethod = UtilsType.GetMethods(BindingFlags.NonPublic | BindingFlags.Static)
.FirstOrDefault(mi => mi.Name == "EndHash" && mi.GetParameters().Length == 1);
var endHashDyn = new DynamicMethod("EndHashDyn", typeof(byte[]), new[] { typeof(SafeHandle) }, typeof(object), true);
var ilGen = endHashDyn.GetILGenerator();
ilGen.Emit(OpCodes.Ldarg_0);
ilGen.Emit(OpCodes.Call, endHashMethod);
ilGen.Emit(OpCodes.Ret);
var del = (Func<SafeHandle, byte[]>)endHashDyn.CreateDelegate(typeof(Func<SafeHandle, byte[]>));
return del;
}
}
接下来的问题是这能带来多大的速度优势。根据您要散列的数据的大小,它会给您带来 2-4 倍的增长。越小越能提高速度,这可能是因为我们在那里进行计算的时间更少,而在方法调用之间花费的时间更多。以下是快速基准测试的结果:
BenchmarkDotNet=v0.11.1, OS=Windows 10.0.17134.286 (1803/April2018Update/Redstone4)
Intel Core i5-4200U CPU 1.60GHz (Haswell), 1 CPU, 4 logic and 2 physical cores
频率=2240904 Hz, Resolution=446.2485 ns, Timer=TSC
[主机]:.NET Framework 4.7.2 (CLR 4.0.30319.42000),32 位 LegacyJIT-v4.7.3163.0
DefaultJob:.NET Framework 4.7.2 (CLR 4.0.30319.42000),32 位 LegacyJIT-v4.7.3163。 0方法 | N | 平均值 | 错误 | 标准差 |
----------- |------ |----------:|----------:|------- ---:|
反射 | 1000 | 16.239 我们 | 0.1252 我们 | 0.1046 我们 |
代表 | 1000 | 4.329 我们 | 0.0245 我们 | 0.0230 我们 |
反射 | 10000 | 31.832 我们 | 0.1599 我们 | 0.1335 我们 |
代表 | 10000 | 19.703 我们 | 0.1005 我们 | 0.0940 我们 |
请注意,N 是被散列的字节数。这是使用 OPs 链接中提供的所有代码来创建 MD4 实现,然后在其上调用 ComputeHash。
基准代码:
public class MD4DelegateVsReflection
{
private MD4 md4 = MD4.Create();
private byte[] data;
[Params(1000, 10000)]
public int N;
public void SetupData()
{
data = new byte[N];
new Random(42).NextBytes(data);
}
[GlobalSetup(Target = nameof(Reflection))]
public void ReflectionSetup()
{
MD4.SetReflectionUtils();
SetupData();
}
[GlobalSetup(Target = nameof(Delegate))]
public void DelegateSetup()
{
MD4.SetDelegateUtils();
SetupData();
}
[Benchmark]
public byte[] Reflection() => md4.ComputeHash(data);
[Benchmark]
public byte[] Delegate() => md4.ComputeHash(data);
}
推荐阅读
- javascript - stryker-mutator 有问题
- javascript - 如果在一定时间内未单击按钮,如何隐藏元素
- amazon-web-services - 具有外部依赖问题的无服务器框架部署
- python - 将 CCXT 时间戳数据转换为日期时间
- android - 如何在 Github 上同步 Android Project 3.3 版的所有文件?
- laravel - LARAVEL 5.7:忘记()(和除外())仍然不会删除集合中的项目
- go - 从多个 go 例程中获取响应到一个数组中
- mysql - mysql 查询需要大约一分钟使用 between
- javascript - 通过 Javascript 向浏览器添加和加载 Javascript 而无需重新加载
- python - Django:如何编辑值并存储回数据库