This repository has been archived by the owner on Feb 11, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added a plugin allowing to decompile the selected type with Reflector…
… or ILSpy
- Loading branch information
Showing
17 changed files
with
999 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
148 changes: 148 additions & 0 deletions
148
src/Plugins/Hawkeye.DecompilePlugin/BaseDecompilerController.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
using System; | ||
using System.Text; | ||
using System.Runtime.InteropServices; | ||
|
||
namespace Hawkeye.DecompilePlugin | ||
{ | ||
/// <summary> | ||
/// Allows an external program to remotely control a decompiler (Reflector or ILSpy). | ||
/// This class is adapted from RemoteController.cs found in | ||
/// .NET Reflector addins project (http://www.codeplex.com/reflectoraddins) | ||
/// </summary> | ||
/// <remarks> | ||
/// ILSpy supports a way of being remotely controlled very similar to Reflector's | ||
/// See https://github.com/icsharpcode/ILSpy/blob/master/doc/Command%20Line.txt. | ||
/// </remarks> | ||
internal abstract class BaseDecompilerController : IDecompilerController | ||
{ | ||
private IntPtr targetWindow = IntPtr.Zero; | ||
|
||
public BaseDecompilerController() | ||
{ | ||
WindowMessage = WM_COPYDATA; | ||
} | ||
|
||
#region Win32 Interop | ||
|
||
private const int WM_COPYDATA = 0x4A; | ||
|
||
private delegate bool EnumWindowsCallback(IntPtr hwnd, int lparam); | ||
|
||
[DllImport("user32.dll")] | ||
private static extern int EnumWindows(EnumWindowsCallback callback, int lparam); | ||
|
||
[DllImport("user32.dll")] | ||
private static extern int GetWindowText(IntPtr hWnd, StringBuilder title, int size); | ||
|
||
[DllImport("user32.dll")] | ||
private static extern bool SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, ref CopyDataStruct lParam); | ||
|
||
[StructLayout(LayoutKind.Sequential)] | ||
private struct CopyDataStruct | ||
{ | ||
public IntPtr Padding; | ||
public int Size; | ||
public IntPtr Buffer; | ||
|
||
public CopyDataStruct(IntPtr padding, int size, IntPtr buffer) | ||
{ | ||
Padding = padding; | ||
Size = size; | ||
Buffer = buffer; | ||
} | ||
} | ||
|
||
#endregion | ||
|
||
#region IDecompilerController Members | ||
|
||
/// <summary> | ||
/// Gets a value indicating whether a decompiler instance is running. | ||
/// </summary> | ||
/// <value> | ||
/// <c>true</c> if a running decompiler instance could be found; otherwise, <c>false</c>. | ||
/// </value> | ||
public abstract bool IsRunning { get; } | ||
|
||
/// <summary> | ||
/// Loads the type's assembly then selects the specified type declaration in the decompiler; | ||
/// </summary> | ||
/// <param name="type">The type to decompile.</param> | ||
/// <returns> | ||
/// <c>true</c> if the action succeeded; otherwise, <c>false</c>. | ||
/// </returns> | ||
/// <exception cref="System.NotImplementedException"></exception> | ||
public abstract bool GotoType(Type type); | ||
|
||
#endregion | ||
|
||
/// <summary> | ||
/// Gets the target window (each call re-enumerates to make sure the target exists). | ||
/// </summary> | ||
protected IntPtr TargetWindow | ||
{ | ||
get | ||
{ | ||
targetWindow = IntPtr.Zero; | ||
EnumWindows(new EnumWindowsCallback(EnumWindow), 0); | ||
return targetWindow; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Determines wether the specified window title matches the actual decompiler. | ||
/// </summary> | ||
/// <param name="title">The window title.</param> | ||
/// <returns><c>true</c> if matches; otherwise, <c>false</c>.</returns> | ||
protected abstract bool DoesWindowTitleMatches(string title); | ||
|
||
/// <summary> | ||
/// Gets or sets the window message used to communicate with the decompiler instance. | ||
/// </summary> | ||
/// <value> | ||
/// Default value is WM_COPYDATA. | ||
/// </value> | ||
protected virtual int WindowMessage { get; set; } | ||
|
||
protected bool Send(string message) | ||
{ | ||
targetWindow = IntPtr.Zero; | ||
|
||
// We can't use a simple FindWindow, because the decompiler window title | ||
// can vary: we must detect its window title starts with a known value; | ||
// not simply it is equal to a known value. See the EnumWindow method. | ||
EnumWindows(new EnumWindowsCallback(EnumWindow), 0); | ||
|
||
if (targetWindow != IntPtr.Zero) | ||
{ | ||
var chars = message.ToCharArray(); | ||
var data = new CopyDataStruct(); | ||
data.Padding = IntPtr.Zero; | ||
data.Size = chars.Length * 2; | ||
data.Buffer = Marshal.AllocHGlobal(data.Size); | ||
Marshal.Copy(chars, 0, data.Buffer, chars.Length); | ||
|
||
var result = SendMessage(targetWindow, WindowMessage, IntPtr.Zero, ref data); | ||
Marshal.FreeHGlobal(data.Buffer); | ||
|
||
return result; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
private bool EnumWindow(IntPtr handle, int lparam) | ||
{ | ||
var titleBuilder = new StringBuilder(256); | ||
GetWindowText(handle, titleBuilder, 256); | ||
|
||
var title = titleBuilder.ToString(); | ||
if (DoesWindowTitleMatches(title)) | ||
{ | ||
targetWindow = handle; | ||
return false; // No need to enumerate other windows | ||
} | ||
else return true; // Try again | ||
} | ||
} | ||
} |
120 changes: 120 additions & 0 deletions
120
src/Plugins/Hawkeye.DecompilePlugin/BaseDecompilerPluginCore.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Text; | ||
using Hawkeye.Extensibility; | ||
using Hawkeye.Logging; | ||
using System.Windows.Forms; | ||
|
||
namespace Hawkeye.DecompilePlugin | ||
{ | ||
internal abstract class BaseDecompilerPluginCore : BaseCommandPlugin | ||
{ | ||
private ILogService log = null; | ||
private IWindowInfo windowInfo = null; | ||
private IDecompilerController controller = null; | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="ReflectorPluginCore"/> class. | ||
/// </summary> | ||
/// <param name="descriptor">The descriptor.</param> | ||
public BaseDecompilerPluginCore(IPluginDescriptor descriptor) : | ||
base(descriptor) { } | ||
|
||
public override string Label | ||
{ | ||
get { return "&Decompile"; } | ||
} | ||
|
||
protected abstract IDecompilerController CreateDecompilerController(); | ||
|
||
protected virtual string DecompilerNotAvailable | ||
{ | ||
get | ||
{ | ||
return | ||
@"A running instance of the decompiler could not be found. | ||
Hawkeye can not show you the source code for the selected item. | ||
Make sure it is running"; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Called when the plugin has just been initialized. | ||
/// </summary> | ||
protected override void OnInitialized() | ||
{ | ||
base.OnInitialized(); | ||
|
||
controller = CreateDecompilerController(); | ||
|
||
log = base.Host.GetLogger<ReflectorPluginCore>(); | ||
base.Host.CurrentWindowInfoChanged += (s, e) => | ||
{ | ||
windowInfo = base.Host.CurrentWindowInfo; | ||
base.RaiseCanExecuteChanged(this); | ||
}; | ||
|
||
windowInfo = base.Host.CurrentWindowInfo; | ||
base.RaiseCanExecuteChanged(this); | ||
|
||
log.Info(string.Format("'{0}' was initialized.", base.Descriptor.Name)); | ||
} | ||
|
||
/// <summary> | ||
/// Determines whether this plugin command can be executed. | ||
/// </summary> | ||
/// <returns> | ||
/// <c>true</c> if the command can be executed; otherwise, <c>false</c>. | ||
/// </returns> | ||
protected override bool CanExecuteCore() | ||
{ | ||
return | ||
windowInfo != null && | ||
windowInfo.ControlInfo != null && | ||
windowInfo.ControlInfo.Control != null; | ||
} | ||
|
||
/// <summary> | ||
/// Executes this plugin command. | ||
/// </summary> | ||
protected override void ExecuteCore() | ||
{ | ||
if (!CanExecuteCore()) return; | ||
|
||
var savedCursor = Cursor.Current; | ||
Cursor.Current = Cursors.WaitCursor; | ||
try | ||
{ | ||
OpenInDecompiler(); | ||
} | ||
finally | ||
{ | ||
Cursor.Current = savedCursor; | ||
} | ||
} | ||
|
||
private void OpenInDecompiler() | ||
{ | ||
if (!controller.IsRunning) | ||
{ | ||
MessageBox.Show(DecompilerNotAvailable, | ||
"Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning); | ||
return; | ||
} | ||
|
||
var control = windowInfo.ControlInfo.Control; | ||
var type = control.GetType(); | ||
|
||
// Remark: the logic here is really simplified comapred with the Hawkeye 1 Reflector facility: | ||
// In Hawkeye 1, we tried to get Reflector to point to the exact property, member, event or method | ||
// that was curently selected. | ||
// Here, we only open the current type in Reflector. | ||
// Indeed this makes more sense, because often times, in the previous version, what was selected | ||
// was some member inherited from Control and Reflector was not loading the really inspected | ||
// control but some member of the System.Windows.Forms.Control class. | ||
|
||
controller.GotoType(type); | ||
} | ||
} | ||
} |
Oops, something went wrong.