Click here to Skip to main content
15,896,063 members
Articles / General Programming / Threads

Declarative multithreading

Rate me:
Please Sign up or sign in to vote.
4.94/5 (39 votes)
13 Mar 2012CDDL19 min read 59.1K   862   139  
An introduction and proof of concept code for the idea of declarative multi threading in C#.
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;

namespace ThreadBound
{
    /// <summary>
    /// Creates a proxy class that wraps an interface inside a ContextBoundObject derived class.
    /// </summary>
    class InterfaceProxyGenerator
    {
        /// <summary>
        /// Global Cache for all created wrapper types. This prevents creating the same wrapper over and
        /// over again.
        /// </summary>
        private static readonly Dictionary<Type, Type> Wrappers = new Dictionary<Type, Type>();

        /// <summary>
        /// Module-Builder that is used for the creation of the wrappers.
        /// </summary>
        private static readonly ModuleBuilder ModBuilder;

        /// <summary>
        /// Static c'tor that creates the dynamic assembly and the dynamic module used for generating the proxy classes.
        /// </summary>
        static InterfaceProxyGenerator()
        {
            //-- There's only one assembly and one module used for all dynamic wrappers.
            AssemblyBuilder AssBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("ProxyGeneratorAssembly"), AssemblyBuilderAccess.Run);
            ModBuilder = AssBuilder.DefineDynamicModule("ProxyGenerator");
        }

        /// <summary>
        /// Creates a c'tor that takes a reference to a real interface as an input parameter and saves this reference within the supplied realObject-Field.
        /// </summary>
        /// <param name="newType">The type-builder that should be used to create the new type.</param>
        /// <param name="realObjectField">Information of the field that recieves the reference to the real interface.</param>
        /// <param name="interfaceType">Type of the interface that should be used for the c'tors parameter and is also the type of the realObject field.</param>
        static private void GenerateProxyCtor(TypeBuilder newType, FieldInfo realObjectField, Type interfaceType)
        {
            //-- Create the c'tor with one parameter. It takes a reference to the interface that should be called by the wrapper.
            Type[] ctorArgs = { interfaceType };
            ConstructorBuilder cBuilder = newType.DefineConstructor(MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, CallingConventions.Standard, ctorArgs);

            //-- Now emit the method body.
            ILGenerator ilGen = cBuilder.GetILGenerator();

            //-- Push the this reference and call the default c'tor of the base class
            ilGen.Emit(OpCodes.Ldarg_0);
            ilGen.Emit(OpCodes.Call, newType.BaseType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, System.Type.EmptyTypes, null));

            //-- Now push this and the first parameter (the reference to the real interface) onto the stack.
            //   The interface reference is then written to the realObjectReference via the stfld op-code.
            ilGen.Emit(OpCodes.Ldarg_0);
            ilGen.Emit(OpCodes.Ldarg_1);
            ilGen.Emit(OpCodes.Stfld, realObjectField);

