.NET Decompiler with support for PDB generation, ReadyToRun, Metadata (&more) - cross-platform!
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

344 lines
13 KiB

using System;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices;
using System.ComponentModel.Design;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Shell;
using System.IO;
using Mono.Cecil;
using System.Collections.Generic;
namespace ICSharpCode.ILSpy.AddIn
{
/// <summary>
/// This is the class that implements the package exposed by this assembly.
///
/// The minimum requirement for a class to be considered a valid package for Visual Studio
/// is to implement the IVsPackage interface and register itself with the shell.
/// This package uses the helper classes defined inside the Managed Package Framework (MPF)
/// to do it: it derives from the Package class that provides the implementation of the
/// IVsPackage interface and uses the registration attributes defined in the framework to
/// register itself and its components with the shell.
/// </summary>
// This attribute tells the PkgDef creation utility (CreatePkgDef.exe) that this class is
// a package.
[PackageRegistration(UseManagedResourcesOnly = true)]
// This attribute is used to register the information needed to show this package
// in the Help/About dialog of Visual Studio.
[InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
// This attribute is needed to let the shell know that this package exposes some menus.
[ProvideMenuResource("Menus.ctmenu", 1)]
[Guid(GuidList.guidILSpyAddInPkgString)]
[ProvideAutoLoad(VSConstants.UICONTEXT.SolutionExistsAndFullyLoaded_string)]
public sealed class ILSpyAddInPackage : Package
{
/// <summary>
/// Default constructor of the package.
/// Inside this method you can place any initialization code that does not require
/// any Visual Studio service because at this point the package object is created but
/// not sited yet inside Visual Studio environment. The place to do all the other
/// initialization is the Initialize method.
/// </summary>
public ILSpyAddInPackage()
{
Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering constructor for: {0}", this.ToString()));
}
/////////////////////////////////////////////////////////////////////////////
// Overridden Package Implementation
#region Package Members
/// <summary>
/// Initialization of the package; this method is called right after the package is sited, so this is the place
/// where you can put all the initialization code that rely on services provided by VisualStudio.
/// </summary>
protected override void Initialize()
{
Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this.ToString()));
base.Initialize();
// Add our command handlers for menu (commands must exist in the .vsct file)
OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
if (null != mcs) {
// Create the command for the References context menu.
CommandID menuCommandID = new CommandID(GuidList.guidILSpyAddInCmdSet, (int)PkgCmdIDList.cmdidOpenReferenceInILSpy);
MenuCommand menuItem = new MenuCommand(OpenReferenceInILSpyCallback, menuCommandID);
mcs.AddCommand(menuItem);
// Create the command for the Project context menu, to open the output assembly.
CommandID menuCommandID2 = new CommandID(GuidList.guidILSpyAddInCmdSet, (int)PkgCmdIDList.cmdidOpenProjectOutputInILSpy);
MenuCommand menuItem2 = new MenuCommand(OpenProjectOutputInILSpyCallback, menuCommandID2);
mcs.AddCommand(menuItem2);
// Create the command for the code window context menu.
CommandID menuCommandID3 = new CommandID(GuidList.guidILSpyAddInCmdSet, (int)PkgCmdIDList.cmdidOpenCodeItemInILSpy);
OleMenuCommand menuItem3 = new OleMenuCommand(OpenCodeItemInILSpyCallback, menuCommandID3);
menuItem3.BeforeQueryStatus += OpenCodeItemInILSpyCallback_BeforeQueryStatus;
mcs.AddCommand(menuItem3);
// Create the command for the Tools menu item.
CommandID menuCommandID4 = new CommandID(GuidList.guidILSpyAddInCmdSet, (int)PkgCmdIDList.cmdidOpenILSpy);
MenuCommand menuItem4 = new MenuCommand(OpenILSpyCallback, menuCommandID4);
mcs.AddCommand(menuItem4);
}
}
#endregion
/// <summary>
/// This function is the callback used to execute a command when the a menu item is clicked.
/// See the Initialize method to see how the menu item is associated to this function using
/// the OleMenuCommandService service and the MenuCommand class.
/// </summary>
private void OpenReferenceInILSpyCallback(object sender, EventArgs e)
{
var explorer = ((EnvDTE80.DTE2)GetGlobalService(typeof(EnvDTE.DTE))).ToolWindows.SolutionExplorer;
var items = (object[])explorer.SelectedItems;
foreach (EnvDTE.UIHierarchyItem item in items) {
var reference = GetReference(item.Object);
if (reference.Project != null) {
OpenProjectInILSpy(reference.Project);
continue;
}
string path = null;
if (!string.IsNullOrEmpty(reference.PublicKeyToken)) {
var token = Utils.HexStringToBytes(reference.PublicKeyToken);
path = GacInterop.FindAssemblyInNetGac(new AssemblyNameReference(reference.Name, reference.Version) { PublicKeyToken = token });
}
if (path == null)
path = reference.Path;
OpenAssemblyInILSpy(path);
}
}
class ReferenceInfo
{
public string Name { get; set; }
public string PublicKeyToken { get; set; }
public string Path { get; set; }
public Version Version { get; set; }
public EnvDTE.Project Project { get; set; }
internal static ReferenceInfo FromFullName(string fullName)
{
string[] parts = fullName.Split(new[] { ", " }, StringSplitOptions.RemoveEmptyEntries);
return new ReferenceInfo {
Name = parts[0],
Version = new Version(parts[1].Substring("Version=".Length)),
PublicKeyToken = parts[3].Substring("PublicKeyToken=".Length)
};
}
}
private ReferenceInfo GetReference(object o)
{
dynamic obj = o;
string referenceType = o.GetType().FullName;
// C++
if (referenceType.StartsWith("Microsoft.VisualStudio.PlatformUI", StringComparison.Ordinal)) {
return new ReferenceInfo { Path = obj.Name };
}
// F#
if (referenceType.StartsWith("Microsoft.VisualStudio.FSharp", StringComparison.Ordinal)) {
obj = obj.Object;
}
if (referenceType == "Microsoft.VisualStudio.ProjectSystem.VS.Implementation.Package.Automation.OAReferenceItem") {
var projectItem = (EnvDTE.ProjectItem)o;
foreach (dynamic p in projectItem.Properties) {
try {
switch (p.Name) {
case "FusionName":
if (!string.IsNullOrWhiteSpace(p.Value))
return ReferenceInfo.FromFullName(p.Value);
break;
case "Project":
var fileName = p.Object.Name;
EnvDTE.Projects projects = ((EnvDTE80.DTE2)GetGlobalService(typeof(EnvDTE.DTE))).Solution.Projects;
foreach (EnvDTE.Project proj in projects) {
if (Path.GetFileName(proj.FileName) == fileName) {
return new ReferenceInfo {
Project = proj,
Name = fileName
};
}
}
break;
case "Path":
var values = GetProperties(projectItem.Properties, "Type", "Name", "Version", "Path");
if (values[0] == "Package" && values[1] != null && values[2] != null && values[3] != null) {
return new ReferenceInfo {
Name = values[1],
Path = $"{values[3]}\\{values[1]}.{values[2]}.nupkg"
};
}
break;
}
} catch {
continue;
}
}
}
// C# and VB
return new ReferenceInfo {
Name = obj.Identity,
PublicKeyToken = obj.PublicKeyToken,
Path = obj.Path,
Version = new Version(obj.Version)
};
}
string[] GetProperties(EnvDTE.Properties properties, params string[] names)
{
string[] values = new string[names.Length];
foreach (dynamic p in properties) {
try {
for (int i = 0; i < names.Length; i++) {
if (names[i] == p.Name) {
values[i] = p.Value;
break;
}
}
} catch {
continue;
}
}
return values;
}
private void OpenProjectOutputInILSpyCallback(object sender, EventArgs e)
{
var explorer = ((EnvDTE80.DTE2)GetGlobalService(typeof(EnvDTE.DTE))).ToolWindows.SolutionExplorer;
var items = (object[])explorer.SelectedItems;
foreach (EnvDTE.UIHierarchyItem item in items) {
EnvDTE.Project project = (EnvDTE.Project)item.Object;
OpenProjectInILSpy(project);
}
}
// Called when the menu is popped, determines whether "Open code in ILSpy" option is available.
private void OpenCodeItemInILSpyCallback_BeforeQueryStatus(object sender, EventArgs e)
{
OleMenuCommand menuItem = sender as OleMenuCommand;
if (menuItem != null) {
var document = (EnvDTE.Document)(((EnvDTE80.DTE2)GetGlobalService(typeof(EnvDTE.DTE))).ActiveDocument);
menuItem.Enabled =
(document != null) &&
(document.ProjectItem != null) &&
(document.ProjectItem.ContainingProject != null) &&
(document.ProjectItem.ContainingProject.ConfigurationManager != null) &&
!string.IsNullOrEmpty(document.ProjectItem.ContainingProject.FileName);
}
}
private void OpenCodeItemInILSpyCallback(object sender, EventArgs e)
{
var document = (EnvDTE.Document)(((EnvDTE80.DTE2)GetGlobalService(typeof(EnvDTE.DTE))).ActiveDocument);
var selection = (EnvDTE.TextPoint)((EnvDTE.TextSelection)document.Selection).ActivePoint;
// Search code elements in desired order, working from innermost to outermost.
// Should eventually find something, and if not we'll just open the assembly itself.
var codeElement = GetSelectedCodeElement(selection,
EnvDTE.vsCMElement.vsCMElementFunction,
EnvDTE.vsCMElement.vsCMElementEvent,
EnvDTE.vsCMElement.vsCMElementVariable, // There is no vsCMElementField, fields are just variables outside of function scope.
EnvDTE.vsCMElement.vsCMElementProperty,
EnvDTE.vsCMElement.vsCMElementDelegate,
EnvDTE.vsCMElement.vsCMElementEnum,
EnvDTE.vsCMElement.vsCMElementInterface,
EnvDTE.vsCMElement.vsCMElementStruct,
EnvDTE.vsCMElement.vsCMElementClass,
EnvDTE.vsCMElement.vsCMElementNamespace);
if (codeElement != null) {
OpenCodeItemInILSpy(codeElement);
}
else {
OpenProjectInILSpy(document.ProjectItem.ContainingProject);
}
}
private EnvDTE.CodeElement GetSelectedCodeElement(EnvDTE.TextPoint selection, params EnvDTE.vsCMElement[] elementTypes)
{
foreach (var elementType in elementTypes) {
var codeElement = selection.CodeElement[elementType];
if (codeElement != null) {
return codeElement;
}
}
return null;
}
private void OpenCodeItemInILSpy(EnvDTE.CodeElement codeElement)
{
string codeElementKey = CodeElementXmlDocKeyProvider.GetKey(codeElement);
OpenProjectInILSpy(codeElement.ProjectItem.ContainingProject, "/navigateTo:" + codeElementKey);
}
private void OpenILSpyCallback(object sender, EventArgs e)
{
Process.Start(GetILSpyPath());
}
private string GetILSpyPath()
{
var basePath = Path.GetDirectoryName(typeof(ILSpyAddInPackage).Assembly.Location);
return Path.Combine(basePath, "ILSpy.exe");
}
private void OpenProjectInILSpy(EnvDTE.Project project, params string[] arguments)
{
EnvDTE.Configuration config = project.ConfigurationManager.ActiveConfiguration;
string projectPath = Path.GetDirectoryName(project.FileName);
string outputPath = config.Properties.Item("OutputPath").Value.ToString();
string assemblyFileName = project.Properties.Item("OutputFileName").Value.ToString();
OpenAssemblyInILSpy(Path.Combine(projectPath, outputPath, assemblyFileName), arguments);
}
private void OpenAssemblyInILSpy(string assemblyFileName, params string[] arguments)
{
if (!File.Exists(assemblyFileName)) {
ShowMessage("Could not find assembly '{0}', please ensure the project and all references were built correctly!", assemblyFileName);
return;
}
string commandLineArguments = Utils.ArgumentArrayToCommandLine(assemblyFileName);
if (arguments != null) {
commandLineArguments = string.Concat(commandLineArguments, " ", Utils.ArgumentArrayToCommandLine(arguments));
}
Process.Start(GetILSpyPath(), commandLineArguments);
}
private void ShowMessage(string format, params object[] items)
{
IVsUIShell uiShell = (IVsUIShell)GetService(typeof(SVsUIShell));
Guid clsid = Guid.Empty;
int result;
Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(
uiShell.ShowMessageBox(
0,
ref clsid,
"ILSpy.AddIn",
string.Format(CultureInfo.CurrentCulture, format, items),
string.Empty,
0,
OLEMSGBUTTON.OLEMSGBUTTON_OK,
OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST,
OLEMSGICON.OLEMSGICON_INFO,
0, // false
out result
)
);
}
}
}