// ==++==
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==
namespace System.Runtime.InteropServices.TCEAdapterGen {
    using System.Runtime.InteropServices.ComTypes;
    using ubyte = System.Byte;
    using System;
    using System.Reflection;
    using System.Reflection.Emit;
    using System.Collections;
    using System.Threading;
    using System.Diagnostics.Contracts;
    
    internal class EventProviderWriter
    {
        private const BindingFlags DefaultLookup = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public;

        private readonly Type[] MonitorEnterParamTypes = new Type[] { typeof(Object), Type.GetType("System.Boolean&") };
        
        public EventProviderWriter( ModuleBuilder OutputModule, String strDestTypeName, Type EventItfType, Type SrcItfType, Type SinkHelperType )
        {
            m_OutputModule = OutputModule;  
            m_strDestTypeName = strDestTypeName;
            m_EventItfType = EventItfType;
            m_SrcItfType = SrcItfType;
            m_SinkHelperType = SinkHelperType;
        }
        
        public Type Perform()
        {       
            // Create the event provider class.
            TypeBuilder OutputTypeBuilder = m_OutputModule.DefineType(
                m_strDestTypeName,
                TypeAttributes.Sealed | TypeAttributes.NotPublic, 
                typeof(Object),
                new Type[]{m_EventItfType, typeof(IDisposable)}
                );

            // Create the event source field.
            FieldBuilder fbCPC = OutputTypeBuilder.DefineField( 
                "m_ConnectionPointContainer", 
                typeof(IConnectionPointContainer), 
                FieldAttributes.Private
                );
            
            // Create array of event sink helpers.
            FieldBuilder fbSinkHelper = OutputTypeBuilder.DefineField( 
                "m_aEventSinkHelpers", 
                typeof(ArrayList), 
                FieldAttributes.Private
                );
            
            // Define the connection point field.
            FieldBuilder fbEventCP = OutputTypeBuilder.DefineField( 
                "m_ConnectionPoint", 
                typeof(IConnectionPoint),
                FieldAttributes.Private
                );
            
            // Define the InitXXX method.
            MethodBuilder InitSrcItfMethodBuilder = 
                DefineInitSrcItfMethod( OutputTypeBuilder, m_SrcItfType, fbSinkHelper, fbEventCP, fbCPC );

            // Process all the methods in the event interface.
            MethodInfo[] aMethods = TCEAdapterGenerator.GetNonPropertyMethods(m_SrcItfType);
            for ( int cMethods = 0; cMethods < aMethods.Length; cMethods++ )
            {
                if ( m_SrcItfType == aMethods[cMethods].DeclaringType )
                {
                    // Define the add_XXX method.
                    MethodBuilder AddEventMethodBuilder = DefineAddEventMethod( 
                        OutputTypeBuilder, aMethods[cMethods], m_SinkHelperType, fbSinkHelper, fbEventCP, InitSrcItfMethodBuilder );
                    
                    // Define the remove_XXX method.
                    MethodBuilder RemoveEventMethodBuilder = DefineRemoveEventMethod( 
                        OutputTypeBuilder, aMethods[cMethods], m_SinkHelperType, fbSinkHelper, fbEventCP );
                }
            }
            
            // Define the constructor.
            DefineConstructor( OutputTypeBuilder, fbCPC );
            
            // Define the finalize method.
            MethodBuilder FinalizeMethod = DefineFinalizeMethod( OutputTypeBuilder, m_SinkHelperType, fbSinkHelper, fbEventCP );
            
            // Define the Dispose method.
            DefineDisposeMethod( OutputTypeBuilder, FinalizeMethod);

            return OutputTypeBuilder.CreateType();
        }
        
        private MethodBuilder DefineAddEventMethod( TypeBuilder OutputTypeBuilder, MethodInfo SrcItfMethod, Type SinkHelperClass, FieldBuilder fbSinkHelperArray, FieldBuilder fbEventCP, MethodBuilder mbInitSrcItf )
        {
            Type[] aParamTypes;
            
            // Find the delegate on the event sink helper.
            FieldInfo DelegateField = SinkHelperClass.GetField( "m_" + SrcItfMethod.Name + "Delegate" );
            Contract.Assert(DelegateField != null, "Unable to find the field m_" + SrcItfMethod.Name + "Delegate on the sink helper");
            
            // Find the cookie on the event sink helper.
            FieldInfo CookieField = SinkHelperClass.GetField( "m_dwCookie" );
            Contract.Assert(CookieField != null, "Unable to find the field m_dwCookie on the sink helper");
            
            // Retrieve the sink helper's constructor.
            ConstructorInfo SinkHelperCons = SinkHelperClass.GetConstructor(EventProviderWriter.DefaultLookup | BindingFlags.NonPublic, null, new Type[0], null );    
            Contract.Assert(SinkHelperCons != null, "Unable to find the constructor for the sink helper");
            
            // Retrieve the IConnectionPoint.Advise method.
            MethodInfo CPAdviseMethod = typeof(IConnectionPoint).GetMethod( "Advise" );
            Contract.Assert(CPAdviseMethod != null, "Unable to find the method ConnectionPoint.Advise");
           
            // Retrieve the ArrayList.Add method.
            aParamTypes = new Type[1];
            aParamTypes[0] = typeof(Object);
            MethodInfo ArrayListAddMethod = typeof(ArrayList).GetMethod( "Add", aParamTypes, null );
            Contract.Assert(ArrayListAddMethod != null, "Unable to find the method ArrayList.Add");

            // Retrieve the Monitor.Enter() method.
            MethodInfo MonitorEnterMethod = typeof(Monitor).GetMethod( "Enter", MonitorEnterParamTypes, null );
            Contract.Assert(MonitorEnterMethod != null, "Unable to find the method Monitor.Enter()");
            
            // Retrieve the Monitor.Exit() method.
            aParamTypes[0] = typeof(Object);
            MethodInfo MonitorExitMethod = typeof(Monitor).GetMethod( "Exit", aParamTypes, null );
            Contract.Assert(MonitorExitMethod != null, "Unable to find the method Monitor.Exit()");
            
            // Define the add_XXX method.
            Type[] parameterTypes;
            parameterTypes = new Type[1];
            parameterTypes[0] = DelegateField.FieldType;
            MethodBuilder Meth = OutputTypeBuilder.DefineMethod(
                "add_" + SrcItfMethod.Name,
                MethodAttributes.Public | MethodAttributes.Virtual,
                null,
                parameterTypes );
            
            ILGenerator il = Meth.GetILGenerator();
            
            // Define a label for the m_IFooEventsCP comparision.
            Label EventCPNonNullLabel = il.DefineLabel();
            
            // Declare the local variables.
            LocalBuilder ltSinkHelper = il.DeclareLocal( SinkHelperClass );
            LocalBuilder ltCookie = il.DeclareLocal( typeof(Int32) );
            LocalBuilder ltLockTaken = il.DeclareLocal( typeof(bool) );

            // Generate the following code:
            //   try {
            il.BeginExceptionBlock();

            // Generate the following code:
            //   Monitor.Enter(this, ref lockTaken);
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Ldloca_S, ltLockTaken);
            il.Emit(OpCodes.Call, MonitorEnterMethod);

            // Generate the following code:
            //   if ( m_IFooEventsCP != null ) goto EventCPNonNullLabel;
            il.Emit( OpCodes.Ldarg, (short)0 );
            il.Emit( OpCodes.Ldfld, fbEventCP );
            il.Emit( OpCodes.Brtrue, EventCPNonNullLabel );
            
            // Generate the following code:
            //   InitIFooEvents();
            il.Emit( OpCodes.Ldarg, (short)0 );
            il.Emit( OpCodes.Call, mbInitSrcItf );
            
            // Mark this as label to jump to if the CP is not null.
            il.MarkLabel( EventCPNonNullLabel );
            
            // Generate the following code:
            //   IFooEvents_SinkHelper SinkHelper = new IFooEvents_SinkHelper;  
            il.Emit( OpCodes.Newobj, SinkHelperCons );
            il.Emit( OpCodes.Stloc, ltSinkHelper );
            
            // Generate the following code:
            //   dwCookie = 0;
            il.Emit( OpCodes.Ldc_I4_0 );
            il.Emit( OpCodes.Stloc, ltCookie );
            
            // Generate the following code:
            //   m_IFooEventsCP.Advise( SinkHelper, dwCookie );
            il.Emit( OpCodes.Ldarg, (short)0 );
            il.Emit( OpCodes.Ldfld, fbEventCP );
            il.Emit( OpCodes.Ldloc, ltSinkHelper );
            il.Emit( OpCodes.Castclass, typeof(Object) );
            il.Emit( OpCodes.Ldloca, ltCookie );
            il.Emit( OpCodes.Callvirt, CPAdviseMethod );
            
            // Generate the following code:
            //   SinkHelper.m_dwCookie = dwCookie;
            il.Emit( OpCodes.Ldloc, ltSinkHelper );
            il.Emit( OpCodes.Ldloc, ltCookie );
            il.Emit( OpCodes.Stfld, CookieField );
            
            // Generate the following code:
            //   SinkHelper.m_FooDelegate = d;
            il.Emit( OpCodes.Ldloc, ltSinkHelper );
            il.Emit( OpCodes.Ldarg, (short)1 );
            il.Emit( OpCodes.Stfld, DelegateField );
            
