StateManager.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / cdf / src / NetFx40 / System.Activities / System / Activities / Debugger / StateManager.cs / 1305376 / StateManager.cs

                            //------------------------------------------------------------------------------ 
// Copyright (c) Microsoft Corporation.  All rights reserved.
//-----------------------------------------------------------------------------
namespace System.Activities.Debugger
{ 

    using System; 
    using System.Collections.Generic; 
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis; 
    using System.Diagnostics.SymbolStore;
    using System.Globalization;
    using System.IO;
    using System.Reflection; 
    using System.Reflection.Emit;
    using System.Runtime; 
    using System.Runtime.InteropServices; 
    using System.Security.Cryptography;
 
    // Manager for supporting debugging a state machine.
    // The general usage is to call:
    //  - DefineState() for each state
    //  - Bake() once you've defined all the states you need to enter. 
    //  - EnterState() / LeaveState() for each state.
    // You can Define new states and bake them, such as if the script loads a new file. 
    // Baking is expensive, so it's best to define as many states in each batch. 
    [DebuggerNonUserCode]
    // This class need not serialized. 
    [Fx.Tag.XamlVisible(false)]
    public sealed class StateManager : IDisposable
    {
        static readonly Guid WorkflowLanguageGuid = new Guid("1F1149BB-9732-4EB8-9ED4-FA738768919C"); 

        static readonly LocalsItemDescription[] debugInfoDescriptions = new LocalsItemDescription[]{ 
                new LocalsItemDescription("debugInfo", typeof(DebugInfo)) 
            };
 
        static Type threadWorkerControllerType = typeof(ThreadWorkerController);
        static MethodInfo islandWorkerMethodInfo = threadWorkerControllerType.GetMethod("IslandWorker", BindingFlags.Static | BindingFlags.Public);
        const string Md5Identifier = "406ea660-64cf-4c82-b6f0-42d48172a799";
        internal const string MethodWithPrimingPrefix = "_"; 

        List threads; 
 
        // Don't expose this, because that would expose the setters. Changing the properties
        // after baking types has undefined semantics and would be confusing to the user. 
        Properties properties;

        // List of all state that have been created with DefineState.
        List states; 

        // Index into states array of the last set of states baked. 
        // So Bake() will build islands for each state 
        // { states[x], where indexLastBaked <= x < states.Length; }
        int indexLastBaked; 

        // Mapping from State --> MethodInfo for that state.
        // This gets populated as states get baked
        Dictionary islands; 
        Dictionary islandsWithPriming;
 
        ModuleBuilder dynamicModule; 
        Dictionary sourceDocuments;
 
        bool debugStartedAtRoot;


        // Simple default constructor. 
        internal StateManager()
            : this(new Properties(), true) 
        { 
        }
 

        // Constructor.
        // Properties must be set at creation time.
        internal StateManager(Properties properties, bool debugStartedAtRoot) 
        {
            this.properties = properties; 
            //this.virtualCallStack = new Stack(); 
            this.threads = new List();
            this.states = new List(); 
            this.islands = new Dictionary();
            this.debugStartedAtRoot = debugStartedAtRoot;
            if (!this.debugStartedAtRoot)
            { 
                this.islandsWithPriming = new Dictionary();
            } 
            this.sourceDocuments = new Dictionary(); 
            InitDynamicModule(this.properties.ModuleNamePrefix);
        } 

        internal Properties ManagerProperties
        {
            get { return this.properties; } 
        }
 
        internal bool IsPriming 
        {
            get; 
            set;
        }

        // Whether debugging is started at the root workflow (contrast to attaching in the middle 
        // of a running workflow.
        internal bool DebugStartedAtRoot 
        { 
            get
            { 
                return this.debugStartedAtRoot;
            }
        }
 
        // Declare a new state associated with the given source location.
        // States should have disjoint source locations. 
        // location is Source location associated with this state. 
        // This returns a state object, which can be passed to EnterState.
        internal State DefineState(SourceLocation location) 
        {
            return DefineState(location, string.Empty, null, 0);
        }
 
