diff --git a/ILSpy.sln b/ILSpy.sln index f3c38c7d8..a4ff171cd 100644 --- a/ILSpy.sln +++ b/ILSpy.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 11.00 # Visual Studio 2010 -# SharpDevelop 4.0.1.7100 +# SharpDevelop 4.0.1.7126 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ILSpy", "ILSpy\ILSpy.csproj", "{1E85EFF9-E370-4683-83E4-8A3D063FF791}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ICSharpCode.TreeView", "SharpTreeView\ICSharpCode.TreeView.csproj", "{DDE2A481-8271-4EAC-A330-8FA6A38D13D1}" @@ -16,6 +16,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ICSharpCode.NRefactory", "N EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ICSharpCode.Decompiler.Tests", "ICSharpCode.Decompiler\Tests\ICSharpCode.Decompiler.Tests.csproj", "{FEC0DA52-C4A6-4710-BE36-B484A20C5E22}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "doc", "doc", "{F45DB999-7E72-4000-B5AD-3A7B485A0896}" + ProjectSection(SolutionItems) = postProject + doc\Command Line.txt = doc\Command Line.txt + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/ILSpy/App.xaml.cs b/ILSpy/App.xaml.cs index f6b9dc250..f7a1dab9c 100644 --- a/ILSpy/App.xaml.cs +++ b/ILSpy/App.xaml.cs @@ -20,6 +20,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.Composition.Hosting; using System.Diagnostics; +using System.IO; using System.Linq; using System.Threading; using System.Windows; @@ -44,10 +45,12 @@ namespace ICSharpCode.ILSpy public App() { - App.CommandLineArguments = new CommandLineArguments(Environment.GetCommandLineArgs().Skip(1)); - if (App.CommandLineArguments.SharedInstance) { - string message = string.Join(Environment.NewLine, Environment.GetCommandLineArgs().Skip(1)); - if (SendToPreviousInstance("ILSpy:\r\n" + message)) { + var cmdArgs = Environment.GetCommandLineArgs().Skip(1); + App.CommandLineArguments = new CommandLineArguments(cmdArgs); + if (App.CommandLineArguments.SingleInstance ?? true) { + cmdArgs = cmdArgs.Select(FullyQualifyPath); + string message = string.Join(Environment.NewLine, cmdArgs); + if (SendToPreviousInstance("ILSpy:\r\n" + message, !App.CommandLineArguments.NoActivate)) { Environment.Exit(0); } } @@ -71,6 +74,19 @@ namespace ICSharpCode.ILSpy new RequestNavigateEventHandler(Window_RequestNavigate)); } + string FullyQualifyPath(string argument) + { + // Fully qualify the paths before passing them to another process, + // because that process might use a different current directory. + if (string.IsNullOrEmpty(argument) || argument[0] == '/') + return argument; + try { + return Path.Combine(Environment.CurrentDirectory, argument); + } catch (ArgumentException) { + return argument; + } + } + #region Exception Handling static void Dispatcher_UnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) { @@ -90,7 +106,7 @@ namespace ICSharpCode.ILSpy #endregion #region Pass Command Line Arguments to previous instance - bool SendToPreviousInstance(string message) + bool SendToPreviousInstance(string message, bool activate) { bool success = false; NativeMethods.EnumWindows( @@ -101,7 +117,8 @@ namespace ICSharpCode.ILSpy IntPtr result = Send(hWnd, message); Debug.WriteLine("WM_COPYDATA result: {0:x8}", result); if (result == (IntPtr)1) { - NativeMethods.SetForegroundWindow(hWnd); + if (activate) + NativeMethods.SetForegroundWindow(hWnd); success = true; return false; // stop enumeration } diff --git a/ILSpy/CommandLineArguments.cs b/ILSpy/CommandLineArguments.cs index 9ba1fc793..dafb99e2c 100644 --- a/ILSpy/CommandLineArguments.cs +++ b/ILSpy/CommandLineArguments.cs @@ -8,9 +8,12 @@ namespace ICSharpCode.ILSpy { sealed class CommandLineArguments { + // see /doc/Command Line.txt for details public List AssembliesToLoad = new List(); + public bool? SingleInstance; public string NavigateTo; - public bool SharedInstance = true; + public string Language; + public bool NoActivate; public CommandLineArguments(IEnumerable arguments) { @@ -18,12 +21,16 @@ namespace ICSharpCode.ILSpy if (arg.Length == 0) continue; if (arg[0] == '/') { - if (arg.Equals("/sharedInstance", StringComparison.OrdinalIgnoreCase)) - this.SharedInstance = true; + if (arg.Equals("/singleInstance", StringComparison.OrdinalIgnoreCase)) + this.SingleInstance = true; else if (arg.Equals("/separate", StringComparison.OrdinalIgnoreCase)) - this.SharedInstance = false; + this.SingleInstance = false; else if (arg.StartsWith("/navigateTo:", StringComparison.OrdinalIgnoreCase)) this.NavigateTo = arg.Substring("/navigateTo:".Length); + else if (arg.StartsWith("/language:", StringComparison.OrdinalIgnoreCase)) + this.Language = arg.Substring("/language:".Length); + else if (arg.Equals("/noActivate", StringComparison.OrdinalIgnoreCase)) + this.NoActivate = true; } else { this.AssembliesToLoad.Add(arg); } diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index 6b631cecd..d0a039257 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -16,6 +16,7 @@ 4 false Images\ILSpy.ico + False x86 @@ -38,6 +39,7 @@ False True DEBUG;TRACE + Project bin\Release\ @@ -125,7 +127,8 @@ - + + README.txt diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index 79fefa49a..e0d146da8 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -200,18 +200,52 @@ namespace ICSharpCode.ILSpy while ((line = r.ReadLine()) != null) lines.Add(line); } - HandleCommandLineArguments(new CommandLineArguments(lines)); - handled = true; - return (IntPtr)1; + var args = new CommandLineArguments(lines); + if (HandleCommandLineArguments(args)) { + HandleCommandLineArgumentsAfterShowList(args); + handled = true; + return (IntPtr)1; + } } } return IntPtr.Zero; } #endregion - void HandleCommandLineArguments(CommandLineArguments args) + List commandLineLoadedAssemblies = new List(); + + bool HandleCommandLineArguments(CommandLineArguments args) { - OpenFiles(args.AssembliesToLoad.ToArray()); + foreach (string file in args.AssembliesToLoad) { + commandLineLoadedAssemblies.Add(assemblyList.OpenAssembly(file)); + } + if (args.Language != null) + sessionSettings.FilterSettings.Language = Languages.GetLanguage(args.Language); + return true; + } + + void HandleCommandLineArgumentsAfterShowList(CommandLineArguments args) + { + if (args.NavigateTo != null) { + bool found = false; + foreach (LoadedAssembly asm in commandLineLoadedAssemblies) { + AssemblyDefinition def = asm.AssemblyDefinition; + if (def != null) { + MemberReference mr = XmlDocKeyProvider.FindMemberByKey(def.MainModule, args.NavigateTo); + if (mr != null) { + found = true; + JumpToReference(mr); + break; + } + } + } + if (!found) { + AvalonEditTextOutput output = new AvalonEditTextOutput(); + output.Write("Cannot find " + args.NavigateTo); + decompilerTextView.Show(output); + } + } + commandLineLoadedAssemblies.Clear(); // clear references once we don't need them anymore } void MainWindow_Loaded(object sender, RoutedEventArgs e) @@ -224,19 +258,27 @@ namespace ICSharpCode.ILSpy this.assemblyList = assemblyListManager.LoadList(spySettings, sessionSettings.ActiveAssemblyList); HandleCommandLineArguments(App.CommandLineArguments); - ShowAssemblyList(this.assemblyList); - if (assemblyList.GetAssemblies().Length == 0) + if (assemblyList.GetAssemblies().Length == 0 + && assemblyList.ListName == AssemblyListManager.DefaultListName) + { LoadInitialAssemblies(); + } + + ShowAssemblyList(this.assemblyList); - SharpTreeNode node = FindNodeByPath(sessionSettings.ActiveTreeViewPath, true); - if (node != null) { - SelectNode(node); - - // only if not showing the about page, perform the update check: - ShowMessageIfUpdatesAvailableAsync(spySettings); - } else { - AboutPage.Display(decompilerTextView); + HandleCommandLineArgumentsAfterShowList(App.CommandLineArguments); + + if (App.CommandLineArguments.NavigateTo == null) { + SharpTreeNode node = FindNodeByPath(sessionSettings.ActiveTreeViewPath, true); + if (node != null) { + SelectNode(node); + + // only if not showing the about page, perform the update check: + ShowMessageIfUpdatesAvailableAsync(spySettings); + } else { + AboutPage.Display(decompilerTextView); + } } } diff --git a/ILSpy/XamlResourceNode.cs b/ILSpy/TreeNodes/XamlResourceNode.cs similarity index 100% rename from ILSpy/XamlResourceNode.cs rename to ILSpy/TreeNodes/XamlResourceNode.cs diff --git a/ILSpy/XmlDocKeyProvider.cs b/ILSpy/XmlDocKeyProvider.cs new file mode 100644 index 000000000..f6aba0ae2 --- /dev/null +++ b/ILSpy/XmlDocKeyProvider.cs @@ -0,0 +1,168 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under MIT X11 license (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Mono.Cecil; + +namespace ICSharpCode.ILSpy +{ + /// + /// Provides XML documentation tags. + /// + sealed class XmlDocKeyProvider + { + #region GetKey + public static string GetKey(MemberReference member) + { + StringBuilder b = new StringBuilder(); + if (member is TypeReference) { + b.Append("T:"); + AppendTypeName(b, (TypeDefinition)member); + } else { + if (member is FieldReference) + b.Append("F:"); + else if (member is PropertyDefinition) + b.Append("P:"); + else if (member is EventDefinition) + b.Append("E:"); + else if (member is MethodReference) + b.Append("M:"); + AppendTypeName(b, member.DeclaringType); + b.Append('.'); + b.Append(member.Name); + IList parameters; + if (member is PropertyDefinition) { + parameters = ((PropertyDefinition)member).Parameters; + } else if (member is MethodReference) { + parameters = ((MethodReference)member).Parameters; + } else { + parameters = null; + } + if (parameters != null && parameters.Count > 0) { + b.Append('('); + for (int i = 0; i < parameters.Count; i++) { + if (i > 0) b.Append(','); + AppendTypeName(b, parameters[i].ParameterType); + } + b.Append(')'); + } + } + return b.ToString(); + } + + static void AppendTypeName(StringBuilder b, TypeReference type) + { + if (type is TypeSpecification) { + AppendTypeName(b, ((TypeSpecification)type).ElementType); + ArrayType arrayType = type as ArrayType; + if (arrayType != null) { + b.Append('['); + for (int i = 1; i < arrayType.Dimensions.Count; i++) { + b.Append(','); + } + b.Append(']'); + } + ByReferenceType refType = type as ByReferenceType; + if (refType != null) { + b.Append('@'); + } + GenericInstanceType giType = type as GenericInstanceType; + if (giType != null) { + b.Append('{'); + for (int i = 0; i < giType.GenericArguments.Count; i++) { + if (i > 0) b.Append(','); + AppendTypeName(b, giType.GenericArguments[i]); + } + b.Append('}'); + } + PointerType ptrType = type as PointerType; + if (ptrType != null) { + b.Append('*'); // TODO: is this correct? + } + } else { + GenericParameter gp = type as GenericParameter; + if (gp != null) { + b.Append('`'); + if (gp.Owner.GenericParameterType == GenericParameterType.Method) { + b.Append('`'); + } + b.Append(gp.Position); + } else if (type.DeclaringType != null) { + AppendTypeName(b, type.DeclaringType); + b.Append('.'); + b.Append(type.Name); + } else { + b.Append(type.FullName); + } + } + } + #endregion + + #region FindMemberByKey + public static MemberReference FindMemberByKey(ModuleDefinition module, string key) + { + if (module == null) + throw new ArgumentNullException("module"); + if (key == null || key.Length < 2 || key[1] != ':') + return null; + switch (key[0]) { + case 'T': + return FindType(module, key.Substring(2)); + case 'F': + return FindMember(module, key, type => type.Fields); + case 'P': + return FindMember(module, key, type => type.Properties); + case 'E': + return FindMember(module, key, type => type.Events); + case 'M': + return FindMember(module, key, type => type.Methods); + default: + return null; + } + } + + static MemberReference FindMember(ModuleDefinition module, string key, Func> memberSelector) + { + int pos = key.IndexOf('('); + int dotPos; + if (pos > 0) { + dotPos = key.LastIndexOf('.', 0, pos); + } else { + dotPos = key.LastIndexOf('.'); + } + TypeDefinition type = FindType(module, key.Substring(2, dotPos - 2)); + if (type == null) + return null; + foreach (MemberReference member in memberSelector(type)) { + if (GetKey(member) == key) + return member; + } + return null; + } + + static TypeDefinition FindType(ModuleDefinition module, string name) + { + int pos = name.LastIndexOf('.'); + string ns; + if (pos >= 0) { + ns = name.Substring(0, pos); + name = name.Substring(pos + 1); + } else { + ns = string.Empty; + } + TypeDefinition type = module.GetType(ns, name); + if (type == null && ns.Length > 0) { + // try if this is a nested type + type = FindType(module, ns); + if (type != null) { + type = type.NestedTypes.FirstOrDefault(t => t.Name == name); + } + } + return type; + } + #endregion + } +} diff --git a/doc/Command Line.txt b/doc/Command Line.txt new file mode 100644 index 000000000..0aef7a2d6 --- /dev/null +++ b/doc/Command Line.txt @@ -0,0 +1,52 @@ +ILSpy Command Line Arguments + +Command line arguments can be either options or file names. +If an argument is a file name, the file will be opened as assembly and added to the current assembly list. + +Available options: + /singleInstance If ILSpy is already running, activates the existing instance + and passes command line arguments to that instance. + This is the default value if /list is not used. + + /separate Start up a separate ILSpy instance even if it is already running. + + /noActivate Do not activate the existing ILSpy instance. This option has no effec + if a new ILSpy instance is being started. + + /list:listname Specifies the name of the assembly list that is loaded initially. + When this option is not specified, ILSpy loads the previously opened list. + Specify "/list" (without value) to open the default list. + + When this option is used, ILSpy will activate an existing instance + only if it uses the same list as specified. + + [Note: Assembly Lists are not yet implemented] + + /clearList Clears the assembly list before loading the specified assemblies. + [Note: Assembly Lists are not yet implemented] + + /navigateTo:tag Navigates to the member specified by the given XML documentation tag. + The member is searched for only in the assemblies specified on the command line. + Example: 'ILSpy ILSpy.exe /navigateTo:T:ICSharpCode.ILSpy.CommandLineArguments' + + /language:name Selects the specified language. + Example: 'ILSpy /language:C#' or 'ILSpy /language:IL' + +WM_COPYDATA (SendMessage API): + ILSpy can be controlled by other programs that send a WM_COPYDATA message to its main window. + The message data must be an Unicode (UTF-16) string starting with "ILSpy:\r\n". + All lines except the first ("ILSpy:") in that string are handled as command-line arguments. + There must be exactly one argument per line. + + That is, by sending this message: + ILSpy: + C:\Assembly.dll + /navigateTo T:Type + The target ILSpy instance will open C:\Assembly.dll and navigate to the specified type. + + ILSpy will return TRUE (1) if it handles the message, and FALSE (0) otherwise. + The /separate option will be ignored; WM_COPYDATA will never start up a new instance. + The /noActivate option has no effect, sending WM_COPYDATA will never activate the window. + Instead, the calling process should use SetForegroundWindow(). + If you use /list with WM_COPYDATA, you need to specify /singleInstance as well, otherwise + ILSpy will not handle the message if it has opened a different assembly list.