Attach Process At Runtime For Debugging In Visual Studio

With Integration tests a common scenario comes into picture. Integration tests always depend on system components e.g. Database, Services, etc. If the Integration test is failing unexpectedly then it might require to debug the test including that component which are usually part of the same VS solution. Recently I came across this SO question. Question seemed legit because while debugging a test, attaching a process again and again to debug becomes really annoying.

To make dependent components like services, I usually invoke the process to launch e.g. ConsoleApplication project in same solution. Its like simply add a helper class to invoke the process.

  1. internal class ProcessInvoker   
  2. {  
  3.     /// <summary>  
  4.     /// Invokes the host process for test service  
  5.     /// </summary>  
  6.     public static void InvokeDummyService()  
  7.     {  
  8.         var path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);  
  9.   
  10.   
  11.         ProcessStartInfo info = newProcessStartInfo(Path.Combine(path, "DummyService.exe"));  
  12.   
  13.   
  14.         info.UseShellExecute = true;  
  15.         info.WorkingDirectory = path;  
  16.   
  17.   
  18.         var process = Process.Start(info);  
  19.         // This is what would attach the process to current VS debugger  
  20.         AttachDebugger.ToProcess(process.Id);  
  21.     }  
  22.   
  23.   
  24.     /// <summary>  
  25.     /// Kills the process of service host  
  26.     /// </summary>  
  27.     public static void KillDummyService()   
  28.     {  
  29.         Process.GetProcessesByName("DummyService").ToList().ForEach(x => x.Kill());  
  30.     }  
  31. }  
viewrawProcessInvoker.cs hosted with ❤ by GitHub

Now in the TestInitialize and TestCleanup methods (I’m using NUnit in this sample code so it would be TestFixtureSetup and TextFixtureTearDown methods) I would start the process and kill the respective process. Here the Integration test is a sample from one of my Github project WCFDynamicProxy. In this Test I’ll be invoking a Dummy service instance hosted in a Console application. Then completion of test just kill the process.
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Reflection;  
  5. using System.Text;  
  6. using System.Threading.Tasks;  
  7. using SampleService.Contracts;  
  8. using WcfDynamicProxy.Tests.Helper;  
  9. using NUnit.Framework;  
  10.   
  11.   
  12. namespace WcfDynamicProxy.Tests.Tests   
  13. {  
  14.     /// <summary>  
  15.     /// Implements the tests for CustomClientProxyFactory class. The tests have falvour of integration  
  16.     /// as we're testing it on dummy service client.  
  17.     /// </summary>  
  18.     [TestFixture]  
  19.     public class CustomClientProxyFactoryTest  
  20.     {  
  21.         /// <summary>  
  22.         /// Setup required before the tests of the fixture will run.  
  23.         /// </summary>  
  24.         [TestFixtureSetUp]  
  25.         public void Init()   
  26.         {  
  27.             ServiceHostProcessInvoker.InvokeDummyService();  
  28.         }  
  29.   
  30.   
  31.         /// <summary>  
  32.         /// Tear down to perform clean when the execution is finished.  
  33.         /// </summary>  
  34.         [TestFixtureTearDown]  
  35.         public void TearDown()   
  36.         {  
  37.             ServiceHostProcessInvoker.KillDummyService();  
  38.         }  
  39.   
  40.   
  41.         /// <summary>  
  42.         /// Case when the method GetDefaultErrorLoggingInterfaceProxy was invoked to get the real   
  43.         /// proxy of a test service, succeeds.  
  44.         /// </summary>  
  45.         [Test]  
  46.         public void GetDefaultWCFClientProxy_RequestingProxyClientGeneration_Succeeds()  
  47.         {  
  48.             var proxy = DynamicProxyWrapperFactory.GetProxyInstanceByInterface < ITestService > ("TestService");  
  49.   
  50.   
  51.             var result = proxy.SayHello();  
  52.   
  53.   
  54.             Assert.IsNotNull(result, "The result was null.");  
  55.             Assert.IsNotEmpty(result, "The result was empty.");  
  56.         }  
  57.     }  
  58. }  
viewrawWCFDynamicProxyTest.cs hosted with ❤ by GitHub

Now, comes the part to attach this process for debugging. This is pretty tricky one. I found a VS Addin from Visual studio team to attach child process automatically to current debugger but it seems it only works with "F5" debugging.

