在当前调试器会话下启动新进程

时间:2014-09-23 07:35:32

标签: c# .net visual-studio debugging

我正在编写一个简单的测试应用程序(在这种情况下是一个WPF应用程序,如果它很重要),它试图从其中启动第二个应用程序(在这种情况下,是同一个应用程序的第二个实例,但应该真的很重要)。如果第一个程序在调试器内运行(在VS2013中,在我的情况下),我希望启动的辅助实例自动附加到第一个实例的调试会话。

现在,我正在使用Process.Start启动第二个进程,但是如果我尝试在其中调用Debugger.Launch,它将显示“选择调试器”窗口,其中当前会话是明确的从列表中排除。

我是否可以通过第一个进程在当前调试会话中显式启动第二个进程,或者(如果失败)获取当前调试会话的句柄并调用代码附加到进程?或者,或者,一种让第二个进程调用特定调试器会话以附加到它的方法?

(我熟悉VS中的各种宏或快捷方式,以便快速连接到第二个进程,我已经在使用它们了。只是想知道是否有办法让它自动发生。)

1 个答案:

答案 0 :(得分:2)

Visual Studio团队发布了一个Visual Studio扩展,允许自动将子进程附加到当前调试器:Introducing the Child Process Debugging Power Tool

Visual Studio 2013及更高版本的Gallery可以使用它。

我个人拿出以下代码手动将新进程附加到当前调试器:

using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Schedulers;
using EnvDTE;
using EnvDTE80;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;

namespace Test {
    public static class Debugging {
        private static _DTE Dte;
        private static readonly object DteLock = new object();
        private static bool Initialized;

        public static void AttachCurrentDebuggerToProcess(int processId) {
            lock (DteLock) {
                using (var sta = new StaTaskScheduler(numberOfThreads: 1)) {
                    Task.Factory.StartNew(() => {
                        if (System.Threading.Thread.CurrentThread.GetApartmentState() != ApartmentState.STA) throw new NotSupportedException("Thread should be in STA appartment state.");

                        // Register the IOleMessageFilter to handle any threading errors.
                        MessageFilter.Register();
                        if (!Initialized) {
                            using (var currentProcess = System.Diagnostics.Process.GetCurrentProcess())
                            using (var vsInstances = System.Diagnostics.Process.GetProcessesByName("devenv").AsDisposable()) {
                                foreach (var p in vsInstances.Enumerable) {
                                    _DTE dte;
                                    if (TryGetVSInstance(p.Id, out dte)) {
                                        //Will return null if target process doesn't have the same elevated rights as current process.
                                        Utils.Retry(() => {
                                            var debugger = dte?.Debugger;
                                            if (debugger != null) {
                                                foreach (Process2 process in debugger.DebuggedProcesses) {
                                                    if (process.ProcessID == currentProcess.Id) {
                                                        Dte = dte;
                                                        break;
                                                    }
                                                }
                                            }
                                        }, nbRetries: int.MaxValue, msInterval: 1000, retryOnlyOnExceptionTypes: typeof(COMException).InArray());
                                        if (Dte != null) break;
                                    }
                                }
                            }
                            Initialized = true;
                        }
                        if (Dte != null) {
                            foreach (Process2 process in Dte.Debugger.LocalProcesses) {
                                if (process.ProcessID == processId) {
                                    process.Attach2();
                                    Dte.Debugger.CurrentProcess = process;
                                }
                            }
                        }
                        //turn off the IOleMessageFilter.
                        MessageFilter.Revoke();
                    }, CancellationToken.None, TaskCreationOptions.None, sta).Wait();
                }
            }
        }

        private static bool TryGetVSInstance(int processId, out _DTE instance) {
            IntPtr numFetched = IntPtr.Zero;
            IRunningObjectTable runningObjectTable;
            IEnumMoniker monikerEnumerator;
            IMoniker[] monikers = new IMoniker[1];

            GetRunningObjectTable(0, out runningObjectTable);
            runningObjectTable.EnumRunning(out monikerEnumerator);
            monikerEnumerator.Reset();

            while (monikerEnumerator.Next(1, monikers, numFetched) == 0) {
                IBindCtx ctx;
                CreateBindCtx(0, out ctx);

                string runningObjectName;
                monikers[0].GetDisplayName(ctx, null, out runningObjectName);

                object runningObjectVal;
                runningObjectTable.GetObject(monikers[0], out runningObjectVal);

                if (runningObjectVal is _DTE && runningObjectName.StartsWith("!VisualStudio")) {
                    int currentProcessId = int.Parse(runningObjectName.Split(':')[1]);

                    if (currentProcessId == processId) {
                        instance = (_DTE)runningObjectVal;
                        return true;
                    }
                }
            }

            instance = null;
            return false;
        }

