简体   繁体   中英

How to debug code compiled with Roslyn in a Visual Studio extension inside current Visual Studio host?

I have a Visual Studio extensions that use Roslyn to get a project in current opened solution, compile it and run methods from it. The project can be modified by the programmer.

I have successfully compiled a project in a Visual Studio extension from the current VisualStudioWorkspace.

    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());

            }
        }
    }

Then I can load the assembly in current domain, get it's types and invoke methods.

The problem is that I need to allow the programmer to put breakpoints if the current configuration of the loaded solution is debug.

I need to run some code in current Visual Studio Host from an extension and allow it to be debugged in the current Visual Studio instance.

Seems like this is actually impossible.

The current visual studio instance cannot debug itself.

I've tried creating a Console Application with roslyn, attaching it to the debugger and then run the generation code from it. But the VisualStudioWorkspace is only available inside VisualStudio (Not serializable and not avalaible througt DTE com interface). So the only solution left was using MBBuildWorkspace. Since it does not have that same behavior as Visual studio workspace, I've abandoned the project.

Here's my code for further references :

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

You have to attach the debugger to the currently running visual studio host. To do that you need to:

  1. Get hold of the DTE object ,
  2. find the (current) process in DTE.Debugger.LocalProcesses
  3. Attach the debugger - example
  4. run method from the compiled assembly

You'd probably want a separate command/button for this, don't just switch on the current build configuration. That's how everybody else does it (eg test runners and even Visual Studio). Also, load the compiled Assembly in a new AppDomain , otherwise it'll stick around for ever .

There's also the more "creative" solution of injecting a System.Diagnostics.Debugger.Launch() call to the method you're about to run (modify the correct Compilation.SyntaxTrees before calling Emit ) - but seriously, don't do this.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM