C# how to get RuntimeMethodHandle from DynamicMethod?

3.3k views Asked by At

I want to replace a method with dynamic method in .Net Framework 4, then I found a very useful answer in Dynamically replace the contents of a C# method?, but I can't get MethodHandle from DynamicMethod directly:

we cannot return a MethodHandle because we cannot track it via GC so this method is off limits

In this article CLR Injection: Runtime Method Replacer,

private static IntPtr GetDynamicMethodRuntimeHandle(MethodBase method)
{
    if (method is DynamicMethod)
    {
        FieldInfo fieldInfo = typeof(DynamicMethod).GetField("m_method", 
                              BindingFlags.NonPublic|BindingFlags.Instance);
        return ((RuntimeMethodHandle)fieldInfo.GetValue(method)).Value;
    }
    return method.MethodHandle.Value;
}

which m_method can not be found.

Then I noticed m_methodHandle, but don't know when it will be initialized.

internal unsafe RuntimeMethodHandle GetMethodDescriptor() {
    if (m_methodHandle == null) {
        lock (this) {
            if (m_methodHandle == null) {
                if (m_DynamicILInfo != null)
                    m_DynamicILInfo.GetCallableMethod(m_module, this);
                else {
                    if (m_ilGenerator == null || m_ilGenerator.ILOffset == 0)
                        throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_BadEmptyMethodBody", Name));

                    m_ilGenerator.GetCallableMethod(m_module, this);
                }
            }
        }
    }
    return new RuntimeMethodHandle(m_methodHandle);
}

According to another question Resolving the tokens found in the IL from a dynamic method, DynamicResolver has a ResolveToken method which return methodHandle address. So I use some code in the answer:

var resolver = typeof(DynamicMethod)
    .GetField("m_resolver", BindingFlags.Instance | BindingFlags.NonPublic)
    .GetValue(dynamicMethod);
if (resolver == null) 
    throw new ArgumentException("The dynamic method's IL has not been finalized.");

But... DynamicResolver will only be initialized in DynamicILGenerator.GetCallableMethod method which will be called in DynamicMethod.GetMethodDescriptor method, so the resolver must be null when I getting it.

Here is my dynamic method:

private static MethodInfo build(MethodInfo originMethod)
{
    var parameters = originMethod.GetParameters();
    var parameterTypes = parameters.Length == 0 ? 
        null :
        parameters
            .Select(param => param.ParameterType)
            .ToArray();

    DynamicMethod method = new DynamicMethod(
        originMethod.Name,
        originMethod.ReturnType,
        parameterTypes,
        originMethod.Module);

    ILGenerator il = method.GetILGenerator();
    il.Emit(OpCodes.Ldstr, "Injected");
    var console_writeline = typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) });
    il.Emit(OpCodes.Call, console_writeline);
    il.Emit(OpCodes.Ret);

    return method;
}

I learned JIT a very little, so I don't quite understand it.

Can someone help?

--------------------------------Edited--------------------------

the answer of @Latency works fine:

RuntimeMethodHandle GetMethodRuntimeHandle(MethodBase method)
{
    if (!(method is DynamicMethod))
        return method.MethodHandle;

    RuntimeMethodHandle handle;
    if (Environment.Version.Major == 4)
    {
        var getMethodDescriptorInfo = typeof(DynamicMethod).GetMethod("GetMethodDescriptor", BindingFlags.NonPublic | BindingFlags.Instance);
        handle = (RuntimeMethodHandle)getMethodDescriptorInfo.Invoke(method, null);
    }
    else
    {
        var fieldInfo = typeof(DynamicMethod).GetField("m_method", BindingFlags.NonPublic | BindingFlags.Instance);
        handle = (RuntimeMethodHandle)fieldInfo.GetValue(method);
    }
    return handle;
}

and after so long I can't remember what's next after get RuntimeMethodHandle and reject a dynamic method, but I hope this can help other people.

1

There are 1 answers

1
Latency On BEST ANSWER

lol @ lock(this)

The .NET Framework changed its memory specs after v3.5

You will need to put in the condition to test against the framework version.

Typically, you would want to overload the method by doing something like this:

private static RuntimeMethodHandle GetDynamicMethodRuntimeHandle(DynamicMethod method) => method.MethodHandle;

However, this operation is not supported for dynamic methods.

Getting the base definition appears to work.

GetDynamicMethodRuntimeHandle(DynamicMethod method) => GetDynamicMethodRuntimeHandle(method.GetBaseDefinition());

private static RuntimeMethodHandle GetDynamicMethodRuntimeHandle(MethodBase method) {
  RuntimeMethodHandle handle;    
  if (Environment.Version.Major == 4) {
    var getMethodDescriptorInfo = typeof(DynamicMethod).GetMethod("GetMethodDescriptor", BindingFlags.NonPublic | BindingFlags.Instance);
    handle = (RuntimeMethodHandle) getMethodDescriptorInfo.Invoke(method, null);
  } else {
    var fieldInfo = typeof(DynamicMethod).GetField("m_method", BindingFlags.NonPublic | BindingFlags.Instance);
    handle = (RuntimeMethodHandle) fieldInfo.GetValue(method);
  }    
  return handle;
}

Which can be simplified to this:

private static IntPtr GetDynamicMethodRuntimeHandle(MethodBase method) {
  if (!(method is DynamicMethod))
    return method.MethodHandle.Value;
  var fieldInfo = typeof(DynamicMethod).GetField("m_method", BindingFlags.NonPublic | BindingFlags.Instance);
  return fieldInfo != null ? ((RuntimeMethodHandle) fieldInfo.GetValue(method)).Value : method.MethodHandle.Value;
}