如何在Visual Studio中调试时运行自定义代码并附加到调试器

3

VS Code似乎有一个名为Launch.json的文件,可以自定义调试应用程序时发生的情况。那么对于Visual Studio,我们该如何做呢?我的目标是在没有使用VS Docker工具的情况下运行docker-compose,然后在容器启动后,告诉VS附加到容器上。

我的问题并不特定于该情况,它可能是另一种自定义Run with debugger场景。理想情况下,我可以运行一些命令来构建项目,运行项目,然后告诉VS附加到它并将该逻辑与VS关联起来。


你能从VIad的建议中获得有用的信息吗?或者你想在项目属性中使用Pre/Post Build运行某些命令吗? - Jack Zhai
@JackZhai-MSFT,我仍在努力尝试,但由于Docker需要运行多个命令,所以这很具有挑战性。1 Docker compose up,2 运行远程调试器,3 以某种方式附加到正在Docker实例内部运行的远程调试器。 目前,我正在尝试指向PowerShell脚本以获得更大的灵活性。 - Swimburger
我仍然不知道如何通过脚本使VS附加到远程进程。 - Swimburger
@JackZhai-MSFT,我在这里发布了我想出的解决方案。虽然我真的很希望有一种更容易控制VS实例的方法,但这样的PowerShell模块将非常棒。 - Swimburger
3个回答

3

根据Vlad和Jack的答案,我想出了以下解决方案。 为了在按下“运行”按钮时运行自己的代码,我设置了一个带有自定义启动设置的空白命令行项目。

{
  "profiles": {
    "Build": {
      "commandName": "Executable",
      "executablePath": "powershell",
      "commandLineArgs": ".\\DebugRun.ps1",
      "workingDirectory": "."
    }
  }
}

每当我按下“运行”按钮,它都会使用PowerShell运行“DebugRun.ps1”脚本。以下是我放置在“DebugRun.ps1”中的内容。
docker-compose -f "./docker-compose.debug.yml" --no-ansi up -d --force-recreate --build

Start-Sleep -Seconds 5

$appId = ((docker ps --filter "ancestor=employeemapapp:debug")[1] -split " ")[0]
$apiId = ((docker ps --filter "ancestor=employeemapapi:debug")[1] -split " ")[0]

$appIp = (docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $appId)
$apiIp = (docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $apiId)

docker exec -d $appId C:\\remote_debugger\\x64\\msvsmon.exe /noauth /anyuser /silent /nostatus /noclrwarn /nosecuritywarn /nofirewallwarn /nowowwarn /timeout:214748364
docker exec -d $apiId C:\\remote_debugger\\x64\\msvsmon.exe /noauth /anyuser /silent /nostatus /noclrwarn /nosecuritywarn /nofirewallwarn /nowowwarn /timeout:214748364

$appTarget = $appId + ":4022"
$apiTarget = $apiId + ":4022"

#Parameters
## 1: Solution Name is used to run the code only to the VS instance that has the Solution open
## 2: Transportation method for remote debugger
## 3: Target is the hostname/IP/... to the target where remote debugging is running
## 4: The process name you want to attach tos
./RemoteDebugAttach.exe "EmployeeMap.sln" "Remote (no authentication)" $appTarget "dotnet.exe"
./RemoteDebugAttach.exe "EmployeeMap.sln" "Remote (no authentication)" $apiTarget "dotnet.exe"

Write-Host "Api:" $apiIp
Write-Host "App:" $appIp
$apiUrl = "http://$apiIp/api/employees"
$appUrl = "http://$appIp"

start $apiUrl
start $appUrl

Read-Host "Press any key to quit"

./RemoteDebugDetach.exe EmployeeMap.sln "dotnet.exe"

docker exec -d $appId C:\\remote_debugger\\x64\\utils\\KillProcess.exe msvsmon.exe
docker exec -d $apiId C:\\remote_debugger\\x64\\utils\\KillProcess.exe msvsmon.exe

docker-compose -f "./docker-compose.debug.yml" --no-ansi down 

这个脚本执行以下操作:
  • 通过docker-compose启动我的docker容器
  • 获取容器ID、IP等信息
  • 在容器内部启动远程调试器实例
  • 使用自定义可执行文件(RemoteDebugAttach)附加到容器内的dotnet.exe进程
  • 打开浏览器访问从容器服务的网站
  • 等待按键以关闭基础设施
  • 使用自定义可执行文件分离进程
  • 杀死docker容器上的远程调试器
  • 关闭容器
