e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
1134 lines
51 KiB
C#
1134 lines
51 KiB
C#
// Copyright (c) Microsoft Corp., 2004. All rights reserved.
|
|
#region Using directives
|
|
|
|
using System;
|
|
using System.IO;
|
|
using System.Xml;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Reflection;
|
|
using System.Collections;
|
|
using System.Diagnostics;
|
|
using System.Runtime.Remoting;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Runtime.Serialization;
|
|
using System.Workflow.Runtime;
|
|
using System.Workflow.Runtime.Hosting;
|
|
using System.Runtime.InteropServices;
|
|
using System.Runtime.Remoting.Channels;
|
|
using System.Workflow.ComponentModel;
|
|
using System.Workflow.ComponentModel.Compiler;
|
|
using System.Workflow.ComponentModel.Design;
|
|
using System.Workflow.ComponentModel.Serialization;
|
|
using System.Runtime.Serialization.Formatters;
|
|
using System.Runtime.Remoting.Channels.Ipc;
|
|
using System.Configuration;
|
|
using System.Security.Permissions;
|
|
using System.Globalization;
|
|
using Microsoft.Win32;
|
|
using System.Security.AccessControl;
|
|
using System.Security.Principal;
|
|
#endregion
|
|
|
|
namespace System.Workflow.Runtime.DebugEngine
|
|
{
|
|
internal static class RegistryKeys
|
|
{
|
|
internal static readonly string ProductRootRegKey = @"SOFTWARE\Microsoft\Net Framework Setup\NDP\v3.0\Setup\Windows Workflow Foundation";
|
|
internal static readonly string DebuggerSubKey = ProductRootRegKey + @"\Debugger";
|
|
}
|
|
|
|
[Obsolete("The System.Workflow.* types are deprecated. Instead, please use the new types from System.Activities.*")]
|
|
public sealed class DebugController : MarshalByRefObject
|
|
{
|
|
#region Data members
|
|
|
|
private Guid programId;
|
|
private string hostName;
|
|
private int attachTimeout;
|
|
private ProgramPublisher programPublisher;
|
|
private DebugControllerThread debugControllerThread;
|
|
private Timer attachTimer;
|
|
private WorkflowRuntime serviceContainer;
|
|
private IpcChannel channel;
|
|
private IWorkflowDebugger controllerConduit;
|
|
private bool isZombie;
|
|
private bool isAttached;
|
|
private ManualResetEvent eventConduitAttached;
|
|
private InstanceTable instanceTable;
|
|
private Dictionary<Type, Guid> typeToGuid;
|
|
private Dictionary<byte[], Guid> xomlHashToGuid;
|
|
bool isServiceContainerStarting;
|
|
private const string rootExecutorGuid = "98fcdc7a-8ab4-4fb7-92d4-20f437285729";
|
|
private object eventLock;
|
|
private object syncRoot = new object();
|
|
private static readonly string ControllerConduitTypeName = "ControllerConduitTypeName";
|
|
|
|
#endregion
|
|
|
|
#region Security related methods
|
|
private delegate void ExceptionNotification(Exception e);
|
|
|
|
internal static void InitializeProcessSecurity()
|
|
{
|
|
// Spawn off a separate thread to that does RevertToSelf and adjusts DACLs.
|
|
// This is because RevertToSelf terminates client impersonation on the thread
|
|
// that calls it. We do not want to change that on the current thread when
|
|
// the runtime is hosted inside ASP.net for example.
|
|
Exception workerThreadException = null;
|
|
ProcessSecurity processSecurity = new ProcessSecurity();
|
|
Thread workerThread = new Thread(new ThreadStart(processSecurity.Initialize));
|
|
|
|
processSecurity.exceptionNotification += delegate(Exception e)
|
|
{
|
|
workerThreadException = e;
|
|
};
|
|
|
|
workerThread.Start(); workerThread.Join();
|
|
if (workerThreadException != null)
|
|
throw workerThreadException;
|
|
}
|
|
|
|
private class ProcessSecurity
|
|
{
|
|
internal ExceptionNotification exceptionNotification;
|
|
|
|
internal void Initialize()
|
|
{
|
|
try
|
|
{
|
|
// This is needed if the thread calling the method is impersonating
|
|
// a client call (ASP.net hosting scenarios).
|
|
if (!NativeMethods.RevertToSelf())
|
|
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
|
|
|
|
// Get the DACL for process token. Add TOKEN_QUERY permissions for the Administrators group.
|
|
// Set the updated DACL for process token.
|
|
RawAcl tokenDacl = GetCurrentProcessTokenDacl();
|
|
CommonAce adminsGroupAceForToken = new CommonAce(AceFlags.None, AceQualifier.AccessAllowed, NativeMethods.TOKEN_QUERY, new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null), false, null);
|
|
int i = FindIndexInDacl(adminsGroupAceForToken, tokenDacl);
|
|
if (i != -1)
|
|
tokenDacl.InsertAce(i, adminsGroupAceForToken);
|
|
SetCurrentProcessTokenDacl(tokenDacl);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
// Communicate any exceptions from this thread back to the thread
|
|
// that spawned it.
|
|
if (exceptionNotification != null)
|
|
exceptionNotification(e);
|
|
}
|
|
}
|
|
|
|
private RawAcl GetCurrentProcessTokenDacl()
|
|
{
|
|
IntPtr hProcess = IntPtr.Zero;
|
|
IntPtr hProcessToken = IntPtr.Zero;
|
|
IntPtr securityDescriptorPtr = IntPtr.Zero;
|
|
|
|
try
|
|
{
|
|
hProcess = NativeMethods.GetCurrentProcess();
|
|
|
|
if (!NativeMethods.OpenProcessToken(hProcess, NativeMethods.TOKEN_ALL_ACCESS, out hProcessToken))
|
|
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
|
|
|
|
// Get security descriptor associated with the kernel object, read the DACL and return
|
|
// that to the caller.
|
|
uint returnLength;
|
|
|
|
NativeMethods.GetKernelObjectSecurity(hProcessToken, NativeMethods.SECURITY_INFORMATION.DACL_SECURITY_INFORMATION, IntPtr.Zero, 0, out returnLength);
|
|
int lasterror = Marshal.GetLastWin32Error(); //#pragma warning disable 56523 doesnt recognize 56523
|
|
|
|
securityDescriptorPtr = Marshal.AllocCoTaskMem((int)returnLength);
|
|
|
|
if (!NativeMethods.GetKernelObjectSecurity(hProcessToken, NativeMethods.SECURITY_INFORMATION.DACL_SECURITY_INFORMATION, securityDescriptorPtr, returnLength, out returnLength))
|
|
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
|
|
|
|
byte[] sdBytes = new byte[returnLength];
|
|
Marshal.Copy(securityDescriptorPtr, sdBytes, 0, (int)returnLength);
|
|
|
|
RawSecurityDescriptor rawSecurityDescriptor = new RawSecurityDescriptor(sdBytes, 0);
|
|
|
|
return rawSecurityDescriptor.DiscretionaryAcl;
|
|
}
|
|
finally
|
|
{
|
|
if (hProcess != IntPtr.Zero && hProcess != (IntPtr)(-1))
|
|
if (!NativeMethods.CloseHandle(hProcess))
|
|
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
|
|
|
|
if (hProcessToken != IntPtr.Zero)
|
|
if (!NativeMethods.CloseHandle(hProcessToken))
|
|
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
|
|
|
|
if (securityDescriptorPtr != IntPtr.Zero)
|
|
Marshal.FreeCoTaskMem(securityDescriptorPtr);
|
|
}
|
|
}
|
|
private void SetCurrentProcessTokenDacl(RawAcl dacl)
|
|
{
|
|
IntPtr hProcess = IntPtr.Zero;
|
|
IntPtr hProcessToken = IntPtr.Zero;
|
|
IntPtr securityDescriptorPtr = IntPtr.Zero;
|
|
try
|
|
{
|
|
hProcess = NativeMethods.GetCurrentProcess();
|
|
|
|
if (!NativeMethods.OpenProcessToken(hProcess, NativeMethods.TOKEN_ALL_ACCESS, out hProcessToken))
|
|
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
|
|
|
|
// Get security descriptor associated with the kernel object and modify it.
|
|
uint returnLength;
|
|
|
|
NativeMethods.GetKernelObjectSecurity(hProcessToken, NativeMethods.SECURITY_INFORMATION.DACL_SECURITY_INFORMATION, IntPtr.Zero, 0, out returnLength);
|
|
int lasterror = Marshal.GetLastWin32Error(); //#pragma warning disable 56523 doesnt recognize 56523
|
|
|
|
securityDescriptorPtr = Marshal.AllocCoTaskMem((int)returnLength);
|
|
|
|
if (!NativeMethods.GetKernelObjectSecurity(hProcessToken, NativeMethods.SECURITY_INFORMATION.DACL_SECURITY_INFORMATION, securityDescriptorPtr, returnLength, out returnLength))
|
|
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
|
|
|
|
byte[] sdBytes = new byte[returnLength];
|
|
Marshal.Copy(securityDescriptorPtr, sdBytes, 0, (int)returnLength);
|
|
|
|
RawSecurityDescriptor rawSecurityDescriptor = new RawSecurityDescriptor(sdBytes, 0);
|
|
rawSecurityDescriptor.DiscretionaryAcl = dacl;
|
|
|
|
sdBytes = new byte[rawSecurityDescriptor.BinaryLength];
|
|
rawSecurityDescriptor.GetBinaryForm(sdBytes, 0);
|
|
Marshal.FreeCoTaskMem(securityDescriptorPtr);
|
|
securityDescriptorPtr = Marshal.AllocCoTaskMem(rawSecurityDescriptor.BinaryLength);
|
|
Marshal.Copy(sdBytes, 0, securityDescriptorPtr, rawSecurityDescriptor.BinaryLength);
|
|
|
|
if (!NativeMethods.SetKernelObjectSecurity(hProcessToken, NativeMethods.SECURITY_INFORMATION.DACL_SECURITY_INFORMATION, securityDescriptorPtr))
|
|
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
|
|
}
|
|
finally
|
|
{
|
|
if (hProcess != IntPtr.Zero && hProcess != (IntPtr)(-1))
|
|
if (!NativeMethods.CloseHandle(hProcess))
|
|
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
|
|
|
|
if (hProcessToken != IntPtr.Zero)
|
|
if (!NativeMethods.CloseHandle(hProcessToken))
|
|
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
|
|
|
|
if (securityDescriptorPtr != IntPtr.Zero)
|
|
Marshal.FreeCoTaskMem(securityDescriptorPtr);
|
|
|
|
}
|
|
}
|
|
|
|
// The preferred order in which ACEs are added to DACLs is
|
|
// documented here: http://search.msdn.microsoft.com/search/results.aspx?qu=Order+of+ACEs+in+a+DACL&View=msdn&st=b.
|
|
// This routine follows that logic to determine the position of an ACE in the DACL.
|
|
private int FindIndexInDacl(CommonAce newAce, RawAcl dacl)
|
|
{
|
|
int i = 0;
|
|
for (i = 0; i < dacl.Count; i++)
|
|
{
|
|
if (dacl[i] is CommonAce && ((CommonAce)dacl[i]).SecurityIdentifier.Value == newAce.SecurityIdentifier.Value && dacl[i].AceType == newAce.AceType)
|
|
{
|
|
i = -1;
|
|
break;
|
|
}
|
|
|
|
if (newAce.AceType == AceType.AccessDenied && dacl[i].AceType == AceType.AccessDenied && !newAce.IsInherited && !dacl[i].IsInherited)
|
|
continue;
|
|
|
|
if (newAce.AceType == AceType.AccessDenied && !newAce.IsInherited)
|
|
break;
|
|
|
|
if (newAce.AceType == AceType.AccessAllowed && dacl[i].AceType == AceType.AccessAllowed && !newAce.IsInherited && !dacl[i].IsInherited)
|
|
continue;
|
|
|
|
if (newAce.AceType == AceType.AccessAllowed && !newAce.IsInherited)
|
|
break;
|
|
|
|
}
|
|
|
|
return i;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Constructor and Lifetime methods
|
|
|
|
internal DebugController(WorkflowRuntime serviceContainer, string hostName)
|
|
{
|
|
if (serviceContainer == null)
|
|
throw new ArgumentNullException("serviceContainer");
|
|
|
|
try
|
|
{
|
|
this.programPublisher = new ProgramPublisher();
|
|
}
|
|
catch
|
|
{
|
|
// If we are unable to create the ProgramPublisher, this means that VS does not exist on this machine, so we can't debug.
|
|
return;
|
|
}
|
|
|
|
this.serviceContainer = serviceContainer;
|
|
this.programId = Guid.Empty;
|
|
this.controllerConduit = null;
|
|
this.channel = null;
|
|
this.isZombie = false;
|
|
this.hostName = hostName;
|
|
|
|
AppDomain.CurrentDomain.ProcessExit += OnDomainUnload;
|
|
AppDomain.CurrentDomain.DomainUnload += OnDomainUnload;
|
|
|
|
this.serviceContainer.Started += this.Start;
|
|
this.serviceContainer.Stopped += this.Stop;
|
|
}
|
|
|
|
public override object InitializeLifetimeService()
|
|
{
|
|
// We can't use a sponser because VS doesn't like to be attached when the lease renews itself - the
|
|
// debugee gets an Access Violation and VS freezes. Returning null implies that the proxy shim will be
|
|
// deleted only when the App Domain unloads. However, we will have disconnected the shim so no
|
|
// one will be able to attach to it and the same proxy is used everytime a debugger attaches.
|
|
return null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Attach and Detach methods
|
|
|
|
internal void Attach(Guid programId, int attachTimeout, int detachPingInterval, out string hostName, out string uri, out int controllerThreadId, out bool isSynchronousAttach)
|
|
{
|
|
Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "WDE: DebugController.Attach(): programId = {0}", programId));
|
|
lock (this.syncRoot)
|
|
{
|
|
hostName = String.Empty;
|
|
uri = String.Empty;
|
|
controllerThreadId = 0;
|
|
isSynchronousAttach = false;
|
|
|
|
// Race condition:
|
|
// During the call to Attach() if Uninitialize() is also called, we should ignore the call to Attach() and
|
|
// just return. The Zombie flag and lock(this) help us recognize the ----.
|
|
if (this.isZombie)
|
|
return;
|
|
|
|
|
|
// Race condition:
|
|
// The isAttached flat along with lock(this) catch the ---- where a debugger may have detached which
|
|
// we haven't detected yet and another debugger may have attached, so we force detach from the first
|
|
// debugger.
|
|
if (this.isAttached)
|
|
Detach();
|
|
|
|
|
|
this.isAttached = true;
|
|
|
|
this.programId = programId;
|
|
this.debugControllerThread = new DebugControllerThread();
|
|
this.instanceTable = new InstanceTable(this.debugControllerThread.ManagedThreadId);
|
|
this.typeToGuid = new Dictionary<Type, Guid>();
|
|
this.xomlHashToGuid = new Dictionary<byte[], Guid>((IEqualityComparer<byte[]>)new DigestComparer());
|
|
|
|
this.debugControllerThread.RunThread(this.instanceTable);
|
|
|
|
// Publish our MBR object.
|
|
IDictionary providerProperties = new Hashtable();
|
|
providerProperties["typeFilterLevel"] = "Full";
|
|
BinaryServerFormatterSinkProvider sinkProvider = new BinaryServerFormatterSinkProvider(providerProperties, null);
|
|
|
|
Hashtable channelProperties = new Hashtable();
|
|
channelProperties["name"] = string.Empty;
|
|
channelProperties["portName"] = this.programId.ToString();
|
|
SecurityIdentifier si = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null);
|
|
IdentityReference idRef = si.Translate(typeof(NTAccount));
|
|
channelProperties["authorizedGroup"] = idRef.ToString();
|
|
this.channel = new IpcChannel(channelProperties, null, sinkProvider);
|
|
ChannelServices.RegisterChannel(this.channel, true);
|
|
|
|
ObjRef o = RemotingServices.Marshal(this, this.programId.ToString());
|
|
hostName = this.hostName;
|
|
|
|
uri = this.channel.GetUrlsForUri(this.programId.ToString())[0];
|
|
controllerThreadId = this.debugControllerThread.ThreadId;
|
|
isSynchronousAttach = !this.isServiceContainerStarting;
|
|
|
|
this.attachTimeout = attachTimeout;
|
|
this.attachTimer = new Timer(AttachTimerCallback, null, attachTimeout, detachPingInterval);
|
|
}
|
|
}
|
|
|
|
private void Detach()
|
|
{
|
|
Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "WDE: DebugController.Detach():"));
|
|
|
|
using (new DebuggerThreadMarker())
|
|
{
|
|
lock (this.syncRoot)
|
|
{
|
|
|
|
AppDomain.CurrentDomain.AssemblyLoad -= OnAssemblyLoad;
|
|
|
|
// See comments in Attach().
|
|
if (this.isZombie || !this.isAttached)
|
|
return;
|
|
|
|
this.isAttached = false;
|
|
|
|
// Undone: AkashS - At this point wait for all event handling to complete to avoid exceptions.
|
|
|
|
this.programId = Guid.Empty;
|
|
|
|
if (this.debugControllerThread != null)
|
|
{
|
|
this.debugControllerThread.StopThread();
|
|
this.debugControllerThread = null;
|
|
}
|
|
|
|
if (this.attachTimer != null)
|
|
{
|
|
this.attachTimer.Change(Timeout.Infinite, Timeout.Infinite);
|
|
this.attachTimer = null;
|
|
}
|
|
|
|
RemotingServices.Disconnect(this);
|
|
if (this.channel != null)
|
|
{
|
|
ChannelServices.UnregisterChannel(this.channel);
|
|
this.channel = null;
|
|
}
|
|
|
|
this.controllerConduit = null;
|
|
|
|
this.eventConduitAttached.Reset();
|
|
this.instanceTable = null;
|
|
this.typeToGuid = null;
|
|
this.xomlHashToGuid = null;
|
|
|
|
// Do this only after we perform the previous cleanup! Otherwise
|
|
// we may get exceptions from the runtime that may cause the cleanup
|
|
// to not happen.
|
|
|
|
if (!this.serviceContainer.IsZombie)
|
|
{
|
|
foreach (WorkflowInstance instance in this.serviceContainer.GetLoadedWorkflows())
|
|
{
|
|
WorkflowExecutor executor = instance.GetWorkflowResourceUNSAFE();
|
|
using (executor.ExecutorLock.Enter())
|
|
{
|
|
if (executor.IsInstanceValid)
|
|
executor.WorkflowExecutionEvent -= OnInstanceEvent;
|
|
}
|
|
}
|
|
|
|
this.serviceContainer.WorkflowExecutorInitializing -= InstanceInitializing;
|
|
this.serviceContainer.DefinitionDispenser.WorkflowDefinitionLoaded -= ScheduleTypeLoaded;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void AttachTimerCallback(object state)
|
|
{
|
|
Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "WDE: DebugController.AttachTimerCallback():"));
|
|
|
|
try
|
|
{
|
|
lock (this.syncRoot)
|
|
{
|
|
// See comments in Attach().
|
|
if (this.isZombie || !this.isAttached)
|
|
return;
|
|
|
|
if (!Debugger.IsAttached)
|
|
{
|
|
// The debugger had attached and has now detached, so cleanup, or Attach() was called on the
|
|
// Program Node, but the process of attach failed thereafter and so we were never actually
|
|
// debugged.
|
|
this.attachTimer.Change(Timeout.Infinite, Timeout.Infinite);
|
|
Detach();
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Avoid throwing unhandled exceptions!
|
|
}
|
|
}
|
|
|
|
private void OnDomainUnload(object sender, System.EventArgs e)
|
|
{
|
|
Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "WDE: DebugController.OnDomainUnload():"));
|
|
|
|
Stop(null, default(WorkflowRuntimeEventArgs));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Methods for the DE
|
|
|
|
public void AttachToConduit(Uri url)
|
|
{
|
|
if (url == null)
|
|
throw new ArgumentNullException("url");
|
|
|
|
try
|
|
{
|
|
using (new DebuggerThreadMarker())
|
|
{
|
|
try
|
|
{
|
|
RegistryKey debugEngineSubKey = Registry.LocalMachine.OpenSubKey(RegistryKeys.DebuggerSubKey);
|
|
if (debugEngineSubKey != null)
|
|
{
|
|
string controllerConduitTypeName = debugEngineSubKey.GetValue(ControllerConduitTypeName, String.Empty) as string;
|
|
if (!String.IsNullOrEmpty(controllerConduitTypeName) && Type.GetType(controllerConduitTypeName) != null)
|
|
this.controllerConduit = Activator.GetObject(Type.GetType(controllerConduitTypeName), url.AbsolutePath) as IWorkflowDebugger;
|
|
}
|
|
}
|
|
catch { }
|
|
|
|
if (this.controllerConduit == null)
|
|
{
|
|
const string controllerConduitTypeFormat = "Microsoft.Workflow.DebugEngine.ControllerConduit, Microsoft.Workflow.DebugController, Version={0}.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35";
|
|
|
|
// Try versions 12.0.0.0, 11.0.0.0, 10.0.0.0
|
|
Type controllerConduitType = null;
|
|
for (int version = 12; controllerConduitType == null && version >= 10; --version)
|
|
{
|
|
try
|
|
{
|
|
controllerConduitType = Type.GetType(string.Format(CultureInfo.InvariantCulture, controllerConduitTypeFormat, version));
|
|
}
|
|
catch (TypeLoadException)
|
|
{
|
|
// Fall back to next-lower version
|
|
}
|
|
}
|
|
|
|
if (controllerConduitType != null)
|
|
{
|
|
this.controllerConduit = Activator.GetObject(controllerConduitType, url.AbsolutePath) as IWorkflowDebugger;
|
|
}
|
|
}
|
|
Debug.Assert(this.controllerConduit != null, "Failed to create Controller Conduit");
|
|
if (this.controllerConduit == null)
|
|
return;
|
|
|
|
this.eventLock = new object();
|
|
|
|
// Race Condition:
|
|
// We hook up to the AssemblyLoad event, the Schedule events and Instance events handler
|
|
// before we iterate over all loaded assemblies. This means that we need to deal with duplicates in the
|
|
// debugee.
|
|
|
|
// Race Condition:
|
|
// Further the order in which we hook up handlers/iterate is important to avoid ----s if the events fire
|
|
// before the iterations complete. We need to hook and iterate over the assemblies, then the schedule
|
|
// types and finally the instances. This guarantees that we always have all the assemblies when we load
|
|
// schedules types and we always have all the schedule types when we load instances.
|
|
|
|
AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoad;
|
|
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
|
|
{
|
|
if (!assembly.IsDynamic
|
|
&& !(assembly is System.Reflection.Emit.AssemblyBuilder)
|
|
&& !(string.IsNullOrEmpty(assembly.Location)))
|
|
{
|
|
this.controllerConduit.AssemblyLoaded(this.programId, assembly.Location, assembly.GlobalAssemblyCache);
|
|
}
|
|
}
|
|
this.serviceContainer.DefinitionDispenser.WorkflowDefinitionLoaded += ScheduleTypeLoaded;
|
|
|
|
// In here we load all schedule types defined as they are - with no dynamic updates
|
|
ReadOnlyCollection<Type> types;
|
|
ReadOnlyCollection<Activity> values;
|
|
this.serviceContainer.DefinitionDispenser.GetWorkflowTypes(out types, out values);
|
|
for (int i = 0; i < types.Count; i++)
|
|
{
|
|
Type scheduleType = types[i];
|
|
Activity rootActivity = values[i];
|
|
LoadExistingScheduleType(GetScheduleTypeId(scheduleType), scheduleType, false, rootActivity);
|
|
}
|
|
|
|
ReadOnlyCollection<byte[]> keys;
|
|
this.serviceContainer.DefinitionDispenser.GetWorkflowDefinitions(out keys, out values);
|
|
for (int i = 0; i < keys.Count; i++)
|
|
{
|
|
byte[] scheduleDefHash = keys[i];
|
|
Activity rootActivity = values[i];
|
|
Activity workflowDefinition = (Activity)rootActivity.GetValue(Activity.WorkflowDefinitionProperty);
|
|
ArrayList changeActions = null;
|
|
if (workflowDefinition != null)
|
|
changeActions = (ArrayList)workflowDefinition.GetValue(WorkflowChanges.WorkflowChangeActionsProperty);
|
|
LoadExistingScheduleType(GetScheduleTypeId(scheduleDefHash), rootActivity.GetType(), (changeActions != null && changeActions.Count != 0), rootActivity);
|
|
}
|
|
|
|
this.serviceContainer.WorkflowExecutorInitializing += InstanceInitializing;
|
|
|
|
foreach (WorkflowInstance instance in this.serviceContainer.GetLoadedWorkflows())
|
|
{
|
|
WorkflowExecutor executor = instance.GetWorkflowResourceUNSAFE();
|
|
using (executor.ExecutorLock.Enter())
|
|
{
|
|
LoadExistingInstance(instance, true);
|
|
}
|
|
}
|
|
|
|
this.eventConduitAttached.Set();
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
// Don't throw exceptions to the Runtime. Ignore exceptions that may occur if the debugger detaches
|
|
// and closes the remoting channel.
|
|
Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "WDE: Failure in DebugController.AttachToConduit: {0}, Call stack:{1}", e.Message, e.StackTrace));
|
|
}
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Methods for the Runtime
|
|
|
|
private void Start(object source, WorkflowRuntimeEventArgs e)
|
|
{
|
|
Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "WDE: DebugController.ServiceContainerStarted():"));
|
|
|
|
this.isZombie = false;
|
|
this.isAttached = false;
|
|
this.eventConduitAttached = new ManualResetEvent(false);
|
|
this.isServiceContainerStarting = true;
|
|
|
|
bool published = this.programPublisher.Publish(this);
|
|
|
|
// If the debugger is already attached, then the DE will invoke AttachToConduit() on a separate thread.
|
|
// We need to wait for that to happen to prevent new instances being created and causing a ----. See
|
|
// comments in ControllerConduit.ProgramCreated(). However, if the DE never calls AttachToConduit(),
|
|
// and the detaches instead, we set a wait timeout to that of our Attach Timer.
|
|
// Note that when we publish the program node, if the debugger is attached, isAttached will be set to true
|
|
// when the debugger calls Attach() on the Program Node!
|
|
|
|
while (published && this.isAttached && !this.eventConduitAttached.WaitOne(attachTimeout, false));
|
|
this.isServiceContainerStarting = false;
|
|
}
|
|
|
|
private void Stop(object source, WorkflowRuntimeEventArgs e)
|
|
{
|
|
Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "WDE: DebugController.ServiceContainerStopped():"));
|
|
|
|
try
|
|
{
|
|
lock (this.syncRoot)
|
|
{
|
|
Detach();
|
|
this.programPublisher.Unpublish();
|
|
|
|
// See comments in Attach().
|
|
this.isZombie = true;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Do not throw exceptions back!
|
|
}
|
|
}
|
|
|
|
internal void Close()
|
|
{
|
|
//Unregister from Appdomain event to remove ourselves from GCRoot.
|
|
AppDomain.CurrentDomain.ProcessExit -= OnDomainUnload;
|
|
AppDomain.CurrentDomain.DomainUnload -= OnDomainUnload;
|
|
|
|
if (!this.isZombie)
|
|
{
|
|
Stop(null, new WorkflowRuntimeEventArgs(false));
|
|
}
|
|
}
|
|
|
|
|
|
private void OnInstanceEvent(object sender, WorkflowExecutor.WorkflowExecutionEventArgs e)
|
|
{
|
|
switch (e.EventType)
|
|
{
|
|
case WorkflowEventInternal.Completed:
|
|
InstanceCompleted(sender, new WorkflowEventArgs(((WorkflowExecutor)sender).WorkflowInstance));
|
|
break;
|
|
case WorkflowEventInternal.Terminated:
|
|
InstanceTerminated(sender, new WorkflowEventArgs(((WorkflowExecutor)sender).WorkflowInstance));
|
|
break;
|
|
case WorkflowEventInternal.Unloaded:
|
|
InstanceUnloaded(sender, new WorkflowEventArgs(((WorkflowExecutor)sender).WorkflowInstance));
|
|
break;
|
|
case WorkflowEventInternal.Changed:
|
|
OnWorkflowChanged(sender, e);
|
|
break;
|
|
case WorkflowEventInternal.HandlerInvoking:
|
|
OnHandlerInvoking(sender, e);
|
|
break;
|
|
case WorkflowEventInternal.HandlerInvoked:
|
|
OnHandlerInvoked(sender, e);
|
|
break;
|
|
case WorkflowEventInternal.ActivityStatusChange:
|
|
OnActivityStatusChanged(sender, (WorkflowExecutor.ActivityStatusChangeEventArgs)e);
|
|
break;
|
|
case WorkflowEventInternal.ActivityExecuting:
|
|
OnActivityExecuting(sender, (WorkflowExecutor.ActivityExecutingEventArgs)e);
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
private void InstanceInitializing(object sender, WorkflowRuntime.WorkflowExecutorInitializingEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
if (e.Loading)
|
|
LoadExistingInstance(((WorkflowExecutor)sender).WorkflowInstance, true);
|
|
else
|
|
LoadExistingInstance(((WorkflowExecutor)sender).WorkflowInstance, false);
|
|
}
|
|
catch
|
|
{
|
|
// Don't throw exceptions to the Runtime. Ignore exceptions that may occur if the debugger detaches
|
|
// and closes the remoting channel.
|
|
}
|
|
}
|
|
|
|
private void InstanceCompleted(object sender, WorkflowEventArgs args)
|
|
{
|
|
try
|
|
{
|
|
UnloadExistingInstance(args.WorkflowInstance);
|
|
}
|
|
catch
|
|
{
|
|
// Don't throw exceptions to the Runtime. Ignore exceptions that may occur if the debugger detaches
|
|
// and closes the remoting channel.
|
|
}
|
|
}
|
|
|
|
private void InstanceTerminated(object sender, WorkflowEventArgs args)
|
|
{
|
|
try
|
|
{
|
|
UnloadExistingInstance(args.WorkflowInstance);
|
|
}
|
|
catch
|
|
{
|
|
// Don't throw exceptions to the Runtime. Ignore exceptions that may occur if the debugger detaches
|
|
// and closes the remoting channel.
|
|
}
|
|
}
|
|
|
|
private void InstanceUnloaded(object sender, WorkflowEventArgs args)
|
|
{
|
|
// Treat this as if the instance completed so that it won't show up in the UI anymore.
|
|
try
|
|
{
|
|
UnloadExistingInstance(args.WorkflowInstance);
|
|
}
|
|
catch
|
|
{
|
|
// Don't throw exceptions to the Runtime. Ignore exceptions that may occur if the debugger detaches
|
|
// and closes the remoting channel.
|
|
}
|
|
}
|
|
|
|
private void ScheduleTypeLoaded(object sender, WorkflowDefinitionEventArgs args)
|
|
{
|
|
try
|
|
{
|
|
if (args.WorkflowType != null)
|
|
{
|
|
Activity rootActivity = ((WorkflowRuntime)sender).DefinitionDispenser.GetWorkflowDefinition(args.WorkflowType);
|
|
LoadExistingScheduleType(GetScheduleTypeId(args.WorkflowType), args.WorkflowType, false, rootActivity);
|
|
}
|
|
else
|
|
{
|
|
Activity rootActivity = ((WorkflowRuntime)sender).DefinitionDispenser.GetWorkflowDefinition(args.WorkflowDefinitionHashCode);
|
|
LoadExistingScheduleType(GetScheduleTypeId(args.WorkflowDefinitionHashCode), rootActivity.GetType(), false, rootActivity);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Don't throw exceptions to the Runtime. Ignore exceptions that may occur if the debugger detaches
|
|
// and closes the remoting channel.
|
|
}
|
|
}
|
|
|
|
private void OnActivityExecuting(object sender, WorkflowExecutor.ActivityExecutingEventArgs eventArgs)
|
|
{
|
|
if (this.isZombie || !this.isAttached)
|
|
return;
|
|
|
|
try
|
|
{
|
|
lock (this.eventLock)
|
|
{
|
|
IWorkflowCoreRuntime workflowCoreRuntime = (IWorkflowCoreRuntime)sender;
|
|
Guid scheduleTypeId = GetScheduleTypeId(workflowCoreRuntime);
|
|
|
|
// When the activity starts executing, update its handler list for stepping.
|
|
EnumerateEventHandlersForActivity(scheduleTypeId, eventArgs.Activity);
|
|
this.controllerConduit.BeforeActivityStatusChanged(this.programId, scheduleTypeId, workflowCoreRuntime.InstanceID, eventArgs.Activity.QualifiedName, GetHierarchicalId(eventArgs.Activity), eventArgs.Activity.ExecutionStatus, GetContextId(eventArgs.Activity));
|
|
this.controllerConduit.ActivityStatusChanged(this.programId, scheduleTypeId, workflowCoreRuntime.InstanceID, eventArgs.Activity.QualifiedName, GetHierarchicalId(eventArgs.Activity), eventArgs.Activity.ExecutionStatus, GetContextId(eventArgs.Activity));
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Don't throw exceptions to the Runtime. Ignore exceptions that may occur if the debugger detaches
|
|
// and closes the remoting channel.
|
|
}
|
|
}
|
|
|
|
private void OnActivityStatusChanged(object sender, WorkflowExecutor.ActivityStatusChangeEventArgs eventArgs)
|
|
{
|
|
if (this.isZombie || !this.isAttached)
|
|
return;
|
|
|
|
try
|
|
{
|
|
lock (this.eventLock)
|
|
{
|
|
// We will receive an event when Activity.Execute() is about to be called.
|
|
if (eventArgs.Activity.ExecutionStatus == ActivityExecutionStatus.Executing)
|
|
return;
|
|
|
|
IWorkflowCoreRuntime workflowCoreRuntime = (IWorkflowCoreRuntime)sender;
|
|
Guid scheduleTypeId = GetScheduleTypeId(workflowCoreRuntime);
|
|
|
|
// When the activity starts executing, update its handler list for stepping.
|
|
if (eventArgs.Activity.ExecutionStatus == ActivityExecutionStatus.Executing)
|
|
EnumerateEventHandlersForActivity(scheduleTypeId, eventArgs.Activity);
|
|
|
|
this.controllerConduit.BeforeActivityStatusChanged(this.programId, scheduleTypeId, workflowCoreRuntime.InstanceID, eventArgs.Activity.QualifiedName, GetHierarchicalId(eventArgs.Activity), eventArgs.Activity.ExecutionStatus, GetContextId(eventArgs.Activity));
|
|
this.controllerConduit.ActivityStatusChanged(this.programId, scheduleTypeId, workflowCoreRuntime.InstanceID, eventArgs.Activity.QualifiedName, GetHierarchicalId(eventArgs.Activity), eventArgs.Activity.ExecutionStatus, GetContextId(eventArgs.Activity));
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Don't throw exceptions to the Runtime. Ignore exceptions that may occur if the debugger detaches
|
|
// and closes the remoting channel.
|
|
}
|
|
}
|
|
|
|
private void OnHandlerInvoking(object sender, EventArgs eventArgs)
|
|
{
|
|
// Undone: AkashS - We need to remove EnumerateHandlersForActivity() and set the CPDE
|
|
// breakpoints from here. This is handle the cases where event handlers are modified
|
|
// at runtime.
|
|
}
|
|
|
|
private void OnHandlerInvoked(object sender, EventArgs eventArgs)
|
|
{
|
|
if (this.isZombie || !this.isAttached)
|
|
return;
|
|
|
|
try
|
|
{
|
|
lock (this.eventLock)
|
|
{
|
|
IWorkflowCoreRuntime workflowCoreRuntime = sender as IWorkflowCoreRuntime;
|
|
this.controllerConduit.HandlerInvoked(this.programId, workflowCoreRuntime.InstanceID, NativeMethods.GetCurrentThreadId(), GetHierarchicalId(workflowCoreRuntime.CurrentActivity));
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Don't throw exceptions to the Runtime. Ignore exceptions that may occur if the debugger detaches
|
|
// and closes the remoting channel.
|
|
}
|
|
}
|
|
|
|
private void OnWorkflowChanged(object sender, EventArgs eventArgs)
|
|
{
|
|
if (this.isZombie || !this.isAttached)
|
|
return;
|
|
|
|
try
|
|
{
|
|
lock (this.eventLock)
|
|
{
|
|
IWorkflowCoreRuntime workflowCoreRuntime = (IWorkflowCoreRuntime)sender;
|
|
|
|
// Get cached old root activity.
|
|
Activity oldRootActivity = this.instanceTable.GetRootActivity(workflowCoreRuntime.InstanceID);
|
|
|
|
Guid scheduleTypeId = workflowCoreRuntime.InstanceID; // From now on we will treat the instance id as a dynamic schedule type id.
|
|
LoadExistingScheduleType(scheduleTypeId, oldRootActivity.GetType(), true, oldRootActivity);
|
|
|
|
// And now reload the instance.
|
|
this.instanceTable.UpdateRootActivity(workflowCoreRuntime.InstanceID, oldRootActivity);
|
|
|
|
// The DE will update the schedule type on the thread that is running the instance.
|
|
// DE should be called after the instance table entry is replaced.
|
|
this.controllerConduit.InstanceDynamicallyUpdated(this.programId, workflowCoreRuntime.InstanceID, scheduleTypeId);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Don't throw exceptions to the Runtime. Ignore exceptions that may occur if the debugger detaches
|
|
// and closes the remoting channel.
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Helper methods and properties
|
|
|
|
// Callers of this method should acquire the executor lock only if they
|
|
// are not being called in the runtime thread.(bug 17231).
|
|
private void LoadExistingInstance(WorkflowInstance instance, bool attaching)
|
|
{
|
|
WorkflowExecutor executor = instance.GetWorkflowResourceUNSAFE();
|
|
if (!executor.IsInstanceValid)
|
|
return;
|
|
IWorkflowCoreRuntime runtimeService = (IWorkflowCoreRuntime)executor;
|
|
Activity rootActivity = runtimeService.RootActivity;
|
|
Guid scheduleTypeId = GetScheduleTypeId(runtimeService);
|
|
|
|
// If we are just attaching, need to LoadExistingScheduleType with the dynamic definition and type
|
|
// since the OnDynamicUpdateEvent has never been executed.
|
|
if (attaching && runtimeService.IsDynamicallyUpdated)
|
|
LoadExistingScheduleType(scheduleTypeId, rootActivity.GetType(), true, rootActivity);
|
|
|
|
// Add to the InstanceTable before firing the DE event !
|
|
this.instanceTable.AddInstance(instance.InstanceId, rootActivity);
|
|
|
|
this.controllerConduit.InstanceCreated(this.programId, instance.InstanceId, scheduleTypeId);
|
|
|
|
// Take a lock so that SetInitialActivityStatus is always called before next status events.
|
|
lock (this.eventLock)
|
|
{
|
|
executor.WorkflowExecutionEvent += OnInstanceEvent;
|
|
foreach (Activity activity in DebugController.WalkActivityTree(rootActivity))
|
|
{
|
|
#if false
|
|
//
|
|
ReplicatorActivity replicator = activity as ReplicatorActivity;
|
|
if (replicator != null)
|
|
{
|
|
foreach (Activity queuedChildActivity in replicator.DynamicActivities)
|
|
activityQueue.Enqueue(queuedChildActivity);
|
|
}
|
|
else
|
|
#endif
|
|
UpdateActivityStatus(scheduleTypeId, instance.InstanceId, activity);
|
|
|
|
}
|
|
|
|
ActivityExecutionContext rootExecutionContext = new ActivityExecutionContext(rootActivity);
|
|
foreach (ActivityExecutionContext executionContext in DebugController.WalkExecutionContextTree(rootExecutionContext))
|
|
{
|
|
Activity instanceActivity = executionContext.Activity;
|
|
foreach (Activity childInstance in DebugController.WalkActivityTree(instanceActivity))
|
|
{
|
|
UpdateActivityStatus(scheduleTypeId, instance.InstanceId, childInstance);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UpdateActivityStatus(Guid scheduleTypeId, Guid instanceId, Activity activity)
|
|
{
|
|
if (activity == null)
|
|
throw new ArgumentNullException("activity");
|
|
|
|
// first update its handler list
|
|
if (activity.ExecutionStatus == ActivityExecutionStatus.Executing)
|
|
EnumerateEventHandlersForActivity(scheduleTypeId, activity);
|
|
|
|
//report only states different from the initialized
|
|
if (activity.ExecutionStatus != ActivityExecutionStatus.Initialized)
|
|
{
|
|
Activity contextActivity = ContextActivityUtils.ContextActivity(activity);
|
|
int context = ContextActivityUtils.ContextId(contextActivity);
|
|
this.controllerConduit.SetInitialActivityStatus(this.programId, scheduleTypeId, instanceId, activity.QualifiedName, GetHierarchicalId(activity), activity.ExecutionStatus, context);
|
|
}
|
|
|
|
}
|
|
|
|
private static IEnumerable WalkActivityTree(Activity rootActivity)
|
|
{
|
|
if (rootActivity == null || !rootActivity.Enabled)
|
|
yield break;
|
|
|
|
// Return self
|
|
yield return rootActivity;
|
|
|
|
// Go through the children as well
|
|
if (rootActivity is CompositeActivity)
|
|
{
|
|
foreach (Activity childActivity in ((CompositeActivity)rootActivity).Activities)
|
|
{
|
|
foreach (Activity nestedChild in WalkActivityTree(childActivity))
|
|
yield return nestedChild;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static IEnumerable WalkExecutionContextTree(ActivityExecutionContext rootContext)
|
|
{
|
|
if (rootContext == null)
|
|
yield break;
|
|
|
|
yield return rootContext;
|
|
|
|
foreach (ActivityExecutionContext executionContext in rootContext.ExecutionContextManager.ExecutionContexts)
|
|
{
|
|
foreach (ActivityExecutionContext nestedContext in WalkExecutionContextTree(executionContext))
|
|
yield return nestedContext;
|
|
}
|
|
yield break;
|
|
}
|
|
|
|
private void UnloadExistingInstance(WorkflowInstance instance)
|
|
{
|
|
// Fire DE event before removing from the InstanceTable!
|
|
this.controllerConduit.InstanceCompleted(this.programId, instance.InstanceId);
|
|
this.instanceTable.RemoveInstance(instance.InstanceId);
|
|
}
|
|
|
|
private void LoadExistingScheduleType(Guid scheduleTypeId, Type scheduleType, bool isDynamic, Activity rootActivity)
|
|
{
|
|
if (rootActivity == null)
|
|
throw new InvalidOperationException();
|
|
|
|
using (StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture))
|
|
{
|
|
using (XmlWriter xmlWriter = Helpers.CreateXmlWriter(stringWriter))
|
|
{
|
|
WorkflowMarkupSerializer serializer = new WorkflowMarkupSerializer();
|
|
serializer.Serialize(xmlWriter, rootActivity);
|
|
string fileName = null;
|
|
string md5Digest = null;
|
|
Attribute[] attributes = scheduleType.GetCustomAttributes(typeof(WorkflowMarkupSourceAttribute), false) as Attribute[];
|
|
if (attributes != null && attributes.Length == 1)
|
|
{
|
|
fileName = ((WorkflowMarkupSourceAttribute)attributes[0]).FileName;
|
|
md5Digest = ((WorkflowMarkupSourceAttribute)attributes[0]).MD5Digest;
|
|
}
|
|
|
|
this.controllerConduit.ScheduleTypeLoaded(this.programId, scheduleTypeId, scheduleType.Assembly.FullName, fileName, md5Digest, isDynamic, scheduleType.FullName, scheduleType.Name, stringWriter.ToString());
|
|
}
|
|
}
|
|
}
|
|
|
|
private string GetHierarchicalId(Activity activity)
|
|
{
|
|
string id = string.Empty;
|
|
while (activity != null)
|
|
{
|
|
string iterationId = string.Empty;
|
|
|
|
Activity contextActivity = ContextActivityUtils.ContextActivity(activity);
|
|
int context = ContextActivityUtils.ContextId(contextActivity);
|
|
iterationId = activity.Name + ((context > 1 && activity == contextActivity) ? "(" + context + ")" : string.Empty);
|
|
|
|
id = (id.Length > 0) ? iterationId + "." + id : iterationId;
|
|
|
|
activity = activity.Parent;
|
|
}
|
|
|
|
return id;
|
|
}
|
|
|
|
private int GetContextId(Activity activity)
|
|
{
|
|
Activity contextActivity = ContextActivityUtils.ContextActivity(activity);
|
|
return ContextActivityUtils.ContextId(contextActivity);
|
|
}
|
|
|
|
private Guid GetScheduleTypeId(IWorkflowCoreRuntime workflowCoreRuntime)
|
|
{
|
|
Activity rootActivity = workflowCoreRuntime.RootActivity;
|
|
|
|
if (workflowCoreRuntime.IsDynamicallyUpdated)
|
|
return workflowCoreRuntime.InstanceID;
|
|
else if (string.IsNullOrEmpty(rootActivity.GetValue(Activity.WorkflowXamlMarkupProperty) as string))
|
|
return GetScheduleTypeId(rootActivity.GetType());
|
|
else
|
|
return GetScheduleTypeId(rootActivity.GetValue(WorkflowDefinitionDispenser.WorkflowDefinitionHashCodeProperty) as byte[]);
|
|
}
|
|
|
|
private Guid GetScheduleTypeId(Type scheduleType)
|
|
{
|
|
// We cannot use the GUID from the type because that is not guaranteed to be unique, especially when
|
|
// multiple versions are loaded and the stamps a GuidAttribute.
|
|
lock (this.typeToGuid)
|
|
{
|
|
if (!this.typeToGuid.ContainsKey(scheduleType))
|
|
this.typeToGuid[scheduleType] = Guid.NewGuid();
|
|
|
|
return (Guid)this.typeToGuid[scheduleType];
|
|
}
|
|
}
|
|
|
|
private Guid GetScheduleTypeId(byte[] scheduleDefHashCode)
|
|
{
|
|
// We use the same hashtable to store schedule definition to Guid mapping.
|
|
lock (this.xomlHashToGuid)
|
|
{
|
|
if (!this.xomlHashToGuid.ContainsKey(scheduleDefHashCode))
|
|
this.xomlHashToGuid[scheduleDefHashCode] = Guid.NewGuid();
|
|
|
|
return (Guid)this.xomlHashToGuid[scheduleDefHashCode];
|
|
}
|
|
}
|
|
|
|
private void OnAssemblyLoad(object sender, AssemblyLoadEventArgs args)
|
|
{
|
|
// Call assembly load on the conduit for assemblies loaded from disk.
|
|
if (args.LoadedAssembly.Location != String.Empty)
|
|
{
|
|
try
|
|
{
|
|
this.controllerConduit.AssemblyLoaded(this.programId, args.LoadedAssembly.Location, args.LoadedAssembly.GlobalAssemblyCache);
|
|
}
|
|
catch
|
|
{
|
|
// Don't throw exceptions to the CLR. Ignore exceptions that may occur if the debugger detaches
|
|
// and closes the remoting channel.
|
|
}
|
|
}
|
|
}
|
|
|
|
private void EnumerateEventHandlersForActivity(Guid scheduleTypeId, Activity activity)
|
|
{
|
|
List<ActivityHandlerDescriptor> handlerMethods = new List<ActivityHandlerDescriptor>();
|
|
MethodInfo getInvocationListMethod = activity.GetType().GetMethod("System.Workflow.ComponentModel.IDependencyObjectAccessor.GetInvocationList", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
|
|
|
|
foreach (EventInfo eventInfo in activity.GetType().GetEvents(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy))
|
|
{
|
|
DependencyProperty dependencyEvent = DependencyProperty.FromName(eventInfo.Name, activity.GetType());
|
|
|
|
if (dependencyEvent != null)
|
|
{
|
|
try
|
|
{
|
|
MethodInfo boundGetInvocationListMethod = getInvocationListMethod.MakeGenericMethod(new Type[] { dependencyEvent.PropertyType });
|
|
foreach (Delegate handler in (boundGetInvocationListMethod.Invoke(activity, new object[] { dependencyEvent }) as Delegate[]))
|
|
{
|
|
MethodInfo handlerMethodInfo = handler.Method;
|
|
ActivityHandlerDescriptor handlerMethod;
|
|
handlerMethod.Name = handlerMethodInfo.DeclaringType.FullName + "." + handlerMethodInfo.Name;
|
|
handlerMethod.Token = handlerMethodInfo.MetadataToken;
|
|
handlerMethods.Add(handlerMethod);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
}
|
|
}
|
|
|
|
this.controllerConduit.UpdateHandlerMethodsForActivity(this.programId, scheduleTypeId, activity.QualifiedName, handlerMethods);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|