Получить 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), которые привели меня к этому решению.