        [DllImport("ole32.dll")]
        private static extern void CreateBindCtx(int reserved, out IBindCtx ppbc);

        [DllImport("ole32.dll")]
        private static extern int GetRunningObjectTable(int reserved, out IRunningObjectTable prot);
    }
}

namespace System.Threading.Tasks.Schedulers {
    /// <summary>Provides a scheduler that uses STA threads. From ParallelExtensionsExtras https://code.msdn.microsoft.com/Samples-for-Parallel-b4b76364/sourcecode?fileId=44488&pathId=574018573</summary> 
    public sealed class StaTaskScheduler : TaskScheduler, IDisposable {
        /// <summary>Stores the queued tasks to be executed by our pool of STA threads.</summary> 
        private BlockingCollection<Task> _tasks;
        /// <summary>The STA threads used by the scheduler.</summary> 
        private readonly List<Thread> _threads;

        /// <summary>Initializes a new instance of the StaTaskScheduler class with the specified concurrency level.</summary> 
        /// <param name="numberOfThreads">The number of threads that should be created and used by this scheduler.</param> 
        public StaTaskScheduler(int numberOfThreads) {
            // Validate arguments 
            if (numberOfThreads < 1) throw new ArgumentOutOfRangeException(nameof(numberOfThreads));

            // Initialize the tasks collection 
            _tasks = new BlockingCollection<Task>();

            // Create the threads to be used by this scheduler 
            _threads = Enumerable.Range(0, numberOfThreads).Select(i =>
            {
                var thread = new Thread(() => {
                    // Continually get the next task and try to execute it. 
                    // This will continue until the scheduler is disposed and no more tasks remain. 
                    foreach (var t in _tasks.GetConsumingEnumerable()) {
                        TryExecuteTask(t);
                    }
                }) { IsBackground = true };
                thread.SetApartmentState(ApartmentState.STA);
                return thread;
            }).ToList();

            // Start all of the threads 
            _threads.ForEach(t => t.Start());
        }

        /// <summary>Queues a Task to be executed by this scheduler.</summary> 
        /// <param name="task">The task to be executed.</param> 
        protected override void QueueTask(Task task) {
            // Push it into the blocking collection of tasks 
            _tasks.Add(task);
        }

        /// <summary>Provides a list of the scheduled tasks for the debugger to consume.</summary> 
        /// <returns>An enumerable of all tasks currently scheduled.</returns> 
        protected override IEnumerable<Task> GetScheduledTasks() {
            // Serialize the contents of the blocking collection of tasks for the debugger 
            return _tasks.ToArray();
        }

        /// <summary>Determines whether a Task may be inlined.</summary> 
        /// <param name="task">The task to be executed.</param> 
        /// <param name="taskWasPreviouslyQueued">Whether the task was previously queued.</param> 
        /// <returns>true if the task was successfully inlined; otherwise, false.</returns> 
        protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) {
            // Try to inline if the current thread is STA 
            return
                Thread.CurrentThread.GetApartmentState() == ApartmentState.STA &&
                TryExecuteTask(task);
        }

        /// <summary>Gets the maximum concurrency level supported by this scheduler.</summary> 
        public override int MaximumConcurrencyLevel => this._threads.Count;

        /// <summary> 
        /// Cleans up the scheduler by indicating that no more tasks will be queued. 
        /// This method blocks until all threads successfully shutdown. 
        /// </summary> 
        public void Dispose() {
            if (_tasks != null) {
                // Indicate that no new tasks will be coming in 
                _tasks.CompleteAdding();

                // Wait for all threads to finish processing tasks 
                foreach (var thread in _threads) thread.Join();

                // Cleanup 
                _tasks.Dispose();
                _tasks = null;
            }
        }
    }
}

用法:

AttachCurrentDebuggerToProcess(1234); //where 1234 is your pid