【问题标题】:How to debug code compiled with Roslyn in a Visual Studio extension inside current Visual Studio host?如何在当前 Visual Studio 主机内的 Visual Studio 扩展中调试使用 Roslyn 编译的代码?
【发布时间】:2026-01-22 17:00:02
【问题描述】:

我有一个 Visual Studio 扩展,它使用 Roslyn 在当前打开的解决方案中获取一个项目,编译它并从中运行方法。项目可以由程序员修改。

我已经从当前的 VisualStudioWorkspace 成功地在 Visual Studio 扩展中编译了一个项目。

    private static Assembly CompileAndLoad(Compilation compilation)
    {
        using (MemoryStream dllStream = new MemoryStream())
        using (MemoryStream pdbStream = new MemoryStream())
        {
            EmitResult result = compilation.Emit(dllStream, pdbStream);

            if (!result.Success)
            {
                IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
                    diagnostic.IsWarningAsError ||
                    diagnostic.Severity == DiagnosticSeverity.Error);

                string failuresException = "Failed to compile code generation project : \r\n";

                foreach (Diagnostic diagnostic in failures)
                {
                    failuresException += $"{diagnostic.Id} : {diagnostic.GetMessage()}\r\n";
                }

                throw new Exception(failuresException);
            }
            else
            {

                dllStream.Seek(0, SeekOrigin.Begin);
                return AppDomain.CurrentDomain.Load(dllStream.ToArray(), pdbStream.ToArray());

            }
        }
    }

然后我可以在当前域中加载程序集,获取它的类型并调用方法。

问题是如果加载的解决方案的当前配置是调试,我需要允许程序员放置断点。

我需要在当前 Visual Studio 主机中从扩展运行一些代码,并允许在当前 Visual Studio 实例中对其进行调试。

【问题讨论】:

    标签: c# debugging roslyn visual-studio-extensions


    【解决方案1】:

    看来这实际上是不可能的。

    当前的 Visual Studio 实例无法自行调试。

    我尝试使用 roslyn 创建控制台应用程序,将其附加到调试器,然后从中运行生成代码。但是 VisualStudioWorkspace 仅在 VisualStudio 内部可用(不可序列化且不可通过 DTE com 接口使用)。所以剩下的唯一解决方案就是使用 MBBuildWorkspace。由于它没有与 Visual Studio 工作区相同的行为,因此我放弃了该项目。

    这是我的代码供进一步参考:

    Process vsProcess = Process.GetCurrentProcess();
    
    string solutionPath = CurrentWorkspace.CurrentSolution.FilePath;
    
    SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText($@"
                using System;
                using System.Threading.Tasks;
    
                namespace CodeGenApplication
                {{
                    public class Program 
                    {{
                        public static void Main(string[] args) 
                        {{
    
                        Console.ReadLine();
                        int vsProcessId = Int32.Parse(args[0]);
                        CodeGenApp.Test(""{solutionPath.Replace(@"\", @"\\")}"", ""{projectName}"", ""{_codeGenProjectName}"");
                        Console.ReadLine();
                        }}
                    }}
                }}");
    
    string assemblyName = Path.GetRandomFileName();
    
    Project codeGenProject = CurrentWorkspace.CurrentSolution.Projects.Where(x => x.Name == _codeGenProjectName).FirstOrDefault();
    
    List<MetadataReference> references = codeGenProject.MetadataReferences.ToList();
    
    CSharpCompilation compilation = CSharpCompilation.Create(
    
        assemblyName,
        syntaxTrees: new[] { syntaxTree },
        references: references,
        options: new CSharpCompilationOptions(OutputKind.ConsoleApplication));
    
    // Emit assembly to streams.
    EmitResult result = compilation.Emit("CodeGenApplication.exe", "CodeGenApplication.pdb");
    
    if (!result.Success)
    {
        IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
            diagnostic.IsWarningAsError ||
            diagnostic.Severity == DiagnosticSeverity.Error);
    }
    else
    {
        Process codeGenProcess = new Process();
        codeGenProcess.StartInfo.FileName = "CodeGenApplication.exe";
        codeGenProcess.StartInfo.Arguments = vsProcess.Id.ToString();
        codeGenProcess.StartInfo.UseShellExecute = false;
        codeGenProcess.StartInfo.CreateNoWindow = true;
        codeGenProcess.StartInfo.LoadUserProfile = true;
        codeGenProcess.StartInfo.RedirectStandardError = true;
        codeGenProcess.StartInfo.RedirectStandardInput = true;
        codeGenProcess.StartInfo.RedirectStandardOutput = false;
        codeGenProcess.Start();
        foreach (EnvDTE.Process dteProcess in _dte.Debugger.LocalProcesses)
        {
            if (dteProcess.ProcessID == codeGenProcess.Id)
            {
                dteProcess.Attach();
            }
        }
        codeGenProcess.StandardInput.WriteLine("Start");
    }
    

    【讨论】:

      【解决方案2】:

      您必须将调试器附加到当前运行的 Visual Studio 主机。为此,您需要:

      1. Get hold of the DTE object,
      2. DTE.Debugger.LocalProcesses中找到(当前)进程
      3. Attach 调试器 - example
      4. 从编译的程序集中运行方法

      您可能需要一个单独的命令/按钮,不要只打开当前的构建配置。这就是其他人的做法(例如测试运行程序甚至 Visual Studio)。另外,将编译后的Assembly 加载到一个新的AppDomain 中,否则它会保留for ever

      还有一个更“创造性”的解决方案,将System.Diagnostics.Debugger.Launch() 调用注入您将要运行的方法(在调用Emit 之前修改正确的Compilation.SyntaxTrees) - 但说真的,不要这样做。

      【讨论】:

      • 为什么不推荐最后一种方法(使用Debugger.Launch())?
      • 最后一个System.Diagnostics.Debugger.Launch() 是创造性的解决方案,所以我可以指定什么时候开始调试