当前位置: 首页 > news >正文

使用 Visual Studio 调试多进程的程序

当你的编写的是一个多进程的程序的时候,调试起来可能会比较困难,因为 Visual Studio 默认只会把你当前设置的启动项目的启动调试。

本文将介绍几种用 Visual Studio 调试多进程程序的方法,然后给出每种方法的适用条件和优劣。


本文内容

      • Visual Studio 多启动项目(推荐)
      • Microsoft Child Process Debugging Power Tool 插件(推荐)
        • 安装和配置插件
        • 配置项目启动选项
          • 在旧格式的项目中开启混合模式
          • 在新格式的项目中开启混合模式
        • 例子源码和效果
      • 在代码中编写“附加调试器”
      • 总结
        • 参考资料

Visual Studio 多启动项目(推荐)

在 Visual Studio 的解决方案上点击右键,属性。在公共属性节点中选择启动项目。

在这里,你可以给多个项目都设置成启动项目,就像下图这样:

设置多启动项目

当然,这些项目都必须要是能够启动的才行(不一定是可执行程序)。

此方案的好处是 Visual Studio 原生支持。但此方案的使用必须满足两个前提:

  1. 要调试的多个进程必须是不同的项目编译出来的;
  2. 这些项目之间的启动顺序不能有明显的依赖关系(所以你可能需要修改你的代码使得这两个进程之间可以互相唤起)。

Microsoft Child Process Debugging Power Tool 插件(推荐)

安装和配置插件

请先安装 Microsoft Child Process Debugging Power Tool 插件。

安装插件后启动 Visual Studio,可以在 Debug -> Other Debugging Targets 中找到 Child Process Debugging Settings。

打开 Child Process Debugging Settings

然后你可以按照下图的设置开启此项目的子进程调试:

设置子进程调试

配置项目启动选项

但是,子进程要能够调试,你还必须开启混合模式调试。

在旧格式的项目中开启混合模式

旧格式指的是 Visual Studio 2015 及以前版本的 Visual Studio 使用的项目格式。目前 Visual Studio 2017 和 2019 对这种格式的支持还是很完善的。

在项目上右键 -> 属性 -> Debug,这时你可以在底部的调试引擎中发现 Enable native code debugging 选项,开启它你就开启了本机代码调试,于是也就可以使用混合模式调试程序。

在旧格式中开启本机代码调试

在新格式的项目中开启混合模式

如果你在你项目属性的 Debug 标签下没有找到上面那个选项,那么有可能你的项目格式是新格式的。

新格式中没有开启本机代码调试的选项

这个时候,你需要在 lauchsettings.json 文件中设置。这个文件在你项目的 Properties 文件夹下。

如果你没有找到这个文件,那么随便在上图那个框框中写点什么(比如在启动参数一栏中写 吕毅是逗比),然后保存。我们就能得到一个 lauchsettings.json 文件。

launchsettings.json 文件

打开它,然后删掉刚刚的逗比行为,添加 "nativeDebugging": true。这时,你的 lauchsettings.json 文件影响像下面这样:

{
  "profiles": {
    "Walterlv.Debugging": {
      "commandName": "Project",
      "nativeDebugging": true
    }
  }
}

这时你就可以开启本机代码调试了。当然,新的项目格式支持设置多个这样的启动项,于是你可以分别配置本机和非本机的多种配置:

{
  "profiles": {
    "Walterlv.Debugging": {
      "commandName": "Project"
    },
    "本机调试": {
      "commandName": "Project",
      "nativeDebugging": true
    }
  }
}

现在,你可以选择你项目的启动方式了,其中一个是开启了本机代码调试的方式。

选择项目的启动方式

关于这些配置的更多博客,你可以阅读:VisualStudio 使用多个环境进行调试 - 林德熙。

现在,你只需要开始调试你的程序,那么你程序中启动的新的子进程都将可以自动加入调试。

例子源码和效果

现在,我们拿下面这段代码作为例子来尝试子进程的调试。下面的代码中,if 中的代码会运行在子进程中,而 else 中的代码会运行在主进程中。

using System;
using System.Diagnostics;
using System.Linq;

namespace Walterlv.Debugging
{
    class Program
    {
        static void Main(string[] args)
        {
            if (args.Any())
            {
                Console.WriteLine("Walterlv child application");
                Console.WriteLine(string.Join(Environment.NewLine, args));
                Console.ReadLine();
            }
            else
            {
                Console.WriteLine("Walterlv main application");
                var process = new Process
                {
                    StartInfo = new ProcessStartInfo(Process.GetCurrentProcess().MainModule.FileName, "--child"),
                };
                process.Start();
                process.WaitForExit();
            }
        }
    }
}

我们在 ifelse 中都打上断点。正常情况下运行,只有 else 中的代码可以进断点;而如果以上子进程调试配置正确,那么两边你都可以进入断点(如下图)。

子进程进入了调试断点

值得注意的是,只要启动了本机代码调试,就不能在程序暂停之后修改代码了(像平时调试纯托管代码那样)。

在代码中编写“附加调试器”

调用 Debugger.Launch() 可以启动一个调试器来调试此进程。于是我们可以在我们被调试的程序中写下如下代码:

#if DEBUG
    if (!Debugger.IsAttached)
    {
        Debugger.Launch();
    }
#endif

仅在 DEBUG 条件下,如果当前没有附加任何调试器,那么就启动一个新的调试器来调试它。

当存在以上代码时,运行会弹出一个对话框,用于选择调试器。

选择调试器

这里选择的调试器有个不太方便的地方,如果调试器已经在使用,那么就不能选择。对于我们目前的场景,我们的主进程已经在调试了,所以子进程选择调试器的时候不能再选择主进程调试所用的 Visual Studio 了,而只能选择一个新的 Visual Studio;这一点很不方便。

对于此方法,我的建议是平常不要在团队项目中使用(这会让团队中的其他人不方便)。但是由于代码简单不需要配置,所以临时使用的话还是非常建议的。

总结

综上,虽然我给出了 4 种不同的方法,但实际上没有任何一种方法能够像我们调试单个原生托管程序那样方便。每一种方法都各有优劣,一般情况下建议你使用我标注了“推荐”的方法;不过也建议针对不同的情况采用不同的方案。

  1. 简单的个人项目,希望快速开始多进程/子进程调试
    • 使用附加调试器
  2. 你有多个项目组成的多进程,并且这些进程恰好可以互相唤起,它们之间的启动顺序不影响父子进程的组成
    • 使用 Visual Studio 的多启动项目
  3. 你只有单个项目组成的多进程,或者多个进程之间依赖于启动顺序来组成父子进程
    • 安装插件 Microsoft Child Process Debugging Power Tool

参考资料

  • Azure DevOps Blog - Introducing the Child Process Debugging Power Tool
  • Microsoft Child Process Debugging Power Tool - Visual Studio Marketplace
  • attach a process to current visual studio debugger silently using command line …
  • How to get DTE from Visual Studio process ID? – Kirill Osenkov
  • How to start Visual Studio programmatically – Kirill Osenkov
  • EnvDTE Namespace - Microsoft Docs
  • c# - Using the EnvDTE assembly - Stack Overflow

我的博客会首发于 https://walterlv.com/,而 CSDN 和博客园仅从其中摘选发布,而且一旦发布了就不再更新。

如果在博客看到有任何不懂的内容,欢迎交流。我搭建了 dotnet 职业技术学院 欢迎大家加入。

知识共享许可协议

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:https://walterlv.blog.csdn.net/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系。

相关文章:

  • 如何更精准地设置 C# / .NET Core 项目的输出路径?(包括添加和删除各种前后缀)
  • .NET 使用 JustAssembly 比较两个不同版本程序集的 API 变化
  • Windows 上的应用程序在运行期间可以给自己改名(可以做 OTA 自我更新)
  • 为 WPF 程序添加 Windows 跳转列表的支持
  • 在 Windows 系统上降低 UAC 权限运行程序(从管理员权限降权到普通用户权限)
  • 专栏素材
  • Visual Studio 如何能够不进行编译就调试 .NET/C# 项目(用于解决大项目编译缓慢的问题)
  • 仅反射加载(ReflectionOnlyLoadFrom)的 .NET 程序集,如何反射获取它的 Attribute 元数据呢?
  • 全局或为单独的项目添加自定义的 NuGet 源
  • 电脑总是意外从睡眠状态唤醒,可以找出原因然后解决
  • 我收集的各种公有 NuGet 源
  • 制作一个极简的 .NET 客户端应用自安装或自更新程序
  • 在 MSBuild 编译项目时阻止输出所有的警告信息
  • 编写 MSBuild 内联编译任务(Task)用于获取当前编译环境下的所有编译目标(Target)
  • 如何在 csproj 中用 C# 代码写一个内联的编译任务 Task
  • 【391天】每日项目总结系列128(2018.03.03)
  • Android交互
  • CAP理论的例子讲解
  • CentOS7简单部署NFS
  • create-react-app做的留言板
  • CSS盒模型深入
  • CSS相对定位
  • iOS 系统授权开发
  • Java 11 发布计划来了,已确定 3个 新特性!!
  • JS专题之继承
  • KMP算法及优化
  • 从零开始在ubuntu上搭建node开发环境
  • 利用阿里云 OSS 搭建私有 Docker 仓库
  • 日剧·日综资源集合(建议收藏)
  • 实现简单的正则表达式引擎
  • 微信支付JSAPI,实测!终极方案
  • 鱼骨图 - 如何绘制?
  • ​MPV,汽车产品里一个特殊品类的进化过程
  • # Swust 12th acm 邀请赛# [ K ] 三角形判定 [题解]
  • (01)ORB-SLAM2源码无死角解析-(56) 闭环线程→计算Sim3:理论推导(1)求解s,t
  • (1)(1.9) MSP (version 4.2)
  • (16)UiBot:智能化软件机器人(以头歌抓取课程数据为例)
  • (delphi11最新学习资料) Object Pascal 学习笔记---第2章第五节(日期和时间)
  • (html转换)StringEscapeUtils类的转义与反转义方法
  • (二开)Flink 修改源码拓展 SQL 语法
  • (五)c52学习之旅-静态数码管
  • (转)memcache、redis缓存
  • .net 8 发布了,试下微软最近强推的MAUI
  • .NET Core 通过 Ef Core 操作 Mysql
  • .NET CORE使用Redis分布式锁续命(续期)问题
  • .NET Core中Emit的使用
  • .NET MVC第三章、三种传值方式
  • .NET/C# 获取一个正在运行的进程的命令行参数
  • .net之微信企业号开发(一) 所使用的环境与工具以及准备工作
  • [20150629]简单的加密连接.txt
  • [android] 手机卫士黑名单功能(ListView优化)
  • [Android]通过PhoneLookup读取所有电话号码
  • [BJDCTF2020]The mystery of ip1
  • [c++] 单例模式 + cyberrt TimingWheel 单例分析
  • [COI2007] Sabor