            ilGen.Emit(OpCodes.Ret);
        }

        /// <summary>
        /// Creates a proxy method that forwards all calls and all call parameters to the real object.
        /// </summary>
        /// <param name="newType">Type builder instance that is used to create the new method.</param>
        /// <param name="realObjectFieldInfo">FieldInfo for the field that contains the reference to the real interface.</param>
        /// <param name="interfaceType">Type of the interface for which to implement the proxy method. It is used to create the explicit decleration of the method name.</param>
        /// <param name="method">Information about the method to proxy. This defines all properties of the proxy method.</param>
        static private void GenerateProxyMethod(TypeBuilder newType, FieldInfo realObjectFieldInfo, Type interfaceType, MethodInfo method)
        {
            System.Byte paramCount = 0;
            List<Type> paramTypes = new List<Type>();

            //-- Create a list of all parameters of the interface.
            //   Increment a count because we need the number of parameters later.
            foreach (ParameterInfo parameter in method.GetParameters())
            {
                paramTypes.Add(parameter.ParameterType);
                paramCount++;
            }

            //-- Define the method for the interface.
            MethodBuilder mBuilder = newType.DefineMethod(interfaceType.Name + "." + method.Name, MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual | MethodAttributes.Final, method.ReturnType, paramTypes.ToArray());

            //-- Now emit the method body.
            ILGenerator ilGen = mBuilder.GetILGenerator();

            //-- Push this onto the stack and then use ldfld to get the real interface's reference out of the
            //   field referenced by realObjectFieldInfo.
            //   After this operation the this reference has been removed and the real interface's reference is onto the stack.
            ilGen.Emit(OpCodes.Ldarg_0);
            ilGen.Emit(OpCodes.Ldfld, realObjectFieldInfo);

            //-- Now push all parameters onto the stack. For this the ldarg_S op-code is used
            //   because it is the most generic opcode. Eventho it uses a little more memory.
            for (System.Byte i = 1; i <= paramCount; i++)
            {
                ilGen.Emit(OpCodes.Ldarg_S, i);
            }

            //-- Now call the original method. The this reference on the stack is the reference
            //   to the real interface.
            ilGen.Emit(OpCodes.Callvirt, method);
            ilGen.Emit(OpCodes.Ret);

            //-- Now override the interface's method decleration with the real implementation.
            newType.DefineMethodOverride(mBuilder, method);
        }

        /// <summary>
        /// Creates a wrapper of an interface. This wrapper will be derived from ContextBoundObject so it can be 
        /// intercepted as needed. The proxy will just call the original implementation for each of the interface's
        /// methods. This allows intercepting any interface regardless of the dependencies of the real class.
        /// </summary>
        /// <typeparam name="T">Type of the interface.</typeparam>
        /// <param name="realObject">Reference to the real object that implementes the interface that should be proxied.</param>
        /// <returns>An instance of the proxy class just created. This class will be derived from ContextBoundObject and MarshalByRefObject.</returns>
        static public MarshalByRefObject CreateProxy<T>(T realObject) where T : class 
        {
            Type wrapperType;

            if (!typeof(T).IsInterface)
                throw new ArgumentException("T is not an interface.");

            //-- Check if the type is already cached. If it is, don't create a new one.
            if (!Wrappers.ContainsKey(typeof(T)))
            {
                AssemblyName assName = new AssemblyName("ProxyGeneratorAssembly");
                AssemblyBuilder assBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assName, AssemblyBuilderAccess.Run);
                ModuleBuilder modBuilder = assBuilder.DefineDynamicModule("Module" + typeof(T).Name);

                //-- Create the proxy type.
                TypeBuilder newType = modBuilder.DefineType("DynProxy" + typeof(T).Name, TypeAttributes.Public | TypeAttributes.AutoClass | TypeAttributes.BeforeFieldInit, typeof(ContextBoundObject));

                //-- Now add the interface type to the class's definition. This way we can cast the proxy to the real interface.
                newType.AddInterfaceImplementation(typeof(T));

                //-- Create the realObject-field that stores the reference to the real interface.
                FieldBuilder realObjectField = newType.DefineField("realObject", typeof(T), FieldAttributes.Private);

                //-- Create a c'tor that accepts a reference to the real interface.
                GenerateProxyCtor(newType, realObjectField, typeof(T));

                //-- Now create a proxy method for each method of the interface.
                foreach (MethodInfo method in typeof(T).GetMethods())
                {
                    GenerateProxyMethod(newType, realObjectField, typeof(T), method);
                }

                //-- Create the new type and add it to the cache
                wrapperType = newType.CreateType();
                Wrappers.Add(typeof(T), wrapperType);
            }
            else
            {
                //-- If the type is already in the cache, get it.
                wrapperType=Wrappers[typeof(T)];
            }

            return (MarshalByRefObject)Activator.CreateInstance(wrapperType, realObject);
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Common Development and Distribution License (CDDL)


Written By
Systems Engineer
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions