2

Consider the following code:

private class ThirdPartyClass {
    private class InternalPrivateClass { }
    private static InternalPrivateClass Init() { 
        return new InternalPrivateClass(); 
    }
    private static int DoSomething(InternalPrivateClass t1) { 
        return 0; 
    }
}

Assume I have no control over ThirdPartyClass and reverse-engineering it in any way is cost-prohibitive. I want to be able to quickly call DoSomething without the performance overheads of reflection. So what I have so far:

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?
  • Calling mi.Invoke(null, new object[]{context}) is of course slow, because it's using reflection.
  • Delegate.CreateDelegate(typeof(Func<object, int>), mi); fails because the Func signature must match exactly and (object, int) doesn't match the MethodInfo's signature (ThirdPartyClass.InternalPrivateClass, int)
  • Constructing a properly typed Delegate via reflection (see https://stackoverflow.com/a/40579063/2692950) only lets me call .DynamicInvoke(context) which is still slow. I cannot cast this delegate to a Func to be able to invoke it directly because, again, the signatures don't match.
  • I cannot write Func<ThirdPartyClass.InternalPrivateClass, int> - it won't compile since InternalPrivateClass is private.

Solved! (https://stackoverflow.com/a/52652398/2692950)

Example of why I need this:

Take a look at this MD4 hash implementation: https://stackoverflow.com/a/46821287/2692950 (Shortened version: https://stackoverflow.com/a/52640221/2692950)

This works very well, except that every single hashing operation is calling a method via reflection!

In this example, we call via reflection an inaccessible private function System.Security.Cryptography.Utils.HashEnd(SafeProvHandle h), passing a SafeHandle as the parameter. This works because SafeProvHandle inherits from SafeHandle. SafeProvHandle cannot be referenced directly because it's private, hence there appears to be no way invoke this function directly.

(I'm mostly interested in whether a solution exists to the general case at the top of the question, but if anyone knows of a better way to implement obtaining a crypto service provider directly by ALG_ID, I'm all ears :)

Duke Nukem
  • 357
  • 2
  • 11
  • An indirect solution: The third party library seems to be .NET. Fortunately, you can copy the source code from https://referencesource.microsoft.com/#mscorlib/system/security/cryptography/utils.cs, make it compile (not necessarily easy), and then modify it as required. – MineR Oct 04 '18 at 03:16
  • That would pretty much involve re-implementing a good chunk of .NET. There are far easier ways to deal with my particular example. I'm more interested about the general case. I realize I'm very likely asking for something that doesn't exist, but it doesn't hurt to try :) – Duke Nukem Oct 04 '18 at 05:46
  • I don't think there is a way. You've already (I think) realised that there's no way to *declare a variable* to hold the thing that you're trying to create. The next step would be to emit some kind of wrapper function that can call the desired function but doesn't itself have to be passed a `InternalPrivateClass`. That you could store some kind of `Func` ready to call it. But emitting that function faces all of the same obstacles. – Damien_The_Unbeliever Oct 04 '18 at 06:15
  • I added a paragraph illustrating how a private method which expects a private type as a parameter gets invoked without directly referencing the private type. Looks like a solution to this, if one exists, would indeed involve emitting IL. I don't know enough about this area, unfortunately. – Duke Nukem Oct 04 '18 at 06:38
  • Is the private class you need to instantiate as easy to instantiate as the example you've given? ie. a public parameterless constructor? Is the actual code you want to compile this (if we ignore private modifiers): `var x = thirdPartyInstance.SomeFunc(new ThirdPartyClass.InternalPrivateClass());` ? I ask because I have some experience with IL emitting, I could try to hack together a small attempt at doing exactly that. – Lasse V. Karlsen Oct 04 '18 at 06:41
  • I'm actually never creating an instance of an `InternalPrivateClass`. Rather, I'm keeping a reference to it using its base class. Here's a simpler version of the example code: https://stackoverflow.com/a/52640221/2692950 . There I'm pretty much looking for a way to .Invoke a delegate directly instead of .DynamicInvoke – Duke Nukem Oct 04 '18 at 06:46

2 Answers2

4

This is a bit tricky to do but can be done with DynamicMethod in the System.Reflection.Emit namespace. It allows us to emit IL at run time that calls these methods without having to refer to valid, visible identifiers in our code. One of the tricks that this class is able to use is to skip various security and visibility checks which we set via parameters in the constructor. From the example, we need to replace the Utils class. Here is a rewrite of it using DynamicMethod to create delegates:

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;
    }
}

Next the question is how much of a speed advantage this gives. It gives you something like a 2-4x increase depending on the size of the data you are hashing. Smaller gets a better speed boost probably because we spent less time doing computations there and more time in between method invocations. Here were the results of a quick benchmark:

BenchmarkDotNet=v0.11.1, OS=Windows 10.0.17134.286 (1803/April2018Update/Redstone4)
Intel Core i5-4200U CPU 1.60GHz (Haswell), 1 CPU, 4 logical and 2 physical cores
Frequency=2240904 Hz, Resolution=446.2485 ns, Timer=TSC
[Host] : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 32bit LegacyJIT-v4.7.3163.0
DefaultJob : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 32bit LegacyJIT-v4.7.3163.0

Method | N | Mean | Error | StdDev |
----------- |------ |----------:|----------:|----------:|
Reflection | 1000 | 16.239 us | 0.1252 us | 0.1046 us |
Delegate | 1000 | 4.329 us | 0.0245 us | 0.0230 us |
Reflection | 10000 | 31.832 us | 0.1599 us | 0.1335 us |
Delegate | 10000 | 19.703 us | 0.1005 us | 0.0940 us |

Note that N is number of bytes being hashed. This is using all the code provided in OPs links to create a MD4 implementation and then calling ComputeHash on that.

Benchmark code:

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);
}
Mike Zboray
  • 39,828
  • 3
  • 90
  • 122
  • Thanks so much, this was exactly what I needed! Using your example, I modified my `CreateDelegate` helper to be able to handle the general case in my OP. I'm giving you the accepted answer for showing me how this was possible. If you think my general case solution below should be marked accepted instead, please let me know. – Duke Nukem Oct 04 '18 at 17:20
1

Here is the general solution to create a Func<> or Action<> from a MethodInfo with inaccessible types:

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);
}

In the OP, this function can be called as follows to get a directly invokeable Func<>:

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

Thanks to @Sagi (https://stackoverflow.com/a/40579063/2692950) and @mike.z (https://stackoverflow.com/a/52641599/2692950), for leading me to this solution

Duke Nukem
  • 357
  • 2
  • 11