ExternalToolManager and BizHawkExternalToolattribute

Add those 2 classes in order to help handling of external tools
- Also fix value setting in watches constructor
- Bugfix in loading externaltools (they loaded multiple times)
- Add new version of Hello World external tool
Whitespaces noise in RamWatch.cs... :s The code hasn't changed
This commit is contained in:
Hathor86 2015-12-04 14:10:04 +01:00
parent 2535207a40
commit a7074c6341
11 changed files with 317 additions and 78 deletions

View File

@ -0,0 +1,92 @@
using System;
namespace BizHawk.Client.ApiHawk
{
/// <summary>
/// This class hold logic interraction for the ExternalToolAttribute
/// This attribute helps BizHawk to handle ExternalTools
/// </summary>
[AttributeUsage(AttributeTargets.Assembly)]
public class BizHawkExternalToolAttribute : Attribute
{
#region Fields
private string _Name;
private string _Description;
private string _IconResourceName;
#endregion
#region cTor(s)
/// <summary>
/// Initialize a new instance of <see cref="BizHawkExternalToolAttribute"/>
/// </summary>
/// <param name="name">Tool's name</param>
/// <param name="description">Small description about the tool itself</param>
/// <param name="iconResourceName">Icon embedded resource name</param>
public BizHawkExternalToolAttribute(string name, string description, string iconResourceName)
{
_Name = name;
_Description = description;
_IconResourceName = iconResourceName;
}
/// <summary>
/// Initialize a new instance of <see cref="BizHawkExternalToolAttribute"/>
/// </summary>
/// <param name="name">Tool's name</param>
/// <param name="description">Small description about the tool itself</param>
public BizHawkExternalToolAttribute(string name, string description)
: this(name, description, string.Empty)
{ }
/// <summary>
/// Initialize a new instance of <see cref="BizHawkExternalToolAttribute"/>
/// </summary>
/// <param name="name">Tool's name</param>
public BizHawkExternalToolAttribute(string name)
:this(name, string.Empty, string.Empty)
{}
#endregion
#region Properties
/// <summary>
/// Gets tool's friendly name
/// </summary>
public string Name
{
get
{
return _Name;
}
}
/// <summary>
/// Gets tool's descriptino
/// </summary>
public string Description
{
get
{
return _Description;
}
}
/// <summary>
/// Get the name of the embedded resource icon
/// </summary>
/// <remarks>Don't forget to set compile => Embedded reource to the icon file in your project</remarks>
public string IconResourceName
{
get
{
return _IconResourceName;
}
}
#endregion
}
}

View File

@ -21,6 +21,8 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>x86</PlatformTarget>
<DocumentationFile>
</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@ -29,6 +31,8 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>
</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
@ -38,6 +42,8 @@
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<DocumentationFile>
</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>..\output\dll\</OutputPath>
@ -47,10 +53,13 @@
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<DocumentationFile>
</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
@ -59,6 +68,8 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Attributes\ExternalToolAttribute.cs" />
<Compile Include="Classes\ExternalToolManager.cs" />
<Compile Include="Interfaces\IExternalToolForm.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
@ -76,10 +87,7 @@
<Name>BizHawk.Emulation.Common</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="Attributes\" />
<Folder Include="Classes\" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
<None Include="Resources\ApiClassDiagram.cd" />
</ItemGroup>

View File

