From 1f75ad9264fb17fd8d699b34ba7923eac8b91d41 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 15 May 2026 08:12:41 +0200 Subject: [PATCH] =?UTF-8?q?Pdb2Xml=20command=20=E2=80=94=20DEBUG=20+=20Win?= =?UTF-8?q?dows-only,=20matches=20WPF=20gating?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WPF ships Pdb2XmlCommand behind `#if DEBUG && WINDOWS` because Microsoft.DiaSymReader uses native COM interop. Avalonia parity uses the same gate, with the package references conditional on the build host being Windows + Debug, and the consuming command file `#if DEBUG && WINDOWS`. Assisted-by: Claude:claude-opus-4-7:Claude Code --- ILSpy/Commands/FileCommands.cs | 8 +-- ILSpy/Commands/Pdb2XmlCommand.cs | 115 +++++++++++++++++++++++++++++++ ILSpy/ILSpy.csproj | 15 ++++ 3 files changed, 132 insertions(+), 6 deletions(-) create mode 100644 ILSpy/Commands/Pdb2XmlCommand.cs diff --git a/ILSpy/Commands/FileCommands.cs b/ILSpy/Commands/FileCommands.cs index d7e64420d..f683dcf6c 100644 --- a/ILSpy/Commands/FileCommands.cs +++ b/ILSpy/Commands/FileCommands.cs @@ -129,12 +129,8 @@ namespace ILSpy.Commands // DEBUG-only DisassembleAllCommand also moved to DecompileAllCommand.cs to keep all // three parallel-decompile/disassemble stress-test commands in one place. - [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.DEBUGDumpPDBAsXML), MenuCategory = nameof(Resources.Open), MenuOrder = 2.6)] - [Shared] - sealed class Pdb2XmlCommand : SimpleCommand - { - public override void Execute(object? parameter) => NotImplementedDialog.Show(Resources.DEBUGDumpPDBAsXML); - } + // DEBUG-only Pdb2XmlCommand moved to its own file with `#if DEBUG && WINDOWS` gating. + // On non-Windows or non-Debug builds the entry simply isn't compiled. [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources._RemoveAssembliesWithLoadErrors), MenuCategory = nameof(Resources.Remove), MenuOrder = 2.6)] [Shared] diff --git a/ILSpy/Commands/Pdb2XmlCommand.cs b/ILSpy/Commands/Pdb2XmlCommand.cs new file mode 100644 index 000000000..090c2ae58 --- /dev/null +++ b/ILSpy/Commands/Pdb2XmlCommand.cs @@ -0,0 +1,115 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#if DEBUG && WINDOWS +using System.Collections.Generic; +using System.Composition; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +using ICSharpCode.ILSpy.Properties; + +using ILSpy.AssemblyTree; +using ILSpy.Docking; +using ILSpy.TextView; +using ILSpy.TreeNodes; + +using Microsoft.DiaSymReader.Tools; + +namespace ILSpy.Commands +{ + /// + /// DEBUG-only main-menu entry: Dump every selected assembly's PDB as XML in the active + /// decompiler tab via . Windows-only because + /// Microsoft.DiaSymReader uses native COM interop. WPF gates this command + /// identically (#if DEBUG && WINDOWS). + /// + [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.DEBUGDumpPDBAsXML), MenuCategory = nameof(Resources.Open), MenuOrder = 2.7)] + [Shared] + sealed class Pdb2XmlCommand : SimpleCommand + { + readonly AssemblyTreeModel assemblyTreeModel; + readonly DockWorkspace dockWorkspace; + + [ImportingConstructor] + public Pdb2XmlCommand(AssemblyTreeModel assemblyTreeModel, DockWorkspace dockWorkspace) + { + this.assemblyTreeModel = assemblyTreeModel; + this.dockWorkspace = dockWorkspace; + } + + public override bool CanExecute(object? parameter) + { + var selected = assemblyTreeModel.SelectedItems; + return selected?.Count > 0 + && selected.All(n => n is AssemblyTreeNode asm && !asm.LoadedAssembly.HasLoadError); + } + + public override void Execute(object? parameter) + { + var nodes = assemblyTreeModel.SelectedItems.OfType().ToArray(); + if (nodes.Length == 0) + return; + _ = ExecuteAsync(nodes); + } + + async Task ExecuteAsync(AssemblyTreeNode[] nodes) + { + try + { + var options = PdbToXmlOptions.IncludeEmbeddedSources + | PdbToXmlOptions.IncludeMethodSpans + | PdbToXmlOptions.IncludeTokens; + var output = await dockWorkspace.RunWithCancellation(token => Task.Run(() => { + var output = new AvaloniaEditTextOutput { Title = "PDB as XML", SyntaxExtensionOverride = ".xml" }; + var writer = new TextOutputWriter(output); + foreach (var node in nodes) + { + var pdbFileName = Path.ChangeExtension(node.LoadedAssembly.FileName, ".pdb"); + if (!File.Exists(pdbFileName)) + continue; + using var pdbStream = File.OpenRead(pdbFileName); + using var peStream = File.OpenRead(node.LoadedAssembly.FileName); + PdbToXmlConverter.ToXml(writer, pdbStream, peStream, options); + } + return output; + }, token), "Dumping PDB as XML…"); + dockWorkspace.ShowText(output); + } + catch (System.OperationCanceledException) { } + } + } + + /// + /// Adapter that lets 's TextWriter API write into our + /// sink. + /// + internal sealed class TextOutputWriter(ICSharpCode.Decompiler.ITextOutput output) : System.IO.TextWriter + { + public override System.Text.Encoding Encoding => System.Text.Encoding.UTF8; + public override void Write(char value) => output.Write(value); + public override void Write(string? value) + { + if (value != null) + output.Write(value); + } + public override void WriteLine() => output.WriteLine(); + } +} +#endif diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index 76b1878ee..6dd2031b2 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -77,6 +77,18 @@ + + + + + + +