        internal State DefineState(SourceLocation location, string name)
        { 
            return DefineState(location, name, null, 0); 
        }
 
        internal State DefineState(SourceLocation location, string name, LocalsItemDescription[] earlyLocals, int numberOfEarlyLocals)
        {
            State newState = new State(location, name, earlyLocals, numberOfEarlyLocals);
            this.states.Add(newState); 
            return newState;
        } 
 
        internal State DefineStateWithDebugInfo(SourceLocation location, string name)
        { 
            return DefineState(location, name, debugInfoDescriptions, debugInfoDescriptions.Length);
        }

 
        // Bake all states using the default type namespace.
        // States must be baked before calling EnterState(). 
        internal void Bake() 
        {
            Bake(this.properties.TypeNamePrefix); 
        }


        // Bake all newly defined states. States must be baked before calling EnterState(). 
        // typeName is the type name that the islands are contained in. This
        // may show up on the callstack. If this is not unique, it will be appended with a unique 
        // identifier. 
        internal void Bake(string typeName)
        { 
            //ThreadWorkerController controller = new ThreadWorkerController();
            //controller.Initialize(this);

            // Ensure typename is unique. Append a number if needed. 
            int suffix = 1;
            while (this.dynamicModule.GetType(typeName) != null) 
            { 
                typeName = this.properties.TypeNamePrefix + "_" + suffix.ToString(CultureInfo.InvariantCulture);
                ++suffix; 
            }

            TypeBuilder typeBuilder = this.dynamicModule.DefineType(typeName, TypeAttributes.Public | TypeAttributes.Class);
 
            for (int i = indexLastBaked; i < this.states.Count; i++)
            { 
                MethodBuilder methodBuilder = this.CreateIsland(typeBuilder, this.states[i], false); 
                Fx.Assert(methodBuilder != null, "CreateIsland call should have succeeded");
 
                if (!this.DebugStartedAtRoot)
                {
                    MethodBuilder methodBuilderWithPriming = this.CreateIsland(typeBuilder, this.states[i], true);
                    Fx.Assert(methodBuilderWithPriming != null, "CreateIsland call should have succeeded"); 
                }
 
                // Save information needed to call Type.GetMethod() later. 
                this.states[i].CacheMethodInfo(typeBuilder, methodBuilder.Name);
            } 

            // Actual baking.
            typeBuilder.CreateType();
 
            // Calling Type.GetMethod() is slow (10,000 calls can take ~1 minute).
            // So defer that to later. 
 
            this.indexLastBaked = this.states.Count;
            //controller.Exit(); 
        }

        internal int CreateLogicalThread(string threadName)
        { 
            int threadId = -1;
 
            // Reuse thread if exists 
            // Start from 1 since main thread never disposed earlier.
            for (int i = 1; i < this.threads.Count; ++i) 
            {
                if (this.threads[i] == null)
                {
                    this.threads[i] = new LogicalThread(i, threadName, this); 
                    threadId = i;
                    break; 
                } 
            }
 
            // If can't reuse old thread.
            if (threadId < 0)
            {
                threadId = this.threads.Count; 
                this.threads.Add(new LogicalThread(threadId, threadName, this));
            } 
 
            return threadId;
        } 

        // Push the state onto the virtual callstack, with no locals.
        // State is the state to push onto stack.
        //internal void EnterState(int threadIndex, State state) 
        //{
        //    this.EnterState(threadIndex, state, null); 
        //} 

 
        // Enter a state and push it onto the 'virtual callstack'.
        // If the user set a a breakpoint at the source location associated with
        // this state, this call will hit that breakpoint.
        // Call LeaveState when the interpretter is finished with this state. 
        //
        // State is state to enter. 
        // "locals" is local variables (both early-bound and late-bound) associated with this state. 
        // Early-bound locals match by name with the set passed into DefineState.
        // Late-bound will be displayed read-only to the user in the watch window. 
        //
        // EnterState can be called reentrantly. If code calls Enter(A); Enter(B); Enter(C);
        // Then on the call to Enter(C), the virtual callstack will be A-->B-->C.
        // Each call to Enter() will rebuild the virtual callstack. 
        //
        internal void EnterState(int threadIndex, State state, IDictionary locals) 
        { 
            this.EnterState(threadIndex, new VirtualStackFrame(state, locals));
        } 