@ -0,0 +1,166 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows.Forms;
using BizHawk.Client.Common;
using BizHawk.Client.EmuHawk;
namespace BizHawk.Client.ApiHawk
{
/// <summary>
/// This static class handle all ExternalTools
/// </summary>
public static class ExternalToolManager
{
#region Fields
private static FileSystemWatcher directoryMonitor = new FileSystemWatcher(Global.Config.PathEntries["Global", "External Tools"].Path, "*.dll");
private static List<ToolStripMenuItem> menuItems = new List<ToolStripMenuItem>();
#endregion
#region cTor(s)
/// <summary>
/// Initilization
/// </summary>
static ExternalToolManager()
{
directoryMonitor.IncludeSubdirectories = false;
directoryMonitor.Created += DirectoryMonitor_Created;
BuildToolStrip();
}
#endregion
#region Methods
/// <summary>
/// Build the toolstrip menu
/// </summary>
private static void BuildToolStrip()
{
menuItems.Clear();
if (Directory.Exists(directoryMonitor.Path))
{
DirectoryInfo dInfo = new DirectoryInfo(directoryMonitor.Path);
foreach (FileInfo fi in dInfo.GetFiles("*.dll"))
{
menuItems.Add(GenerateToolTipFromFileName(fi.FullName));
}
}
}
/// <summary>
/// Generate a <see cref="ToolStripMenuItem"/> from an
/// external tool dll.
/// The assembly must have <see cref="BizHawkExternalToolAttribute"/> in its
/// assembly attributes
/// </summary>
/// <param name="fileName">File that will be reflected</param>
/// <returns>A new <see cref="ToolStripMenuItem"/>; assembly path can be found in the Tag property</returns>
/// <remarks>For the moment, you could only load a dll that have a form (which implements <see cref="IExternalToolForm"/>)</remarks>
private static ToolStripMenuItem GenerateToolTipFromFileName(string fileName)
{
Type customFormType;
Assembly externalToolFile;
ToolStripMenuItem item = null;
try
{
externalToolFile = Assembly.LoadFrom(fileName);
object[] attributes = externalToolFile.GetCustomAttributes(typeof(BizHawkExternalToolAttribute), false);
if (attributes != null && attributes.Count() == 1)
{
BizHawkExternalToolAttribute attribute = (BizHawkExternalToolAttribute)attributes[0];
item = new ToolStripMenuItem(attribute.Name);
item.ToolTipText = attribute.Description;
if (attribute.IconResourceName != string.Empty)
{
Stream s = externalToolFile.GetManifestResourceStream(string.Format("{0}.{1}", externalToolFile.GetName().Name, attribute.IconResourceName));
if (s != null)
{
item.Image = new Bitmap(s);
}
}
customFormType = externalToolFile.GetTypes().FirstOrDefault<Type>(t => t != null && t.FullName == "BizHawk.Client.EmuHawk.CustomMainForm");
if (customFormType == null)
{
item.ToolTipText = "Does not have a CustomMainForm";
item.Enabled = false;
}
item.Tag = fileName;
}
else
{
item = new ToolStripMenuItem(externalToolFile.GetName().Name);
item.ToolTipText = "BizHawkExternalTool attribute hasn't been found";
item.Enabled = false;
}
}
catch (BadImageFormatException)
{
item = new ToolStripMenuItem(fileName);
item.ToolTipText = "This is not an assembly";
item.Enabled = false;
}
#if DEBUG //I added special debug stuff to get additionnal informations. Don(t think it can be usefull for released versions
catch (ReflectionTypeLoadException ex)
{
foreach (Exception e in ex.LoaderExceptions)
{
Debug.WriteLine(e.Message);
}
item.ToolTipText = "Something goes wrong while trying to load";
item.Enabled = false;
}
#else
catch (ReflectionTypeLoadException)
{
item.ToolTipText = "Something goes wrong while trying to load";
item.Enabled = false;
}
#endif
return item;
}
/// <summary>
/// This event is raised when we add a dll file into
/// the external tools path.
/// It will automatically load the assembly and add it into the list
/// </summary>
/// <param name="sender">Object that raised the event</param>
/// <param name="e">Event arguments</param>
private static void DirectoryMonitor_Created(object sender, FileSystemEventArgs e)
{
menuItems.Add(GenerateToolTipFromFileName(e.FullPath));
}
#endregion
#region Properties
/// <summary>
/// Gets a prebuild <see cref="ToolStripMenuItem"/>
/// This list auto-updated by the <see cref="ExternalToolManager"/> itself
/// </summary>
public static IEnumerable<ToolStripMenuItem> ToolStripMenu
{
get
{
return menuItems;
}
}
#endregion
}
}

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<ClassDiagram MajorVersion="1" MinorVersion="1" MembersFormat="FullSignature">
<Class Name="BizHawk.Client.Common.Watch">
<Position X="17" Y="0.75" Width="8.75" />
<Position X="17" Y="0.75" Width="10" />
<Compartments>
<Compartment Name="Nested Types" Collapsed="false" />
</Compartments>
@ -9,19 +9,19 @@
<Lollipop Orientation="Left" Position="0.1" />
</Class>
<Class Name="BizHawk.Client.Common.SeparatorWatch">
<Position X="6" Y="19" Width="2.75" />
<Position X="6" Y="19" Width="3.25" />
<TypeIdentifier />
</Class>
<Class Name="BizHawk.Client.Common.ByteWatch">
<Position X="28.5" Y="19" Width="8.5" />
<Position X="28.5" Y="19" Width="3.25" />
<TypeIdentifier />
</Class>
<Class Name="BizHawk.Client.Common.WordWatch">
<Position X="9.5" Y="19" Width="8.75" />
<Position X="9.5" Y="19" Width="3.25" />
<TypeIdentifier />
</Class>
<Class Name="BizHawk.Client.Common.DWordWatch">
<Position X="19" Y="19" Width="8.75" />
<Position X="19" Y="19" Width="3.25" />
<TypeIdentifier />
</Class>
<Class Name="BizHawk.Client.Common.WatchList">
@ -36,6 +36,20 @@
<Position X="11.5" Y="0.75" Width="2" />
<TypeIdentifier />
</Class>
<Class Name="BizHawk.Client.ApiHawk.ExternalToolManager">
<Position X="4.75" Y="6.75" Width="4.75" />
<TypeIdentifier>
<HashCode>AAAAAQAAAAAAAAAAAAAQAAAAAAgBAAAAAAACAAAAAAI=</HashCode>
<FileName>Classes\ExternalToolManager.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="BizHawk.Client.ApiHawk.BizHawkExternalToolAttribute">
<Position X="11" Y="2.25" Width="5.75" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAgAAAAIAAAIAQAAAAAACAEAAAAAAAAAAA=</HashCode>
<FileName>Attributes\ExternalToolAttribute.cs</FileName>
</TypeIdentifier>
</Class>
<Interface Name="BizHawk.Client.EmuHawk.IExternalToolForm">
<Position X="4.5" Y="4.5" Width="2.75" />
<TypeIdentifier>
@ -52,11 +66,11 @@
<TypeIdentifier />
</Interface>
<Enum Name="BizHawk.Client.Common.WatchSize">
<Position X="26.5" Y="3.75" Width="1.5" />
<Position X="28.5" Y="5.5" Width="1.5" />
<TypeIdentifier />
</Enum>
<Enum Name="BizHawk.Client.Common.DisplayType">
<Position X="26.5" Y="1" Width="1.5" />
<Position X="28.5" Y="2.75" Width="1.5" />
<TypeIdentifier />
</Enum>
<Enum Name="BizHawk.Client.Common.PreviousType">

