Advanced c# programming 3: generalized dynamic method invocation


Update: It appears a more complete solution for this is available for almost no money at NubiloSoft. I encourage everyone to download a trial of this and take a look!

Assuming you have read and used my previous post, there are some caveats to using decorators. The most important one is that you’re not able to actually replace methods with other functionality. If you actually think about it, the decorator implementation I provided is an example of method replacement – other applications include serialization, message passing (WCF), transaction logs, logging proxy’s, testing, and so on. One particularly useful thing you can do with this is to implement thread barriers, which I’ll discuss in a later blog.

I call this pattern the ‘dynamic method invocation’ which basically means you implement an interface and convert it to an object[] that you pass along to a base class. The base class implements functionality for actually handling the information. This also holds the other way around, where you have an object[] and pass it to a method that calls the hidden proxy method.

The overall code is the same as in the previous blog with one difference: the IL generator code simply forwards all interface implementations and has a handle function. I’ll just post the proxy generator code and how to use it here; the rest is easy to figure out using the previous blog post.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;

namespace DesignPatterns
{
    internal class DynamicProxyGenerator
								
    {
        public Type CreateProxy(Type serviceInterface, Type baseType)
        {
            string fqn = "DYNA" + Guid.NewGuid().ToString().Replace("-", "");

            // Create assembly
				
            AssemblyName assemblyName = new AssemblyName(fqn);

            AssemblyBuilder assemblyBuilder =
                AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName,
                AssemblyBuilderAccess.Run);

            // Create module
				
            ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(fqn, false);

            // Create type
				
            TypeBuilder typeBuilder = moduleBuilder.DefineType(
                fqn + ".Dynamic",
                TypeAttributes.Public,
                baseType);
            typeBuilder.AddInterfaceImplementation(serviceInterface);

            // Construct the proxy object
				
            FieldBuilder proxyField =
                typeBuilder.DefineField("proxy", serviceInterface, FieldAttributes.Private);

            // Create type constructor
				
            AddConstructor(typeBuilder, serviceInterface, proxyField, baseType);

            // Generate the code
				
            int memberId = 0;
            MethodInfo invokeMethod = baseType.GetMethod("Invoke", BindingFlags.NonPublic | BindingFlags.Instance);
            List<MethodInfo> methods = new List<MethodInfo>();
            foreach (MethodInfo info in serviceInterface.GetMethods())
            {
                methods.Add(info);

                // Add the method contents
				
                AddMember(typeBuilder, info, serviceInterface, proxyField, memberId++, invokeMethod);
            }

            // Create the 'handleimpl' and 'getmethodname' methods
				
            AddHandleImplMethod(typeBuilder, methods, proxyField);
            AddGetMethodName(typeBuilder, methods, proxyField);

            Type t = typeBuilder.CreateType();
            return t;
        }

        private void AddConstructor(TypeBuilder typeBuilder, Type serviceInterface, FieldBuilder proxyField, Type baseType)
        {
            ConstructorBuilder constructorBuilder = typeBuilder.DefineConstructor(
                MethodAttributes.Public | MethodAttributes.HideBySig |
                MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
                CallingConventions.HasThis,
                new Type[] { serviceInterface });

            // Generate the constructor IL. 
				
            ILGenerator gen = constructorBuilder.GetILGenerator();

            // The constructor calls the base constructor with the string[] parameter.
				
            gen.Emit(OpCodes.Ldarg_0);
            gen.Emit(OpCodes.Call,
                     baseType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, System.Type.DefaultBinder, Type.EmptyTypes, null));

            gen.Emit(OpCodes.Ldarg_0);
            gen.Emit(OpCodes.Ldarg_1);
            gen.Emit(OpCodes.Stfld, proxyField);