        // Enter a state and push it onto the 'virtual callstack'.
        // Stackframe describing state to enter, along with the
        // locals in that state. 
        internal void EnterState(int threadIndex, VirtualStackFrame stackFrame)
        { 
            Fx.Assert(threadIndex < this.threads.Count, "Index out of range for thread"); 
            Fx.Assert(this.threads[threadIndex] != null, "LogicalThread is null");
 
            // Validate that Bake was called. This is more restrictive than is needed since
            // the state we're entering may well already be baked.
            // Regardless, I'd rather be more restrictive and loosen up as needed.
            if (this.states.Count > this.indexLastBaked) 
            {
                Fx.Assert(false, "Can't Enter state (" + stackFrame + ") until it is baked. Missing call to StateManager.Bake()"); 
            } 
            this.threads[threadIndex].EnterState(stackFrame);
        } 

        // Pop the state most recently pushed by EnterState.
        internal void LeaveState(int threadIndex, State state)
        { 
            Fx.Assert(threadIndex < this.threads.Count, "Index out of range for thread");
            Fx.Assert(this.threads[threadIndex] != null, "LogicalThread is null"); 
            this.threads[threadIndex].LeaveState(state); 
        }
 
        // Common helper to invoke an Stack frame.
        // This handles marshaling the args.
        // islandArguments - arbitrary argument passed ot the islands.
        // [DebuggerHidden] 
        internal void InvokeWorker(object islandArguments, VirtualStackFrame stackFrame)
        { 
            State state = stackFrame.State; 
            MethodInfo methodInfo = this.GetIsland(state);
            IDictionary allLocals = stackFrame.Locals; 

            // Package up the raw arguments array.
            const int numberOfBaseArguments = 2;
            int numberOfEarlyLocals = state.NumberOfEarlyLocals; 
            object[] arguments = new object[numberOfBaseArguments + numberOfEarlyLocals]; // +1 for IslandArguments and +1 for IsPriming
            arguments[0] = this.IsPriming; 
            arguments[1] = islandArguments; 
            if (numberOfEarlyLocals > 0)
            { 
                int i = numberOfBaseArguments;
                foreach (LocalsItemDescription localsItemDescription in state.EarlyLocals)
                {
                    string name = localsItemDescription.Name; 
                    object value;
                    if (allLocals.TryGetValue(name, out value)) 
                    { 
                        // We could assert that val.GetType() is assignable to localsItemDescription.Type.
                        // MethodInfo invoke will check this anyways; but we could check 
                        // it and give a better error.
                    }
                    else
                    { 
                        // Local not supplied in the array! Use a default.
                        value = Activator.CreateInstance(localsItemDescription.Type); 
                    } 
                    arguments[i] = value;
                    i++; 
                }
            }
            methodInfo.Invoke(null, arguments);
        } 

 
        internal MethodBuilder CreateMethodBuilder(TypeBuilder typeBuilder, Type typeIslandArguments, State state, bool withPriming) 
        {
            // create the method 
            string methodName = (state.Name != null ? state.Name : ("Line_" + state.Location.StartLine));

            if (withPriming)
            { 
                methodName = MethodWithPrimingPrefix + methodName;
            } 
 
            // Parameters to the islands:
            // 1. Args 
            // 2. IDict of late-bound locals.
            // 3 ... N.  list of early bound locals.
            const int numberOfBaseArguments = 2;
            IEnumerable earlyLocals = state.EarlyLocals; 
            int numberOfEarlyLocals = state.NumberOfEarlyLocals;
 
            Type[] parameterTypes = new Type[numberOfBaseArguments + numberOfEarlyLocals]; 
            parameterTypes[0] = typeof(bool);
            parameterTypes[1] = typeIslandArguments; 

            if (numberOfEarlyLocals > 0)
            {
                int i = numberOfBaseArguments; 
                foreach (LocalsItemDescription localsItemDescription in earlyLocals)
                { 
                    parameterTypes[i] = localsItemDescription.Type; 
                    i++;
                } 
            }

            Type returnType = typeof(void);
            MethodBuilder methodbuilder = typeBuilder.DefineMethod( 
                methodName,
                MethodAttributes.Static | MethodAttributes.Public, 
                returnType, parameterTypes); 

            // Need to define parameter here, otherwise EE cannot get the correct IDebugContainerField 
            // for debugInfo.
            methodbuilder.DefineParameter(1, ParameterAttributes.None, "isPriming");
            methodbuilder.DefineParameter(2, ParameterAttributes.None, "typeIslandArguments");
 
            // Define the parameter names
            // Note that we can hide implementation-specific arguments from VS by not defining parameter 
            // info for them.  Eg., the StepInTarget argument doesn't appear to show up in VS at all. 
            if (numberOfEarlyLocals > 0)
            { 
                int i = numberOfBaseArguments + 1;
                foreach (LocalsItemDescription localsItemDescription in earlyLocals)
                {
                    methodbuilder.DefineParameter(i, ParameterAttributes.None, localsItemDescription.Name); 
                    i++;
                } 
            } 

 
            return methodbuilder;
        }

        [Fx.Tag.InheritThrows(From = "GetILGenerator", FromDeclaringType = typeof(MethodBuilder))] 
        MethodBuilder CreateIsland(TypeBuilder typeBuilder, State state, bool withPrimingTest)
        { 
            MethodBuilder methodbuilder = this.CreateMethodBuilder(typeBuilder, threadWorkerControllerType, state, withPrimingTest); 
            ILGenerator ilGenerator = methodbuilder.GetILGenerator();
            const int lineHidden = 0xFeeFee; // #line hidden directive 

            // Island:
            // void MethodName(Manager m)
            // { 
            //    .line
            //     nop 
            //     call Worker(m) 
            //     ret;
            // } 
            SourceLocation stateLocation = state.Location;
            ISymbolDocumentWriter document = this.GetSourceDocument(stateLocation.FileName);
            Label islandWorkerLabel = ilGenerator.DefineLabel();
 
            // Hide all the opcodes before the real source line.
            // This is needed for Island which is called during priming (Attach to Process): 
            // It should skip the line directive during priming, thus it won't break at user's 
            // breakpoints at the beginning during priming the callstack.
            if (withPrimingTest) 
            {
                ilGenerator.MarkSequencePoint(document, lineHidden, 1, lineHidden, 100);
                ilGenerator.Emit(OpCodes.Ldarg_0);
                ilGenerator.Emit(OpCodes.Brtrue, islandWorkerLabel); 
            }
 
            // Emit sequence point before the IL instructions to map it to a source location. 
            ilGenerator.MarkSequencePoint(document, stateLocation.StartLine, stateLocation.StartColumn, stateLocation.EndLine, stateLocation.EndColumn);
            ilGenerator.Emit(OpCodes.Nop); 

            ilGenerator.MarkLabel(islandWorkerLabel);
            ilGenerator.Emit(OpCodes.Ldarg_1);
            ilGenerator.EmitCall(OpCodes.Call, islandWorkerMethodInfo, null); 
            ilGenerator.Emit(OpCodes.Ret);
 
            return methodbuilder; 
        }
 

        MethodInfo GetIsland(State state)
        {
            MethodInfo island = null; 

            if (this.IsPriming) 
            { 
                Fx.Assert(!this.DebugStartedAtRoot, "Priming should not happen when debuging is done from start");
                if (!islandsWithPriming.TryGetValue(state, out island)) 
                {
                    island = state.GetMethodInfo(true);
                    islandsWithPriming[state] = island;
                } 
            }
            else 
            { 
                if (!islands.TryGetValue(state, out island))
                { 
                    island = state.GetMethodInfo(false);
                    islands[state] = island;
                }
            } 
            return island;
        } 
 
