Получить Func <> из MethodInfo с закрытыми (недоступными) типами - PullRequest
0 голосов
/ 04 октября 2018

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

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)
  • Создание делегата с правильной типизацией с помощью отражения (см. https://stackoverflow.com/a/40579063/2692950), только позволяет мне вызывать .DynamicInvoke(context), который все еще медленныйЯ не могу привести этот делегат к Func, чтобы иметь возможность вызывать его напрямую, потому что, опять же, подписи не совпадают.
  • Я не могу написать Func<ThirdPartyClass.InternalPrivateClass, int> - он не скомпилируется, так как 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 aс параметром.Это работает, потому что SafeProvHandle наследуется от SafeHandle.На SafeProvHandle нельзя ссылаться напрямую, потому что она приватная, поэтому, похоже, нет возможности напрямую вызывать эту функцию.

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

Ответы [ 2 ]

0 голосов
/ 04 октября 2018

Вот общее решение для создания 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 (https://stackoverflow.com/a/40579063/2692950) и @ mike.z (https://stackoverflow.com/a/52641599/2692950),) за то, что вы привели меня к этому решению

0 голосов
/ 04 октября 2018

Это немного сложно сделать, но это можно сделать с помощью 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 / April2018Update / 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
DefaultJob: .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 us |
Делегат |1000 |4,329 нас |0.0245 us |0.0230 us |
Отражение |10000 |31,832 сша |0,1599 с нами |0,1335 us |
Делегат |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);
}
...