【问题标题】:Get a Func<> from MethodInfo with private (inaccessible) types从 MethodInfo 获取具有私有(不可访问)类型的 Func<>
【发布时间】:2019-03-09 07:29:56
【问题描述】:

考虑以下代码:

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); fails 因为 Func 签名必须完全匹配并且 (object, int) 与 MethodInfo 的签名 (ThirdPartyClass.InternalPrivateClass, int) 不匹配
  • 通过反射构造一个正确类型的委托(参见https://stackoverflow.com/a/40579063/2692950)只能让我调用.DynamicInvoke(context),这仍然很慢。我不能将此委托转换为 Func 以便能够直接调用它,因为签名不匹配。
  • 我不能写 Func&lt;ThirdPartyClass.InternalPrivateClass, int&gt; - 因为 InternalPrivateClass 是私有的,所以它不会编译。

解决了! (https://stackoverflow.com/a/52652398/2692950)

我为什么需要这个示例:

看看这个 MD4 哈希实现:https://stackoverflow.com/a/46821287/2692950 (缩短版:https://stackoverflow.com/a/52640221/2692950

这很好用,除了每个散列操作都通过反射调用方法!

在这个例子中,我们通过反射调用一个不可访问的私有函数System.Security.Cryptography.Utils.HashEnd(SafeProvHandle h),传递一个SafeHandle作为参数。这是因为SafeProvHandle 继承自SafeHandleSafeProvHandle 无法直接引用,因为它是私有的,因此似乎无法直接调用此函数。

(我最感兴趣的是问题顶部的一般情况是否存在解决方案,但如果有人知道通过ALG_ID 直接获取加密服务提供商的更好方法,我我全神贯注:)

【问题讨论】:

  • 间接解决方案:第三方库似乎是.NET。幸运的是,您可以从referencesource.microsoft.com/#mscorlib/system/security/…复制源代码,使其编译(不一定容易),然后根据需要进行修改。
  • 这几乎涉及重新实现一大块 .NET。有更简单的方法来处理我的特定示例。我对一般情况更感兴趣。我意识到我很可能会要求一些不存在的东西,但尝试一下也无妨:)
  • 我认为没有办法。你已经(我认为)意识到没有办法声明一个变量来保存你试图创建的东西。下一步是发出某种包装函数,它可以调用所需的函数,但本身不必传递InternalPrivateClass。你可以存储某种Func 准备调用它。但是发出这个函数面临着同样的障碍。
  • 我添加了一段说明如何在不直接引用私有类型的情况下调用期望私有类型作为参数的私有方法。看起来这个问题的解决方案,如果存在的话,确实会涉及发射 IL。不幸的是,我对这方面的了解还不够。
  • 您需要实例化的私有类是否像您给出的示例一样容易实例化? IE。公共无参数构造函数?您要编译的实际代码是(如果我们忽略私有修饰符):var x = thirdPartyInstance.SomeFunc(new ThirdPartyClass.InternalPrivateClass()); 吗?我问是因为我有一些 IL 发射的经验,我可以尝试一起做一个小的尝试来做到这一点。

标签: c# .net reflection delegates func


【解决方案1】:

这有点棘手,但可以使用 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,操作系统=Windows 10.0.17134.286 (1803/April2018Update/Redstone4)
Intel Core i5-4200U CPU 1.60GHz (Haswell),1 个 CPU,4 个逻辑核心和 2 个物理核心
频率=2240904 Hz,分辨率=446.2485 ns,定时器=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);
}

【讨论】:

  • 非常感谢,这正是我所需要的!使用您的示例,我修改了我的 CreateDelegate 助手,以便能够处理我的 OP 中的一般情况。我给你一个公认的答案,让我知道这是怎么可能的。如果您认为我下面的一般案例解决方案应该被标记为已接受,请告诉我。
【解决方案2】:

这是从具有不可访问类型的 MethodInfo 创建 Func 或 Action 的一般解决方案:

public static Delegate CreateDelegate(this MethodInfo methodInfo, object target, params Type[] custTypes) {
    Func<Type[], Type> getType;
    bool isAction = methodInfo.ReturnType.Equals((typeof(void))), cust = custTypes.Length > 0;
    Type[] types = cust ? custTypes : methodInfo.GetParameters().Select(p => p.ParameterType).ToArray();
    if (isAction) getType = Expression.GetActionType;
    else {
        getType = Expression.GetFuncType;
        if (!cust) types = types.Concat(new[] { methodInfo.ReturnType }).ToArray();
    }
    if (cust) {
        int i, nargs = types.Length - (isAction ? 0 : 1);
        var dm = new DynamicMethod(methodInfo.Name, isAction ? typeof(void) : types.Last(), types.Take(nargs).ToArray(), typeof(object), true);
        var il = dm.GetILGenerator();
        for (i = 0; i < nargs; i++)
            il.Emit(OpCodes.Ldarg_S, i);
        il.Emit(OpCodes.Call, methodInfo);
        il.Emit(OpCodes.Ret);
        if (methodInfo.IsStatic) return dm.CreateDelegate(getType(types));
        return dm.CreateDelegate(getType(types), target);
    }
    if (methodInfo.IsStatic) return Delegate.CreateDelegate(getType(types), methodInfo);
    return Delegate.CreateDelegate(getType(types), target, methodInfo.Name);
}

在OP中,这个函数可以如下调用,得到一个可直接调用的Func:

Func<object, int> f = (Func<object, int>)mi.CreateDelegate(null, typeof(object), typeof(int));
f(context);

感谢 @Sagi (https://stackoverflow.com/a/40579063/2692950) 和 @mike.z (https://stackoverflow.com/a/52641599/2692950) 引导我找到这个解决方案

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-01
    • 2021-07-09
    • 1970-01-01
    • 2023-03-04
    • 2016-11-13
    • 1970-01-01
    相关资源
    最近更新 更多