这些自定义的可执行文件来自另一个解决方案,我只是构建了一些命令行应用程序。 我使用了这个源代码中的代码来获取机器上运行的所有VS实例。 并将其与此代码组合以进行附加:
class Program
{
    [STAThread]
    static void Main(string[] args)
    {
        string solutionName = Ask(args, 0, "Solution name?");
        string transportName = Ask(args, 1, "Transport name?");
        string target = Ask(args, 2, "Target machine?");
        string processName = Ask(args, 3, "Process Name?");


        var instances = Msdev.GetIDEInstances(true);
        var dte = (DTE2)instances.Find(d => d.Solution.FullName.EndsWith(solutionName, StringComparison.InvariantCultureIgnoreCase));
        var debugger = dte.Debugger as Debugger2;
        var transports = debugger.Transports;
        Transport transport = null;
        foreach(Transport loopTransport in transports)
        {
            if(loopTransport.Name.Equals(transportName, StringComparison.InvariantCultureIgnoreCase)) // "Remote (no authentication)")
            {
                transport = loopTransport;
                break;
            }
        }

        Processes processes = debugger.GetProcesses(transport, target); // "172.24.50.15:4022");
        foreach(Process process in processes)
        {
            if(process.Name.EndsWith(processName, StringComparison.InvariantCultureIgnoreCase))
            {
                process.Attach();
            }
        }
    }

    static string Ask(string[] args, int index, string question)
    {
        if(args.Length <= index)
        {
            Console.WriteLine(question);
            return Console.ReadLine();
        }

        return args[index];
    }
}

以下是“分离”操作的步骤:
class Program
{
    [STAThread]
    static void Main(string[] args)
    {
        string solutionName = Ask(args, 0, "Solution name");
        string processName = Ask(args, 1, "Process Name?");

        var instances = Msdev.GetIDEInstances(true);

        var dte = (DTE2)instances.Find(d => d.Solution.FullName.EndsWith(solutionName, StringComparison.InvariantCultureIgnoreCase));
        var debugger = dte.Debugger as Debugger2;

        Processes processes = debugger.DebuggedProcesses;
        foreach (Process2 process in processes)
        {
            if (process.Name.EndsWith(processName, StringComparison.InvariantCultureIgnoreCase))
            {
                process.Detach(false);
            }
        }
    }

    static string Ask(string[] args, int index, string question)
    {
        if (args.Length <= index)
        {
            Console.WriteLine(question);
            return Console.ReadLine();
        }

        return args[index];
    }
}

我更希望这段代码使用PowerShell,因为这样复制到其他项目并进行编辑会更容易。但是,尽管使用了反射来应用某些代码到COM对象中,但我仍然无法正确执行代码。希望这对想要在VS中构建自定义流程的人有所帮助。感谢Vlad和Jack。

这是一个相当强硬的解决方案来完成这个。 - tacos_tacos_tacos
@tacos_tacos_tacos 我同意。这很痛苦,但根据您的开发团队使用情况可能是值得的。我希望它更简单。 - Swimburger

3

在解决方案资源管理器中右键单击项目,选择“属性”。 选择“调试”节点。填写“命令”、“命令参数”(如果需要),并将“附加”设置为true。


你怎么做到这一点?在“Command”位置已经有一个“$(TargetPath)”内容了。它代表了生成的可执行文件。请问在启动可执行文件之前如何运行自定义命令? - yO_

1

嗨Jack,谢谢。我已经准备好了一个脚本,它可以启动我的Docker容器,并在容器上启动远程调试器服务器。我看到你提到的SO帖子只适用于本地进程,但这可能足以成为我解决问题的起点 :) - Swimburger
我可以通过 [System.Runtime.InteropServices.Marshal]::GetActiveObject("VisualStudio.DTE.15.0") 获取 VS 实例,但我无法将其转换为 DTE2,这意味着我无法访问远程调试脚本的 Debugger2.Transports。我更喜欢使用 PowerShell,但我认为我必须编写一个控制台应用程序来解决它。 - Swimburger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接