View File

@ -40,7 +40,7 @@ namespace BizHawk.Client.Common
{
if (value == 0)
{
value = GetByte();
this._value = GetByte();
}
else
{
@ -52,7 +52,7 @@ namespace BizHawk.Client.Common
#endregion
#region Methods
#region Methods
/// <summary>
/// Enumerate wich <see cref="DisplayType"/> are valid for a <see cref="ByteWatch"/>

View File

@ -40,7 +40,7 @@ namespace BizHawk.Client.Common
{
if (value == 0)
{
value = GetDWord();
this._value = GetDWord();
}
else
{

View File

@ -40,7 +40,7 @@ namespace BizHawk.Client.Common
{
if (value == 0)
{
value = GetWord();
this._value = GetWord();
}
else
{

View File

@ -23,6 +23,7 @@ using BizHawk.Client.EmuHawk.CustomControls;
using BizHawk.Client.EmuHawk.WinFormExtensions;
using BizHawk.Client.EmuHawk.ToolExtensions;
using BizHawk.Emulation.Cores.Computers.AppleII;
using BizHawk.Client.ApiHawk;
namespace BizHawk.Client.EmuHawk
{
@ -1257,65 +1258,23 @@ namespace BizHawk.Client.EmuHawk
private void ExternalToolToolStripMenuItem_DropDownOpening(object sender, EventArgs e)
{
externalToolToolStripMenuItem.DropDownItems.Clear();
string path = Path.Combine(Global.Config.PathEntries["Global", "External Tools"].Path);
if (Directory.Exists(path))
foreach(ToolStripMenuItem item in ExternalToolManager.ToolStripMenu)
{
DirectoryInfo dInfo = new DirectoryInfo(path);
Type[] assemblyTypes;
Assembly externalToolFile;
foreach (FileInfo fi in dInfo.GetFiles("*.dll"))
if(item.Enabled)
{
try
item.Click += delegate
{
//externalToolFile = Assembly.ReflectionOnlyLoadFrom(fi.FullName);
externalToolFile = Assembly.LoadFrom(fi.FullName);
}
catch (BadImageFormatException)
{
ToolStripMenuItem item = new ToolStripMenuItem(fi.Name, Properties.Resources.ExclamationRed);
item.ToolTipText = "This is not an assembly";
item.ForeColor = Color.Gray;
externalToolToolStripMenuItem.DropDownItems.Add(item);
continue;
}
ToolStripMenuItem externalToolMenu = new ToolStripMenuItem(externalToolFile.GetName().Name);
/*
The reason of using this ugly try catch is due to the use of ReflectionOnlyLoadFrom methods
When the assembly is loaded this way, referenced assemblies are not loaded and so, as soon as a type
existing in another assembly, it raises the exception.
But the advantage of this is that memory footprint is reduced
EDIT: In fact, I have some trouble when loading Reflection only... moved to regular load
*/
try
{
assemblyTypes = externalToolFile.GetTypes().Where<Type>(t => t != null && t.FullName == "BizHawk.Client.EmuHawk.CustomMainForm").ToArray<Type>();
}
catch (ReflectionTypeLoadException ex)
{
assemblyTypes = ex.Types.Where<Type>(t => t != null && t.FullName.Contains("BizHawk.Client.EmuHawk.CustomMainForm")).ToArray<Type>();
}
if (assemblyTypes.Count() == 1)
{
externalToolMenu.Image = Properties.Resources.Debugger;
externalToolMenu.Tag = fi.FullName;
externalToolMenu.Click += delegate (object sender2, EventArgs e2)
{
GlobalWin.Tools.Load<IExternalToolForm>(fi.FullName);
};
}
else
{
externalToolMenu.Image = Properties.Resources.ExclamationRed;
externalToolMenu.ForeColor = Color.Gray;
}
externalToolToolStripMenuItem.DropDownItems.Add(externalToolMenu);
GlobalWin.Tools.Load<IExternalToolForm>((string)item.Tag);
};
}
else
{
item.Image = Properties.Resources.ExclamationRed;
}
externalToolToolStripMenuItem.DropDownItems.Add(item);
}
if (externalToolToolStripMenuItem.DropDownItems.Count == 0)
{
externalToolToolStripMenuItem.DropDownItems.Add("None");

View File

@ -91,7 +91,7 @@ namespace BizHawk.Client.EmuHawk
T existingTool = (T)_tools.FirstOrDefault(x => x is T);
if (existingTool != null && typeof(T) != typeof(IExternalToolForm))
if (existingTool != null)
{
if (existingTool.IsDisposed)
{
@ -572,7 +572,7 @@ namespace BizHawk.Client.EmuHawk
try
{
tool = Activator.CreateInstanceFrom(dllPath, "BizHawk.Client.EmuHawk.CustomMainForm").Unwrap() as IExternalToolForm;
if (tool == null)
if (tool == null)
{
MessageBox.Show("It seems that the object CustomMainForm does not implement IExternalToolForm. Please review the code.", "No, no, no. Wrong Way !", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
return null;

View File

@ -280,7 +280,7 @@ namespace BizHawk.Client.EmuHawk
{
get
{
foreach(var watch in _watches)
foreach (var watch in _watches)
{
if (watch.IsSeparator)
{
@ -409,7 +409,7 @@ namespace BizHawk.Client.EmuHawk
};
we.SetWatch(SelectedWatches.First().Domain, SelectedWatches, duplicate ? WatchEditor.Mode.Duplicate : WatchEditor.Mode.Edit);
var result = we.ShowHawkDialog();
if (result == DialogResult.OK)
{
@ -1038,7 +1038,7 @@ namespace BizHawk.Client.EmuHawk
_watches.Load(filePaths[0], append: false);
Global.Config.RecentWatches.Add(_watches.CurrentFileName);
WatchListView.ItemCount = _watches.Count;
}
}
}
private void NewRamWatch_Enter(object sender, EventArgs e)
@ -1056,9 +1056,9 @@ namespace BizHawk.Client.EmuHawk
PokeContextMenuItem.Visible =
FreezeContextMenuItem.Visible =
Separator4.Visible =
ReadBreakpointContextMenuItem.Visible =
ReadBreakpointContextMenuItem.Visible =
WriteBreakpointContextMenuItem.Visible =
Separator6.Visible =
Separator6.Visible =
InsertSeperatorContextMenuItem.Visible =
MoveUpContextMenuItem.Visible =
MoveDownContextMenuItem.Visible =
@ -1115,7 +1115,7 @@ namespace BizHawk.Client.EmuHawk
}
else
{
ToolHelpers.ViewInHexEditor(selected.First().Domain, selected.Select(x => x.Address ), selected.First().Size);
ToolHelpers.ViewInHexEditor(selected.First().Domain, selected.Select(x => x.Address), selected.First().Size);
}
}
}
@ -1134,7 +1134,7 @@ namespace BizHawk.Client.EmuHawk
}
}
}
private void WriteBreakpointContextMenuItem_Click(object sender, EventArgs e)
{
var selected = SelectedWatches.ToList();

Binary file not shown.