        void InitDynamicModule(string asmName)
        { 
            // See http://blogs.msdn.com/[....]/archive/2005/02/03/366429.aspx for a simple example
            // of debuggable reflection-emit.
            Fx.Assert(dynamicModule == null, "can only be initialized once");
 
            // create a dynamic assembly and module
            AssemblyName assemblyName = new AssemblyName(); 
            assemblyName.Name = asmName; 
            AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave, (string) null);
 
            // Mark generated code as debuggable.
            // See http://blogs.msdn.com/rmbyers/archive/2005/06/26/432922.aspx for explanation.
            Type debuggableAttributeType = typeof(DebuggableAttribute);
            ConstructorInfo constructorInfo = debuggableAttributeType.GetConstructor(new Type[] { typeof(DebuggableAttribute.DebuggingModes) }); 
            CustomAttributeBuilder builder = new CustomAttributeBuilder(constructorInfo, new object[] {
                    DebuggableAttribute.DebuggingModes.DisableOptimizations | 
                    DebuggableAttribute.DebuggingModes.Default 
                });
            assemblyBuilder.SetCustomAttribute(builder); 
            dynamicModule = assemblyBuilder.DefineDynamicModule(asmName, true); // <-- pass 'true' to track debug info.
        }

        internal ISymbolDocumentWriter GetSourceDocument(string fileName) 
        {
            ISymbolDocumentWriter documentWriter; 
            if (!this.sourceDocuments.TryGetValue(fileName, out documentWriter)) 
            {
                documentWriter = 
                    dynamicModule.DefineDocument(
                    fileName,
                    StateManager.WorkflowLanguageGuid,
                    SymLanguageVendor.Microsoft, 
                    SymDocumentType.Text);
                this.sourceDocuments.Add(fileName, documentWriter); 
 
                MD5 md5 = new MD5CryptoServiceProvider();
                byte[] checksumBytes = null; 
                using (StreamReader streamReader = new StreamReader(fileName))
                {
                    checksumBytes = md5.ComputeHash(streamReader.BaseStream);
                } 
                if (checksumBytes != null)
                { 
                    documentWriter.SetCheckSum(new Guid(Md5Identifier), checksumBytes); 
                }
            } 
            return documentWriter;
        }

 
        // Release any unmanaged resources.
        // This may not necessarily unload islands or dynamic modules that were created until the calling appdomain has exited. 
        public void Dispose() 
        {
            foreach (LogicalThread logicalThread in this.threads) 
            {
                if (logicalThread != null)
                {
                    logicalThread.Exit(); 
                }
            } 
            this.threads.Clear(); 
        }
 
        // Release any unmanaged resources.
        // This may not necessarily unload islands or dynamic modules that were created until the calling appdomain has exited.
        public void Exit(int threadIndex)
        { 
            Fx.Assert(threadIndex >= 0, "Invalid thread index");
            Fx.Assert(this.threads[threadIndex] != null, "Cannot dispose null LogicalThread"); 
            LogicalThread thread = this.threads[threadIndex]; 
            thread.Exit();
 
            // Null the entry on the List for future reuse.
            this.threads[threadIndex] = null;
        }
 

        // Property bag for Manager. These provide customization hooks. 
        // All properties have valid default values. 
        [DebuggerNonUserCode]
        internal class Properties 
        {
            public Properties() :
                this("Locals", "Script", "States", "WorkflowDebuggerThread", true)
            { 
            }
 
            public Properties(string defaultLocalsName, string moduleNamePrefix, string typeNamePrefix, string auxiliaryThreadName, bool breakOnStartup) 
            {
                this.DefaultLocalsName = defaultLocalsName; 
                this.ModuleNamePrefix = moduleNamePrefix;
                this.TypeNamePrefix = typeNamePrefix;
                this.AuxiliaryThreadName = auxiliaryThreadName;
                this.BreakOnStartup = breakOnStartup; 
            }
 
