如何在当前 Visual Studio 主机内的 Visual Studio 扩展中调试使用 Roslyn 编译的代码?
How to debug code compiled with Roslyn in a Visual Studio extension inside current Visual Studio host?
我有一个 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 实例中进行调试。
您必须将调试器附加到当前 运行ning visual studio 主机。为此,您需要:
- Get hold of the DTE object,
- 在DTE.Debugger.LocalProcesses
中找到(当前)进程
- Attach the debugger - example
- 运行 来自已编译程序集的方法
您可能需要一个单独的 command/button,不要只打开当前的构建配置。其他人都是这样做的(例如测试 运行 人甚至 Visual Studio)。此外,将已编译的 Assembly
加载到新的 AppDomain
中,否则它将停留在 for ever.
附近
还有更多 "creative" 解决方案,将 System.Diagnostics.Debugger.Launch()
调用注入到您要 运行 的方法中(在调用 [= 之前修改正确的 Compilation.SyntaxTrees
14=]) - 但说真的,不要这样做。
看来这实际上是不可能的。
当前 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");
}
我有一个 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 实例中进行调试。
您必须将调试器附加到当前 运行ning visual studio 主机。为此,您需要:
- Get hold of the DTE object,
- 在DTE.Debugger.LocalProcesses 中找到(当前)进程
- Attach the debugger - example
- 运行 来自已编译程序集的方法
您可能需要一个单独的 command/button,不要只打开当前的构建配置。其他人都是这样做的(例如测试 运行 人甚至 Visual Studio)。此外,将已编译的 Assembly
加载到新的 AppDomain
中,否则它将停留在 for ever.
还有更多 "creative" 解决方案,将 System.Diagnostics.Debugger.Launch()
调用注入到您要 运行 的方法中(在调用 [= 之前修改正确的 Compilation.SyntaxTrees
14=]) - 但说真的,不要这样做。
看来这实际上是不可能的。
当前 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");
}