            gen.Emit(OpCodes.Ret);
        }

        private void AddGetMethodName(TypeBuilder typeBuilder, List<MethodInfo> methods, FieldBuilder proxyField)
        {
            MethodBuilder mb = typeBuilder.DefineMethod(
            "GetMethodName",
            MethodAttributes.Family | MethodAttributes.HideBySig | MethodAttributes.Virtual,
            CallingConventions.HasThis,
            typeof(string),
            new Type[] { typeof(int) });

            ILGenerator gen = mb.GetILGenerator();

            LocalBuilder retval = gen.DeclareLocal(typeof(string));

            // We implement the method handling as a jump table for maximum performance.
				
            Label[] jumpTable = new Label[methods.Count];
            for (int i = 0; i < jumpTable.Length; ++i)
            {
                jumpTable[i] = gen.DefineLabel();
            }
            Label endOfMethod = gen.DefineLabel();

            gen.Emit(OpCodes.Ldarg_1);
            gen.Emit(OpCodes.Switch, jumpTable);
            gen.Emit(OpCodes.Ldnull);
            gen.Emit(OpCodes.Stloc, retval);

            gen.Emit(OpCodes.Br, endOfMethod); // default = end-of-method
						
            for (int i = 0; i < jumpTable.Length; ++i)
            {
                gen.MarkLabel(jumpTable[i]);

                gen.Emit(OpCodes.Ldstr, methods[i].Name);
                gen.Emit(OpCodes.Stloc, retval);
                gen.Emit(OpCodes.Br, endOfMethod);
            }

            gen.MarkLabel(endOfMethod);
            gen.Emit(OpCodes.Ldloc, retval);
            gen.Emit(OpCodes.Ret);
        }

        private void AddHandleImplMethod(TypeBuilder typeBuilder, List<MethodInfo> methods, FieldBuilder proxyField)
        {
            MethodBuilder mb = typeBuilder.DefineMethod(
                "HandleImpl",
                MethodAttributes.Family | MethodAttributes.HideBySig | MethodAttributes.Virtual,
                CallingConventions.HasThis,
                typeof(void),
                new Type[] { typeof(int), typeof(object[]) });

            ILGenerator gen = mb.GetILGenerator();

            // We implement the method handling as a jump table for maximum performance.
				
            Label[] jumpTable = new Label[methods.Count];
            for (int i = 0; i < jumpTable.Length; ++i)
            {
                jumpTable[i] = gen.DefineLabel();
            }
            Label endOfMethod = gen.DefineLabel();

            gen.Emit(OpCodes.Ldarg_1);
            gen.Emit(OpCodes.Switch, jumpTable);
            gen.Emit(OpCodes.Br, endOfMethod); // default = end-of-method
						
            for (int i = 0; i < jumpTable.Length; ++i)
            {
                gen.MarkLabel(jumpTable[i]);

                // convert the arguments passed in argument 2 to a callable set of parameters
				
                gen.Emit(OpCodes.Ldarg_0);
                gen.Emit(OpCodes.Ldfld, proxyField);
                var pars = methods[i].GetParameters();
                for (int j = 0; j < pars.Length; ++j)
                {
                    gen.Emit(OpCodes.Ldarg_2);
                    gen.Emit(OpCodes.Ldc_I4, j + 1);
                    gen.Emit(OpCodes.Ldelem_Ref);
                    if (pars[j].ParameterType.IsValueType)
                    {
                        gen.Emit(OpCodes.Unbox_Any, pars[j].ParameterType);
                    }
                    else
				
                    {
                        gen.Emit(OpCodes.Castclass, pars[j].ParameterType);
                    }
                }
                gen.Emit(OpCodes.Callvirt, methods[i]);
                gen.Emit(OpCodes.Br, endOfMethod);
            }

            gen.MarkLabel(endOfMethod);
            gen.Emit(OpCodes.Ret);
        }

        private void AddMember(TypeBuilder typeBuilder, MethodInfo info, Type serviceInterface,
                               FieldBuilder proxyField, int memberId, MethodInfo invokeMethod)
        {
            // Generate the method
				
            Type[] parameterTypes = info.GetParameters().Select((a) => (a.ParameterType)).ToArray();
            MethodBuilder mb = typeBuilder.DefineMethod(
                info.Name,
                MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot |
                MethodAttributes.Virtual | MethodAttributes.Final, CallingConventions.HasThis,
                info.ReturnType,
                parameterTypes);

            if (info.ReturnType != typeof(void))
            {
                throw new NotSupportedException("Thread barriers don't support non-void return types");
            }

            // Generate the method variables
				
            ILGenerator gen = mb.GetILGenerator();
            var pars = gen.DeclareLocal(typeof(object[]));

            // Generate the code
				
            //    object[] par = new object[] { 1, aap, haar, id }; 
				
            // where 1 is the method id
				

gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Ldc_I4, parameterTypes.Length + 1); gen.Emit(OpCodes.Newarr, typeof(object)); gen.Emit(OpCodes.Stloc, pars); gen.Emit(OpCodes.Ldloc, pars); gen.Emit(OpCodes.Ldc_I4_0); gen.Emit(OpCodes.Ldc_I4, memberId); gen.Emit(OpCodes.Box, typeof(int)); gen.Emit(OpCodes.Stelem_Ref); for (int i = 0; i < parameterTypes.Length; ++i) { gen.Emit(OpCodes.Ldloc, pars); gen.Emit(OpCodes.Ldc_I4, i + 1); gen.Emit(OpCodes.Ldarg, i + 1); if (parameterTypes[i].IsValueType) { gen.Emit(OpCodes.Box, parameterTypes[i]); } gen.Emit(OpCodes.Stelem_Ref); } gen.Emit(OpCodes.Ldloc, pars); gen.Emit(OpCodes.Callvirt, invokeMethod); gen.Emit(OpCodes.Ret); } } }

Basically I generate 2 methods, a constructor and for each method in the interface an additional method:

  • The method from the interface changes the parameters to an object[] and calls the invoke method
  • The GetMethodName retrieves the method name that belongs with an id. It is guaranteed that the id’s are sequencers, so you can also use this to retrieve all method names. This method is useful when attempting to store the data along different versions of the interface (so WCF or protobuf alike scenario’s)
  • The HandleImpl calls the method that belongs to an ID. If you’re not passing along the boundaries of a process and not store the data on disk, you probably don’t need GetMethodName and can simply call the method on ID; otherwise you have to look the ID up first.

In order for this code to work, you need an abstract base class that implements the HandleImpl and GetMethodName as abstract. If you don’t do this, that application will crash, because I don’t check the signature of the method before starting the proxy generation process. As an example:


    public abstract class PassThrough
												
    {
        protected abstract void HandleImpl(int id, object[] data);
        protected abstract string GetMethodName(int id);

        protected void Invoke(object[] data)
        {
            Console.WriteLine("Pass through from {0}", GetMethodName((int)data[0]));
            HandleImpl((int)data[0], data);
        }
    }

[…]

            DynamicProxyConsumer consumer = new DynamicProxyConsumer();
            var proxy = DynamicProxy.CreateProxy<IDynamicProxyTest, PassThrough>(consumer);
            proxy.Send("Hahaa, we can intercept this now!");

… The implementation of the CreateProxy is more or less the same as the CreateDecorator in the previous post.

So there we have it; we can now generate a proxy for any interface and put in our own code, isn’t that cool?

  1. Leave a comment

Leave a comment