            public string DefaultLocalsName 
            {
                get; 
                set;
            }

            // The name of the dynamic module (not including extension) that the states are emitted to. 
            // This may show up on the callstack.
            // This is a prefix because there may be multiple modules for the islands. 
            public string ModuleNamePrefix 
            {
                get; 
                set;
            }

            // Typename that states are created in. 
            // This is a prefix because there may be multiple Types for the islands
            // (such as if islands are created lazily). 
            public string TypeNamePrefix 
            {
                get; 
                set;
            }

            // If UseAuxiliaryThread is true, sets the friendly name of that thread as visible 
            // in the debugger's window.
            public string AuxiliaryThreadName 
            { 
                get;
                set; 
            }

            // If true, the VM issues a Debugger.Break() before entering the first state.
            // This can be useful for an F11 experience on startup to stop at the first state. 
            // If this is false, then the interpreter will run until it hits a breakpoint or some
            // other stopping event. 
            public bool BreakOnStartup 
            {
                get; 
                set;
            }
        }
 
        [DebuggerNonUserCode]
        class LogicalThread 
        { 
            int threadId;
            Stack callStack; 
            ThreadWorkerController controller;

            public LogicalThread(int threadId, string threadName, StateManager stateManager)
            { 
                this.threadId = threadId;
                this.callStack = new Stack(); 
                this.controller = new ThreadWorkerController(); 
                this.controller.Initialize(threadName + "." + threadId.ToString(CultureInfo.InvariantCulture), stateManager);
            } 

            // Unwind call stack cleanly.
            void UnwindCallStack()
            { 
                while (this.callStack.Count > 0)
                { // LeaveState will do the popping. 
                    this.LeaveState(this.callStack.Peek().State); 
                }
            } 

            public void Exit()
            {
                this.UnwindCallStack(); 
                this.controller.Exit();
            } 
 
            // Enter a state and push it onto the 'virtual callstack'.
            // Stackframe describing state to enter, along with the 
            // locals in that state.
            public void EnterState(VirtualStackFrame stackFrame)
            {
                if (stackFrame != null && stackFrame.State != null) 
                {
                    this.callStack.Push(stackFrame); 
                    this.controller.EnterState(stackFrame); 
                }
                else 
                { // signify "Uninstrumented call"
                    this.callStack.Push(null);
                }
            } 

            // Pop the state most recently pushed by EnterState. 
            [SuppressMessage(FxCop.Category.Usage, FxCop.Rule.ReviewUnusedParameters, Justification = "Revisit for bug 36860")] 
            public void LeaveState(State state)
            { 
                if (this.callStack.Count > 0)
                {
                    VirtualStackFrame stackFrame = this.callStack.Pop();
                    Fx.Assert( 
                        (state == null && stackFrame == null) ||
                        (stackFrame != null && stackFrame.State == state), 
                        "Unmatched LeaveState: " + 
                        ((state == null) ? "null" : state.Name) +
                        " with top stack frame: " + 
                        ((stackFrame == null || stackFrame.State == null) ? "null" : stackFrame.State.Name));

                    if (stackFrame != null)    // Matches with an uninstrumented Activity.
                    { 
                        this.controller.LeaveState();
                    } 
                } 
                else
                { 
                    Fx.Assert("Unmatched LeaveState: " + ((state != null) ? state.Name : "null"));
                }
            }
        } 
    }
 
 
    [
    ComImport, 
    Guid("B01FAFEB-C450-3A4D-BEEC-B4CEEC01E006"),
    InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
    ComVisible(false)
    ] 
    internal interface ISymUnmanagedDocumentWriter
    { 
        void SetSource(int sourceSize, 
                          [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] byte[] source);
 
        void SetCheckSum(Guid algorithmId,
                              int checkSumSize,
                              [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] checkSum);
    }; 

} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK