在当前调试器会话下启动新进程 [英] Start new process under the current debugger session

查看:136
本文介绍了在当前调试器会话下启动新进程的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



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



有没有办法可以从第一个进程在当前调试会话中显式启动第二个进程,否则)获取当前调试会话的句柄并调用代码附加到进程?或者,替代地,获得第二个进程的方法来调用特定的调试器会话来附加它



(我熟悉各种VS中的宏或快捷键快速附加到第二个进程,我已经在使用它们,只是想知道是否有办法自动发生)。

解决方案

Visual Studio团队发布了一个Visual Studio扩展,允许自动将子进程附加到当前的调试器中:介绍子进程调试电源工具



它可以在图库上找到Visual Studio 2013及以上版本。



我亲自提出了以下代码附加一个新进程手动到当前调试器:

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

命名空间Test {
public static class Debugging {
private static _DTE Dte;
private static只读对象DteLock = new object();
private static bool初始化;

public static void AttachCurrentDebuggerToProcess(int processId){
lock(DteLock){
using(var sta = new StaTaskScheduler(numberOfThreads:1)){
任务。 Factory.StartNew(()=> {
if(System.Threading.Thread.CurrentThread.GetApartmentState()!= ApartmentState.STA)抛出新的NotSupportedException(Thread应该在STA的公寓状态);

//注册IOleMessageFilter来处理任何线程错误
MessageFilter.Register();
if(!Initialized){
using(var currentProcess = System.Diagnostics .Process.GetCurrentProcess())
使用(var vsInstances = System.Diagnostics.Process.GetProcessesByName(devenv)。AsDisposable()){
foreach(var p in vsInstances.Enumerable){
_DTE dte;
if(TryGetVSInstance(p.Id,out dte)){
//如果目标进程与当前进程的提升权限不相同,则返回null。
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(Dte.Debugger.LocalProcesses中的Process2进程){
if(process.ProcessID == processId){
process.Attach2();
Dte.Debugger.CurrentProcess = process;
}
}
}
//关闭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);

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

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

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

instance = null;
返回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);
}
}

命名空间System.Threading.Tasks.Schedulers {
///< summary>提供使用STA线程的调度程序。来自ParallelExtensionsExtras https://code.msdn.microsoft.com/Samples-for-Parallel-b4b76364/sourcecode?fileId=44488&pathId=574018573</summary>
public sealed class StaTaskScheduler:TaskScheduler,IDisposable {
///< summary>存储要由STA线程池执行的排队任务。< / summary>
private BlockingCollection< Task> _任务;
///< summary>调度程序使用的STA线程。< / summary>
private readonly列表< Thread> _threads;

///< summary>以指定的并发级别初始化StaTaskScheduler类的新实例。< / summary>
///< param name =numberOfThreads>应该由此调度程序创建和使用的线程数。< / param>
public StaTaskScheduler(int numberOfThreads){
//验证参数
if(numberOfThreads< 1)throw new ArgumentOutOfRangeException(nameof(numberOfThreads));

//初始化任务集
_tasks = new BlockingCollection< Task>();

//创建此调度程序使用的线程
_threads = Enumerable.Range(0,numberOfThreads).Select(i =>
{
var thread = new Thread(()=> {
//持续获取下一个任务并尝试执行它
//这将继续,直到调度程序被处理,没有更多的任务保留
foreach(_tasks.GetConsumingEnumerable()中的变量){
TryExecuteTask(t);
}
}){IsBackground = true};
thread.SetApartmentState(ApartmentState。 STA);
return thread;
})。ToList();

//开始所有线程
_threads.ForEach(t => t.Start());
}

///< summary>将要由此调度程序执行的任务排队。< / summary>
///< param name =task>要执行的任务。< / param>
protected override void QueueTask(Task task){
//将其推送到阻塞任务集合
_tasks.Add(task);
}

///< summary>提供调试器使用的调度任务的列表。< / summary>
///< returns>可列举当前安排的所有任务。< / returns>
protected override IEnumerable< Task> GetScheduledTasks(){
//序列化调试器任务的阻塞集合的内容
return _tasks.ToArray();
}

///< summary>确定是否可以内联任务。< / summary>
///< param name =task>要执行的任务。< / param>
///< param name =taskWasPreviouslyQueued>任务先前已排队。< / param>
///< returns>如果任务成功内联,则为true;否则为false。< / returns>
protected override bool TryExecuteTaskInline(Task task,bool taskWasPreviouslyQueued){
//如果当前线程是STA
返回
Thread.CurrentThread.GetApartmentState()== ApartmentState .STA&&
TryExecuteTask(task);
}

///< summary>获取此调度程序支持的最大并发级。< / summary>
public override int MaximumConcurrencyLevel => this._threads.Count;

///< summary>
///通过指示没有更多任务排队来清除调度程序。
///这个方法阻塞,直到所有线程成功关闭。
///< / summary>
public void Dispose(){
if(_tasks!= null){
//指示
_tasks.CompleteAdding()中不会有新任务。

//等待所有线程完成处理任务
foreach(_threads中的var线程)thread.Join();

//清理
_tasks.Dispose();
_tasks = null;
}
}
}
}

  AttachCurrentDebuggerToProcess(1234); //其中1234是您的pid 


I'm writing a simple test application (a WPF application in this case, if it matters), which attempts to launch a second application from within it (in this case, a second instance of the same app, but that should really matter). If the first program is running inside a debugger (in VS2013, in my case), I want the secondary instance launched to be automatically attached to the first instance's debug session.

Right now, I'm using Process.Start to launch the second process, but if I try calling Debugger.Launch within it, it will show the "choose a debugger" window where the current session is explicitly excluded from the list.

Is there a way that I can, from the first process, explicitly launch a second process in the current debugging session, or (failing that) get a handle to the current debugging session and call code to attach to a process? Or, alternately, a way to get the second process to call a specific debugger session to attach to it?

(I am familiar with various macros or shortcuts within VS to quickly attach to the second process, and I'm using them already. Just wondering if there's a way to have it happen automatically).

解决方案

The Visual Studio team has released a Visual Studio extension that allows automatically attaching child processes to the current debugger: Introducing the Child Process Debugging Power Tool.

It is available on the Gallery for Visual Studio 2013 and above.

I have personally come up with the following code to attach a new process manually to the current debugger:

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;
            }
        }
    }
}

Usage:

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

这篇关于在当前调试器会话下启动新进程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