Then I found this SO post and it really worked amazingly. I have done a little customization to get the process Id as argument:
  1. using System;  
  2. using System.Runtime.InteropServices;  
  3. using System.Threading;  
  4. using System.Threading.Tasks;  
  5. using System.Linq;  
  6. using System.Collections.Generic;  
  7. using EnvDTE;  
  8.   
  9.   
  10. namespace Common   
  11. {  
  12.     [ComImport, Guid("00000016-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]  
  13.     public interface IOleMessageFilter  
  14.     {  
  15.         [PreserveSig]  
  16.         int HandleInComingCall(intdwCallType, IntPtrhTaskCaller, intdwTickCount, IntPtrlpInterfaceInfo);  
  17.   
  18.   
  19.         [PreserveSig]  
  20.         int RetryRejectedCall(IntPtrhTaskCallee, intdwTickCount, intdwRejectType);  
  21.   
  22.   
  23.         [PreserveSig]  
  24.         int MessagePending(IntPtrhTaskCallee, intdwTickCount, intdwPendingType);  
  25.     }  
  26.   
  27.   
  28.     public class MessageFilter: IOleMessageFilter  
  29.     {  
  30.         private const int Handled = 0, RetryAllowed = 2, Retry = 99, Cancel = -1, WaitAndDispatch = 2;  
  31.   
  32.   
  33.         int IOleMessageFilter.HandleInComingCall(intdwCallType, IntPtrhTaskCaller, intdwTickCount, IntPtrlpInterfaceInfo)  
  34.         {  
  35.             return Handled;  
  36.         }  
  37.   
  38.   
  39.         int IOleMessageFilter.RetryRejectedCall(IntPtrhTaskCallee, intdwTickCount, intdwRejectType)  
  40.         {  
  41.             returndwRejectType == RetryAllowed ? Retry : Cancel;  
  42.         }  
  43.   
  44.   
  45.         int IOleMessageFilter.MessagePending(IntPtrhTaskCallee, intdwTickCount, intdwPendingType)  
  46.         {  
  47.             returnWaitAndDispatch;  
  48.         }  
  49.   
  50.   
  51.         public static void Register()  
  52.         {  
  53.             CoRegisterMessageFilter(newMessageFilter());  
  54.         }  
  55.   
  56.   
  57.         public static void Revoke()   
  58.         {  
  59.             CoRegisterMessageFilter(null);  
  60.         }  
  61.   
  62.   
  63.         private static void CoRegisterMessageFilter(IOleMessageFilternewFilter)  
  64.         {  
  65.             IOleMessageFilteroldFilter;  
  66.             CoRegisterMessageFilter(newFilter, out oldFilter);  
  67.         }  
  68.   
  69.   
  70.         [DllImport("Ole32.dll")]  
  71.         private static extern intCoRegisterMessageFilter(IOleMessageFilternewFilter, outIOleMessageFilteroldFilter);  
  72.     }  
  73.   
  74.   
  75.     public static class AttachDebugger   
  76.     {  
  77.         public static void ToProcess(intprocessId)   
  78.         {  
  79.             MessageFilter.Register();  
  80.             var process = GetProcess(processId);  
  81.             if (process != null)  
  82.             {  
  83.                 process.Attach();  
  84.                 Console.WriteLine("Attached to {0}", process.Name);  
  85.             }  
  86.             MessageFilter.Revoke();  
  87.         }  
  88.         private static Process GetProcess(intprocessID)  
  89.         {  
  90.             var dte = (DTE) Marshal.GetActiveObject("VisualStudio.DTE.12.0");  
  91.             var processes = dte.Debugger.LocalProcesses.OfType < Process > ();  
  92.             returnprocesses.SingleOrDefault(x => x.ProcessID == processID);  
  93.         }  
  94.     }  
  95. }  
viewrawAttachDebugger.cs hosted with ❤ by GitHub

Note:

You need to add the VS automation library EnvDTE from AddReference ->Extensions. Also the VS version might be different. I was using Visual Studio 2013 so it’s ‘VisualStudio.DTE.12.0’. Change it as per your need.

Now in the ProcessInvoker class add the call to AttachDebugger utility class after the process launch statement.
When I launched a test for debugging it worked like charm. The process was invoked, attached to VS and was able to debug other process code. You can verify it from menu –> Debug, Attach to process, then Locate the process expected to be attached.

attach

Checkout the working code here specially WcfDynamicProxy.Tests in the solution.

 


Similar Articles