            // Generate the following code:
            //   m_aIFooEventsHelpers.Add( SinkHelper );
            il.Emit( OpCodes.Ldarg, (short)0 );
            il.Emit( OpCodes.Ldfld, fbSinkHelperArray );
            il.Emit( OpCodes.Ldloc, ltSinkHelper );
            il.Emit( OpCodes.Castclass, typeof(Object) );
            il.Emit( OpCodes.Callvirt, ArrayListAddMethod );
            il.Emit( OpCodes.Pop );
            
            // Generate the following code:
            //   } finally {
            il.BeginFinallyBlock();

            // Generate the following code:
            //   if (lockTaken)
            //      Monitor.Exit(this);
            Label skipExit = il.DefineLabel();
            il.Emit( OpCodes.Ldloc, ltLockTaken );
            il.Emit( OpCodes.Brfalse_S, skipExit );
            il.Emit( OpCodes.Ldarg, (short)0 );
            il.Emit( OpCodes.Call, MonitorExitMethod );
            il.MarkLabel(skipExit);

            // Generate the following code:
            //   }
            il.EndExceptionBlock();            

            // Generate the return opcode.
            il.Emit( OpCodes.Ret );
            
            return Meth;
        }
        
        private MethodBuilder DefineRemoveEventMethod( TypeBuilder OutputTypeBuilder, MethodInfo SrcItfMethod, Type SinkHelperClass, FieldBuilder fbSinkHelperArray, FieldBuilder fbEventCP )
        {
            Type[] aParamTypes;
            
            // Find the delegate on the event sink helper.
            FieldInfo DelegateField = SinkHelperClass.GetField( "m_" + SrcItfMethod.Name + "Delegate" );
            Contract.Assert(DelegateField != null, "Unable to find the field m_" + SrcItfMethod.Name + "Delegate on the sink helper");
            
            // Find the cookie on the event sink helper.
            FieldInfo CookieField = SinkHelperClass.GetField( "m_dwCookie" );
            Contract.Assert(CookieField != null, "Unable to find the field m_dwCookie on the sink helper");
            
            // Retrieve the ArrayList.RemoveAt method.
            aParamTypes = new Type[1];
            aParamTypes[0] = typeof(Int32);
            MethodInfo ArrayListRemoveMethod = typeof(ArrayList).GetMethod( "RemoveAt", aParamTypes, null );
            Contract.Assert(ArrayListRemoveMethod != null, "Unable to find the method ArrayList.RemoveAt()");
            
            // Retrieve the ArrayList.Item property get method.
            PropertyInfo ArrayListItemProperty = typeof(ArrayList).GetProperty( "Item" );
            Contract.Assert(ArrayListItemProperty != null, "Unable to find the property ArrayList.Item");
            MethodInfo ArrayListItemGetMethod = ArrayListItemProperty.GetGetMethod();
            Contract.Assert(ArrayListItemGetMethod != null, "Unable to find the get method for property ArrayList.Item");
            
            // Retrieve the ArrayList.Count property get method.
            PropertyInfo ArrayListSizeProperty = typeof(ArrayList).GetProperty( "Count" );
            Contract.Assert(ArrayListSizeProperty != null, "Unable to find the property ArrayList.Count");
            MethodInfo ArrayListSizeGetMethod = ArrayListSizeProperty.GetGetMethod();
            Contract.Assert(ArrayListSizeGetMethod != null, "Unable to find the get method for property ArrayList.Count");
            
            // Retrieve the Delegate.Equals() method.
            aParamTypes[0] = typeof(Delegate);
            MethodInfo DelegateEqualsMethod = typeof(Delegate).GetMethod( "Equals", aParamTypes, null );
            Contract.Assert(DelegateEqualsMethod != null, "Unable to find the method Delegate.Equlals()");

            // Retrieve the Monitor.Enter() method.
            MethodInfo MonitorEnterMethod = typeof(Monitor).GetMethod("Enter", MonitorEnterParamTypes, null);
            Contract.Assert(MonitorEnterMethod != null, "Unable to find the method Monitor.Enter()");
            
            // Retrieve the Monitor.Exit() method.
            aParamTypes[0] = typeof(Object);
            MethodInfo MonitorExitMethod = typeof(Monitor).GetMethod( "Exit", aParamTypes, null );
            Contract.Assert(MonitorExitMethod != null, "Unable to find the method Monitor.Exit()");
            
            // Retrieve the ConnectionPoint.Unadvise() method.
            MethodInfo CPUnadviseMethod = typeof(IConnectionPoint).GetMethod( "Unadvise" );
            Contract.Assert(CPUnadviseMethod != null, "Unable to find the method ConnectionPoint.Unadvise()");
            
            // Retrieve the Marshal.ReleaseComObject() method.
            MethodInfo ReleaseComObjectMethod = typeof(Marshal).GetMethod( "ReleaseComObject" );
            Contract.Assert(ReleaseComObjectMethod != null, "Unable to find the method Marshal.ReleaseComObject()");
            
            // Define the remove_XXX method.
            Type[] parameterTypes;
            parameterTypes = new Type[1];
            parameterTypes[0] = DelegateField.FieldType;
            MethodBuilder Meth = OutputTypeBuilder.DefineMethod(
                "remove_" + SrcItfMethod.Name,
                MethodAttributes.Public | MethodAttributes.Virtual,
                null,
                parameterTypes );
            
            ILGenerator il = Meth.GetILGenerator();
            
            // Declare the local variables.
            LocalBuilder ltNumSinkHelpers = il.DeclareLocal( typeof(Int32) );
            LocalBuilder ltSinkHelperCounter = il.DeclareLocal( typeof(Int32) );
            LocalBuilder ltCurrSinkHelper = il.DeclareLocal( SinkHelperClass );
            LocalBuilder ltLockTaken = il.DeclareLocal(typeof(bool));
            
            // Generate the labels for the for loop.
            Label ForBeginLabel = il.DefineLabel();
            Label ForEndLabel = il.DefineLabel();
            Label FalseIfLabel = il.DefineLabel();
            Label MonitorExitLabel = il.DefineLabel();
            
            // Generate the following code:
            //   try {
            il.BeginExceptionBlock();

            // Generate the following code:
            //   Monitor.Enter(this, ref lockTaken);
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Ldloca_S, ltLockTaken);
            il.Emit(OpCodes.Call, MonitorEnterMethod);

            // Generate the following code:
            //   if ( m_aIFooEventsHelpers == null ) goto ForEndLabel;        
            il.Emit( OpCodes.Ldarg, (short)0 );
            il.Emit( OpCodes.Ldfld, fbSinkHelperArray );
            il.Emit( OpCodes.Brfalse, ForEndLabel );

            // Generate the following code:
            //   int NumEventHelpers = m_aIFooEventsHelpers.Count;
            //   int cEventHelpers = 0;
            il.Emit( OpCodes.Ldarg, (short)0 );
            il.Emit( OpCodes.Ldfld, fbSinkHelperArray );
            il.Emit( OpCodes.Callvirt, ArrayListSizeGetMethod );
            il.Emit( OpCodes.Stloc, ltNumSinkHelpers );
            il.Emit( OpCodes.Ldc_I4, 0 );
            il.Emit( OpCodes.Stloc, ltSinkHelperCounter );
            
            // Generate the following code:
            //   if ( 0 >= NumEventHelpers ) goto ForEndLabel;        
            il.Emit( OpCodes.Ldc_I4, 0 );
            il.Emit( OpCodes.Ldloc, ltNumSinkHelpers );
            il.Emit( OpCodes.Bge, ForEndLabel );
            
            // Mark this as the beginning of the for loop's body.
            il.MarkLabel( ForBeginLabel );
            
            // Generate the following code:
            //   CurrentHelper = (IFooEvents_SinkHelper)m_aIFooEventsHelpers.Get( cEventHelpers );
            il.Emit( OpCodes.Ldarg, (short)0 );
            il.Emit( OpCodes.Ldfld, fbSinkHelperArray );
            il.Emit( OpCodes.Ldloc, ltSinkHelperCounter );
            il.Emit( OpCodes.Callvirt, ArrayListItemGetMethod );
            il.Emit( OpCodes.Castclass, SinkHelperClass );
            il.Emit( OpCodes.Stloc, ltCurrSinkHelper );
            
            // Generate the following code:
            //   if ( CurrentHelper.m_FooDelegate )
            il.Emit( OpCodes.Ldloc, ltCurrSinkHelper );
            il.Emit( OpCodes.Ldfld, DelegateField );
            il.Emit( OpCodes.Ldnull );
            il.Emit( OpCodes.Beq, FalseIfLabel );
            
            // Generate the following code:
            //   if ( CurrentHelper.m_FooDelegate.Equals( d ) )
            il.Emit( OpCodes.Ldloc, ltCurrSinkHelper );
            il.Emit( OpCodes.Ldfld, DelegateField );
            il.Emit( OpCodes.Ldarg, (short)1 );
            il.Emit( OpCodes.Castclass, typeof(Object) );
            il.Emit( OpCodes.Callvirt, DelegateEqualsMethod );
            il.Emit( OpCodes.Ldc_I4, 0xff );
            il.Emit( OpCodes.And );
            il.Emit( OpCodes.Ldc_I4, 0 );
            il.Emit( OpCodes.Beq, FalseIfLabel );
            
            // Generate the following code:
            //   m_aIFooEventsHelpers.RemoveAt( cEventHelpers );
            il.Emit( OpCodes.Ldarg, (short)0 );
            il.Emit( OpCodes.Ldfld, fbSinkHelperArray );
            il.Emit( OpCodes.Ldloc, ltSinkHelperCounter );
            il.Emit( OpCodes.Callvirt, ArrayListRemoveMethod );
            
            // Generate the following code:
            //   m_IFooEventsCP.Unadvise( CurrentHelper.m_dwCookie );
            il.Emit( OpCodes.Ldarg, (short)0 );
            il.Emit( OpCodes.Ldfld, fbEventCP );
            il.Emit( OpCodes.Ldloc, ltCurrSinkHelper );
            il.Emit( OpCodes.Ldfld, CookieField );
            il.Emit( OpCodes.Callvirt, CPUnadviseMethod );
            
            // Generate the following code:
            //   if ( NumEventHelpers > 1) break;
            il.Emit( OpCodes.Ldloc, ltNumSinkHelpers );
            il.Emit( OpCodes.Ldc_I4, 1 );
            il.Emit( OpCodes.Bgt, ForEndLabel );
                       
            // Generate the following code:
            //   Marshal.ReleaseComObject(m_IFooEventsCP);
            il.Emit( OpCodes.Ldarg, (short)0 );
            il.Emit( OpCodes.Ldfld, fbEventCP );
            il.Emit( OpCodes.Call, ReleaseComObjectMethod );            
            il.Emit( OpCodes.Pop );

            // Generate the following code:
            //   m_IFooEventsCP = null;
            il.Emit( OpCodes.Ldarg, (short)0 );
            il.Emit( OpCodes.Ldnull );
            il.Emit( OpCodes.Stfld, fbEventCP );
            
            // Generate the following code:
            //   m_aIFooEventsHelpers = null;      
            il.Emit( OpCodes.Ldarg, (short)0 );
            il.Emit( OpCodes.Ldnull );
            il.Emit( OpCodes.Stfld, fbSinkHelperArray );
            
            // Generate the following code:
            //   break;
            il.Emit( OpCodes.Br, ForEndLabel );
            
            // Mark this as the label to jump to when the if statement is false.
            il.MarkLabel( FalseIfLabel );       
            
            // Generate the following code:
            //   cEventHelpers++;
            il.Emit( OpCodes.Ldloc, ltSinkHelperCounter );
            il.Emit( OpCodes.Ldc_I4, 1 );
            il.Emit( OpCodes.Add );
            il.Emit( OpCodes.Stloc, ltSinkHelperCounter );
            
            // Generate the following code:
            //   if ( cEventHelpers < NumEventHelpers ) goto ForBeginLabel;
            il.Emit( OpCodes.Ldloc, ltSinkHelperCounter );
            il.Emit( OpCodes.Ldloc, ltNumSinkHelpers );
            il.Emit( OpCodes.Blt, ForBeginLabel );
            
            // Mark this as the end of the for loop's body.
            il.MarkLabel( ForEndLabel );

            // Generate the following code:
            //   } finally {
            il.BeginFinallyBlock();

            // Generate the following code:
            //   if (lockTaken)
            //      Monitor.Exit(this);
            Label skipExit = il.DefineLabel();
            il.Emit(OpCodes.Ldloc, ltLockTaken);
            il.Emit(OpCodes.Brfalse_S, skipExit);
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Call, MonitorExitMethod);
            il.MarkLabel(skipExit);

            // Generate the following code:
            //   }
            il.EndExceptionBlock();            

            // Generate the return opcode.
            il.Emit( OpCodes.Ret );
            
            return Meth;
        }

        private MethodBuilder DefineInitSrcItfMethod( TypeBuilder OutputTypeBuilder, Type SourceInterface, FieldBuilder fbSinkHelperArray, FieldBuilder fbEventCP, FieldBuilder fbCPC )
        {
            // Retrieve the constructor info for the array list's default constructor.
            ConstructorInfo DefaultArrayListCons = typeof(ArrayList).GetConstructor(EventProviderWriter.DefaultLookup, null, new Type[0], null );
            Contract.Assert(DefaultArrayListCons != null, "Unable to find the constructor for class ArrayList");    
            
            // Temp byte array for Guid
            ubyte[] rgByteGuid = new ubyte[16];
            
            // Retrieve the constructor info for the Guid constructor.
            Type[] aParamTypes = new Type[1];
            aParamTypes[0] = typeof(Byte[]);
            ConstructorInfo ByteArrayGUIDCons = typeof(Guid).GetConstructor(EventProviderWriter.DefaultLookup, null, aParamTypes, null );
            Contract.Assert(ByteArrayGUIDCons != null, "Unable to find the constructor for GUID that accepts a string as argument");    
            
            // Retrieve the IConnectionPointContainer.FindConnectionPoint() method.
            MethodInfo CPCFindCPMethod = typeof(IConnectionPointContainer).GetMethod( "FindConnectionPoint" );
            Contract.Assert(CPCFindCPMethod != null, "Unable to find the method ConnectionPointContainer.FindConnectionPoint()");    
            
            // Define the Init method itself.
            MethodBuilder Meth = OutputTypeBuilder.DefineMethod(
                "Init", 
                MethodAttributes.Private, 
                null, 
                null );
            
            ILGenerator il = Meth.GetILGenerator();
            
            // Declare the local variables.
            LocalBuilder ltCP = il.DeclareLocal( typeof(IConnectionPoint) );
            LocalBuilder ltEvGuid = il.DeclareLocal( typeof(Guid) );
            LocalBuilder ltByteArrayGuid = il.DeclareLocal( typeof(Byte[]) );
            
            // Generate the following code:
            //   IConnectionPoint CP = NULL;
            il.Emit( OpCodes.Ldnull );
            il.Emit( OpCodes.Stloc, ltCP );
            
            // Get unsigned byte array for the GUID of the event interface.
            rgByteGuid = SourceInterface.GUID.ToByteArray();
            
            // Generate the following code:
            //  ubyte rgByteArray[] = new ubyte [16];
            il.Emit( OpCodes.Ldc_I4, 0x10 );
            il.Emit( OpCodes.Newarr, typeof(Byte) ); 
            il.Emit( OpCodes.Stloc, ltByteArrayGuid );
            
            // Generate the following code:
            //  rgByteArray[i] = rgByteGuid[i];
            for (int i = 0; i < 16; i++ )
            {
                il.Emit( OpCodes.Ldloc, ltByteArrayGuid );
                il.Emit( OpCodes.Ldc_I4, i );
                il.Emit( OpCodes.Ldc_I4, (int) (rgByteGuid[i]) );
                il.Emit( OpCodes.Stelem_I1);
            }
            
            // Generate the following code:
            //   EventItfGuid = Guid( ubyte b[] );          
            il.Emit( OpCodes.Ldloca, ltEvGuid );
            il.Emit( OpCodes.Ldloc, ltByteArrayGuid );
            il.Emit( OpCodes.Call, ByteArrayGUIDCons );
            
            // Generate the following code:
            //   m_ConnectionPointContainer.FindConnectionPoint( EventItfGuid, CP );
            il.Emit( OpCodes.Ldarg, (short)0 );
            il.Emit( OpCodes.Ldfld, fbCPC );
            il.Emit( OpCodes.Ldloca, ltEvGuid );
            il.Emit( OpCodes.Ldloca, ltCP );
            il.Emit( OpCodes.Callvirt, CPCFindCPMethod );
            
            // Generate the following code:
            //   m_ConnectionPoint = (IConnectionPoint)CP;
            il.Emit( OpCodes.Ldarg, (short)0 );
            il.Emit( OpCodes.Ldloc, ltCP );
            il.Emit( OpCodes.Castclass, typeof(IConnectionPoint) );
            il.Emit( OpCodes.Stfld, fbEventCP );
            
            // Generate the following code:
            //   m_aEventSinkHelpers = new ArrayList;      
            il.Emit( OpCodes.Ldarg, (short)0 );
            il.Emit( OpCodes.Newobj, DefaultArrayListCons );
            il.Emit( OpCodes.Stfld, fbSinkHelperArray );   
            
            // Generate the return opcode.
            il.Emit( OpCodes.Ret );
            
            return Meth;
        }
        
        private void DefineConstructor( TypeBuilder OutputTypeBuilder, FieldBuilder fbCPC )
        {
            // Retrieve the constructor info for the base class's constructor.
            ConstructorInfo DefaultBaseClsCons = typeof(Object).GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, new Type[0], null );
            Contract.Assert(DefaultBaseClsCons != null, "Unable to find the object's public default constructor");
            
            // Define the default constructor.
            MethodAttributes ctorAttributes = MethodAttributes.SpecialName | (DefaultBaseClsCons.Attributes & MethodAttributes.MemberAccessMask);
            MethodBuilder Cons = OutputTypeBuilder.DefineMethod( 
                ".ctor", 
                ctorAttributes, 
                null, 
                new Type[]{typeof(Object)} );
            
            ILGenerator il = Cons.GetILGenerator();
            
            // Generate the call to the base class constructor.
            il.Emit( OpCodes.Ldarg, (short)0 );
            il.Emit( OpCodes.Call, DefaultBaseClsCons );
            
            // Generate the following code:
            //   m_ConnectionPointContainer = (IConnectionPointContainer)EventSource;
            il.Emit( OpCodes.Ldarg, (short)0 );
            il.Emit( OpCodes.Ldarg, (short)1 );
            il.Emit( OpCodes.Castclass, typeof(IConnectionPointContainer) );
            il.Emit( OpCodes.Stfld, fbCPC );

            // Generate the return opcode.
            il.Emit( OpCodes.Ret );
        }
               
        private MethodBuilder DefineFinalizeMethod( TypeBuilder OutputTypeBuilder, Type SinkHelperClass, FieldBuilder fbSinkHelper, FieldBuilder fbEventCP )
        {
            // Find the cookie on the event sink helper.
            FieldInfo CookieField = SinkHelperClass.GetField( "m_dwCookie" );
            Contract.Assert(CookieField != null, "Unable to find the field m_dwCookie on the sink helper");    

            // Retrieve the ArrayList.Item property get method.
            PropertyInfo ArrayListItemProperty = typeof(ArrayList).GetProperty( "Item" );
            Contract.Assert(ArrayListItemProperty != null, "Unable to find the property ArrayList.Item");    
            MethodInfo ArrayListItemGetMethod = ArrayListItemProperty.GetGetMethod();
            Contract.Assert(ArrayListItemGetMethod != null, "Unable to find the get method for property ArrayList.Item");    
            
            // Retrieve the ArrayList.Count property get method.
            PropertyInfo ArrayListSizeProperty = typeof(ArrayList).GetProperty( "Count" );
            Contract.Assert(ArrayListSizeProperty != null, "Unable to find the property ArrayList.Count");    
            MethodInfo ArrayListSizeGetMethod = ArrayListSizeProperty.GetGetMethod();
            Contract.Assert(ArrayListSizeGetMethod != null, "Unable to find the get method for property ArrayList.Count");    
            
            // Retrieve the ConnectionPoint.Unadvise() method.
            MethodInfo CPUnadviseMethod = typeof(IConnectionPoint).GetMethod( "Unadvise" );
            Contract.Assert(CPUnadviseMethod != null, "Unable to find the method ConnectionPoint.Unadvise()");    

            // Retrieve the Marshal.ReleaseComObject() method.
            MethodInfo ReleaseComObjectMethod = typeof(Marshal).GetMethod( "ReleaseComObject" );
            Contract.Assert(ReleaseComObjectMethod != null, "Unable to find the method Marshal.ReleaseComObject()");

            // Retrieve the Monitor.Enter() method.
            MethodInfo MonitorEnterMethod = typeof(Monitor).GetMethod("Enter", MonitorEnterParamTypes, null);
            Contract.Assert(MonitorEnterMethod != null, "Unable to find the method Monitor.Enter()");
            
            // Retrieve the Monitor.Exit() method.
            Type[] aParamTypes = new Type[1];
            aParamTypes[0] = typeof(Object);
            MethodInfo MonitorExitMethod = typeof(Monitor).GetMethod( "Exit", aParamTypes, null );
            Contract.Assert(MonitorExitMethod != null, "Unable to find the method Monitor.Exit()");
                        
            // Define the Finalize method itself.
            MethodBuilder Meth = OutputTypeBuilder.DefineMethod( "Finalize", MethodAttributes.Public | MethodAttributes.Virtual, null, null );
            
            ILGenerator il = Meth.GetILGenerator();
            
            // Declare the local variables.
            LocalBuilder ltNumSinkHelpers = il.DeclareLocal( typeof(Int32) );
            LocalBuilder ltSinkHelperCounter = il.DeclareLocal( typeof(Int32) );
            LocalBuilder ltCurrSinkHelper = il.DeclareLocal( SinkHelperClass );
            LocalBuilder ltLockTaken = il.DeclareLocal(typeof(bool));
                        
            // Generate the following code:
            //   try {
            il.BeginExceptionBlock();

            // Generate the following code:
            //   Monitor.Enter(this, ref lockTaken);
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Ldloca_S, ltLockTaken);
            il.Emit(OpCodes.Call, MonitorEnterMethod);

            // Generate the labels.
            Label ForBeginLabel = il.DefineLabel();
            Label ReleaseComObjectLabel = il.DefineLabel();
            Label AfterReleaseComObjectLabel = il.DefineLabel();

            // Generate the following code:
            //   if ( m_IFooEventsCP == null ) goto AfterReleaseComObjectLabel;        
            il.Emit( OpCodes.Ldarg, (short)0 );
            il.Emit( OpCodes.Ldfld, fbEventCP );
            il.Emit( OpCodes.Brfalse, AfterReleaseComObjectLabel );

            // Generate the following code:
            //   int NumEventHelpers = m_aIFooEventsHelpers.Count;
            //   int cEventHelpers = 0;
            il.Emit( OpCodes.Ldarg, (short)0 );
            il.Emit( OpCodes.Ldfld, fbSinkHelper );
            il.Emit( OpCodes.Callvirt, ArrayListSizeGetMethod );
            il.Emit( OpCodes.Stloc, ltNumSinkHelpers );
            il.Emit( OpCodes.Ldc_I4, 0 );
            il.Emit( OpCodes.Stloc, ltSinkHelperCounter );
            
            // Generate the following code:
            //   if ( 0 >= NumEventHelpers ) goto ReleaseComObjectLabel;        
            il.Emit( OpCodes.Ldc_I4, 0 );
            il.Emit( OpCodes.Ldloc, ltNumSinkHelpers );
            il.Emit( OpCodes.Bge, ReleaseComObjectLabel );
            
            // Mark this as the beginning of the for loop's body.
            il.MarkLabel( ForBeginLabel );
            
            // Generate the following code:
            //   CurrentHelper = (IFooEvents_SinkHelper)m_aIFooEventsHelpers.Get( cEventHelpers );
            il.Emit( OpCodes.Ldarg, (short)0 );
            il.Emit( OpCodes.Ldfld, fbSinkHelper );
            il.Emit( OpCodes.Ldloc, ltSinkHelperCounter );
            il.Emit( OpCodes.Callvirt, ArrayListItemGetMethod );
            il.Emit( OpCodes.Castclass, SinkHelperClass );
            il.Emit( OpCodes.Stloc, ltCurrSinkHelper );
            
            // Generate the following code:
            //   m_IFooEventsCP.Unadvise( CurrentHelper.m_dwCookie );
            il.Emit( OpCodes.Ldarg, (short)0 );
            il.Emit( OpCodes.Ldfld, fbEventCP );
            il.Emit( OpCodes.Ldloc, ltCurrSinkHelper );
            il.Emit( OpCodes.Ldfld, CookieField );
            il.Emit( OpCodes.Callvirt, CPUnadviseMethod );
            
            // Generate the following code:
            //   cEventHelpers++;
            il.Emit( OpCodes.Ldloc, ltSinkHelperCounter );
            il.Emit( OpCodes.Ldc_I4, 1 );
            il.Emit( OpCodes.Add );
            il.Emit( OpCodes.Stloc, ltSinkHelperCounter );
            
            // Generate the following code:
            //   if ( cEventHelpers < NumEventHelpers ) goto ForBeginLabel;
            il.Emit( OpCodes.Ldloc, ltSinkHelperCounter );
            il.Emit( OpCodes.Ldloc, ltNumSinkHelpers );
            il.Emit( OpCodes.Blt, ForBeginLabel );
            
            // Mark this as the end of the for loop's body.
            il.MarkLabel( ReleaseComObjectLabel );           

            // Generate the following code:
            //   Marshal.ReleaseComObject(m_IFooEventsCP);
            il.Emit( OpCodes.Ldarg, (short)0 );
            il.Emit( OpCodes.Ldfld, fbEventCP );
            il.Emit( OpCodes.Call, ReleaseComObjectMethod );            
            il.Emit( OpCodes.Pop );

            // Mark this as the end of the for loop's body.
            il.MarkLabel( AfterReleaseComObjectLabel );           
            
            // Generate the following code:
            //   } catch {
            il.BeginCatchBlock(typeof(System.Exception));
            il.Emit( OpCodes.Pop );

            // Generate the following code:
            //   } finally {
            il.BeginFinallyBlock();

            // Generate the following code:
            //   if (lockTaken)
            //      Monitor.Exit(this);
            Label skipExit = il.DefineLabel();
            il.Emit(OpCodes.Ldloc, ltLockTaken);
            il.Emit(OpCodes.Brfalse_S, skipExit);
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Call, MonitorExitMethod);
            il.MarkLabel(skipExit);

            // Generate the following code:
            //   }
            il.EndExceptionBlock();            
            
            // Generate the return opcode.
            il.Emit( OpCodes.Ret );
            
            return Meth;
        }   
        
        private void DefineDisposeMethod( TypeBuilder OutputTypeBuilder, MethodBuilder FinalizeMethod )
        {
            // Retrieve the method info for GC.SuppressFinalize().
            MethodInfo SuppressFinalizeMethod = typeof(GC).GetMethod("SuppressFinalize");
            Contract.Assert(SuppressFinalizeMethod != null, "Unable to find the GC.SuppressFinalize");    
            
            // Define the Finalize method itself.
            MethodBuilder Meth = OutputTypeBuilder.DefineMethod( "Dispose", MethodAttributes.Public | MethodAttributes.Virtual, null, null );
            
            ILGenerator il = Meth.GetILGenerator();
            
            // Generate the following code:
            //   Finalize()
            il.Emit( OpCodes.Ldarg, (short)0 );
            il.Emit( OpCodes.Callvirt, FinalizeMethod );
            
            // Generate the following code:
            //   GC.SuppressFinalize()
            il.Emit( OpCodes.Ldarg, (short)0 );
            il.Emit( OpCodes.Call, SuppressFinalizeMethod );    
            
            // Generate the return opcode.
            il.Emit( OpCodes.Ret );   
        }      

        private ModuleBuilder m_OutputModule;
        private String m_strDestTypeName;
        private Type m_EventItfType;
        private Type m_SrcItfType;
        private Type m_SinkHelperType;
    }
}
