如何在当前的Visual Studio扩展中调试使用Roslyn编译的代码?

4
我有一个使用Roslyn的Visual Studio扩展程序,可以获取当前打开解决方案中的项目,编译并运行其中的方法。该项目可以由程序员进行修改。
我已经成功地从当前的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实例中进行调试。

2个回答

1

看起来这是不可能的。

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

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

以下是供参考的代码:

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

0

您需要将调试器附加到当前运行的Visual Studio主机。要做到这一点,您需要:

  1. 获取DTE对象,
  2. DTE.Debugger.LocalProcesses中找到(当前)进程
  3. 附加调试器 - 示例
  4. 从已编译的程序集中运行方法

您可能想要一个单独的命令/按钮来执行此操作,不要只是切换到当前的构建配置。这就是其他所有人都这样做的方式(例如测试运行程序甚至Visual Studio)。此外,将已编译的Assembly加载到新的AppDomain中,否则它将永久存在

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


为什么您不推荐最后一种方法(使用Debugger.Launch())? - Florin-Constantin Ciubotariu
最后的 System.Diagnostics.Debugger.Launch() 是一个创造性的解决方案,这样我就可以指定何时开始调试。 - Tấn Nguyên

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