Получить Func<> из MethodInfo с закрытыми (недоступными) типами

Рассмотрим следующий код:

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 должна точно совпадать и (object, int) не соответствует подписи MethodInfo (ThirdPartyClass.InternalPrivateClass, int)
  • Создание делегата с правильной типизацией с помощью отражения (см. /questions/20863750/poluchenie-delegata-iz-methodinfo/20863763#20863763) позволяет мне вызывать только .DynamicInvoke(context) который все еще медленный Я не могу привести этот делегат к Func, чтобы иметь возможность вызывать его напрямую, потому что, опять же, подписи не совпадают.
  • Я не могу писать Func<ThirdPartyClass.InternalPrivateClass, int> - это не скомпилируется с InternalPrivateClass это личное.

Решено! ( /questions/781309/poluchit-func-iz-methodinfo-s-zakryityimi-nedostupnyimi-tipami/781313#781313)

Пример того, почему мне нужно это:

Взгляните на эту реализацию хеша MD4: /questions/12820038/c-ntlm-hash-calculator/12820048#12820048 (сокращенная версия: /questions/12820038/c-ntlm-hash-calculator/12820053#12820053)

Это работает очень хорошо, за исключением того, что каждая операция хеширования вызывает метод через отражение!

В этом примере мы вызываем через отражение недоступную приватную функцию System.Security.Cryptography.Utils.HashEnd(SafeProvHandle h), передавая SafeHandle в качестве параметра. Это работает, потому что SafeProvHandle наследуется от SafeHandle, SafeProvHandle на него нельзя ссылаться напрямую, потому что он закрытый, поэтому, похоже, нет способа напрямую вызвать эту функцию.

(В основном меня интересует, существует ли решение для общего случая в верхней части вопроса, но если кто-нибудь знает, как лучше реализовать получение поставщика крипто-услуг напрямую ALG_ID , Я весь во внимании:)

2 ответа

Решение

Это немного сложно сделать, но это можно сделать с помощью DynamicMethod в пространстве имен System.Reflection.Emit. Это позволяет нам генерировать 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/ апрель2018, обновление /Redstone4)
Процессор Intel Core i5-4200U 1,60 ГГц (Haswell), 1 процессор, 4 логических и 2 физических ядра
Частота =2240904 Гц, разрешение =446,2485 нс, таймер = TSC
[Хост]: .NET Framework 4.7.2 (CLR 4.0.30319.42000), 32-разрядная версия LegacyJIT-v4.7.3163.0.
Задание по умолчанию:.NET Framework 4.7.2 (CLR 4.0.30319.42000), 32-разрядная версия LegacyJIT-v4.7.3163.0

Метод | N | Значит | Ошибка | StdDev |
----------- | ------ | ----------: | ----------: | ------- ---: |
Отражение 1000 | 16,239 с нами | 0,1252 нас | 0,1046 с нами |
Делегат | 1000 | 4,329 нас | 0.0245 us | 0.0230 us |
Отражение 10000 | 31,832 сша | 0,1599 с нами | 0,1335 нас |
Делегат | 10000 | 19,703 us | 0,1005 с нами | 0.0940 us |

Обратите внимание, что N - это число байтов, которые хэшируются. Это использует весь код, предоставленный в ссылках OP, для создания реализации 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);
}

Вот общее решение для создания Func<> или Action<> из MethodInfo с недоступными типами:

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 ( /questions/20863750/poluchenie-delegata-iz-methodinfo/20863763#20863763) и @ mike.z ( /questions/781309/poluchit-func-iz-methodinfo-s-zakryityimi-nedostupnyimi-tipami/781314#781314), которые привели меня к этому решению.

Другие вопросы по тегам