mirror of https://github.com/icsharpcode/ILSpy.git
41 changed files with 807 additions and 512 deletions
@ -0,0 +1,125 @@
@@ -0,0 +1,125 @@
|
||||
using System; |
||||
|
||||
using FluentAssertions; |
||||
|
||||
using ICSharpCode.ILSpy.AppEnv; |
||||
|
||||
using NUnit.Framework; |
||||
|
||||
namespace ICSharpCode.ILSpy.Tests |
||||
{ |
||||
[TestFixture] |
||||
public class CommandLineArgumentsTests |
||||
{ |
||||
[Test] |
||||
public void VerifyEmptyArgumentsArray() |
||||
{ |
||||
var cmdLineArgs = CommandLineArguments.Create(new string[] { }); |
||||
|
||||
cmdLineArgs.AssembliesToLoad.Should().BeEmpty(); |
||||
cmdLineArgs.SingleInstance.Should().BeNull(); |
||||
cmdLineArgs.NavigateTo.Should().BeNull(); |
||||
cmdLineArgs.Search.Should().BeNull(); |
||||
cmdLineArgs.Language.Should().BeNull(); |
||||
cmdLineArgs.NoActivate.Should().BeFalse(); |
||||
cmdLineArgs.ConfigFile.Should().BeNull(); |
||||
} |
||||
|
||||
[Test] |
||||
public void VerifyHelpOption() |
||||
{ |
||||
var cmdLineArgs = CommandLineArguments.Create(new string[] { "--help" }); |
||||
cmdLineArgs.ArgumentsParser.IsShowingInformation.Should().BeTrue(); |
||||
} |
||||
|
||||
[Test] |
||||
public void VerifyForceNewInstanceOption() |
||||
{ |
||||
var cmdLineArgs = CommandLineArguments.Create(new string[] { "--newinstance" }); |
||||
cmdLineArgs.SingleInstance.Should().BeFalse(); |
||||
} |
||||
|
||||
[Test] |
||||
public void VerifyNavigateToOption() |
||||
{ |
||||
const string navigateTo = "MyNamespace.MyClass"; |
||||
var cmdLineArgs = CommandLineArguments.Create(new string[] { "--navigateto", navigateTo }); |
||||
cmdLineArgs.NavigateTo.Should().BeEquivalentTo(navigateTo); |
||||
} |
||||
|
||||
[Test] |
||||
public void VerifyNavigateToOption_NoneTest_Matching_VSAddin() |
||||
{ |
||||
var cmdLineArgs = CommandLineArguments.Create(new string[] { "--navigateto:none" }); |
||||
cmdLineArgs.NavigateTo.Should().BeEquivalentTo("none"); |
||||
} |
||||
|
||||
[Test] |
||||
public void VerifyCaseSensitivityOfOptionsDoesntThrow() |
||||
{ |
||||
var cmdLineArgs = CommandLineArguments.Create(new string[] { "--navigateTo:none" }); |
||||
|
||||
cmdLineArgs.ArgumentsParser.RemainingArguments.Should().HaveCount(1); |
||||
} |
||||
|
||||
[Test] |
||||
public void VerifySearchOption() |
||||
{ |
||||
const string searchWord = "TestContainers"; |
||||
var cmdLineArgs = CommandLineArguments.Create(new string[] { "--search", searchWord }); |
||||
cmdLineArgs.Search.Should().BeEquivalentTo(searchWord); |
||||
} |
||||
|
||||
[Test] |
||||
public void VerifyLanguageOption() |
||||
{ |
||||
const string language = "csharp"; |
||||
var cmdLineArgs = CommandLineArguments.Create(new string[] { "--language", language }); |
||||
cmdLineArgs.Language.Should().BeEquivalentTo(language); |
||||
} |
||||
|
||||
[Test] |
||||
public void VerifyConfigOption() |
||||
{ |
||||
const string configFile = "myilspyoptions.xml"; |
||||
var cmdLineArgs = CommandLineArguments.Create(new string[] { "--config", configFile }); |
||||
cmdLineArgs.ConfigFile.Should().BeEquivalentTo(configFile); |
||||
} |
||||
|
||||
[Test] |
||||
public void VerifyNoActivateOption() |
||||
{ |
||||
var cmdLineArgs = CommandLineArguments.Create(new string[] { "--noactivate" }); |
||||
cmdLineArgs.NoActivate.Should().BeTrue(); |
||||
} |
||||
|
||||
[Test] |
||||
public void MultipleAssembliesAsArguments() |
||||
{ |
||||
var cmdLineArgs = CommandLineArguments.Create(new string[] { "assembly1", "assembly2", "assembly3" }); |
||||
cmdLineArgs.AssembliesToLoad.Should().HaveCount(3); |
||||
} |
||||
|
||||
[Test] |
||||
public void PassAtFileArguments() |
||||
{ |
||||
string filepath = System.IO.Path.GetTempFileName(); |
||||
|
||||
System.IO.File.WriteAllText(filepath, "assembly1\r\nassembly2\r\nassembly3\r\n--newinstance\r\n--noactivate"); |
||||
|
||||
var cmdLineArgs = CommandLineArguments.Create(new string[] { $"@{filepath}" }); |
||||
|
||||
try |
||||
{ |
||||
System.IO.File.Delete(filepath); |
||||
} |
||||
catch (Exception) |
||||
{ |
||||
} |
||||
|
||||
cmdLineArgs.SingleInstance.Should().BeFalse(); |
||||
cmdLineArgs.NoActivate.Should().BeTrue(); |
||||
cmdLineArgs.AssembliesToLoad.Should().HaveCount(3); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
|
||||
using System.Runtime.InteropServices; |
||||
|
||||
namespace ICSharpCode.ILSpy.AppEnv |
||||
{ |
||||
public static class AppEnvironment |
||||
{ |
||||
public static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); |
||||
} |
||||
} |
@ -0,0 +1,119 @@
@@ -0,0 +1,119 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using McMaster.Extensions.CommandLineUtils; |
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
|
||||
namespace ICSharpCode.ILSpy.AppEnv |
||||
{ |
||||
public sealed class CommandLineArguments |
||||
{ |
||||
// see /doc/Command Line.txt for details
|
||||
public List<string> AssembliesToLoad = new List<string>(); |
||||
public bool? SingleInstance; |
||||
public string NavigateTo; |
||||
public string Search; |
||||
public string Language; |
||||
public bool NoActivate; |
||||
public string ConfigFile; |
||||
|
||||
public CommandLineApplication ArgumentsParser { get; } |
||||
|
||||
private CommandLineArguments(CommandLineApplication app) |
||||
{ |
||||
ArgumentsParser = app; |
||||
} |
||||
|
||||
public static CommandLineArguments Create(IEnumerable<string> arguments) |
||||
{ |
||||
var app = new CommandLineApplication() { |
||||
// https://natemcmaster.github.io/CommandLineUtils/docs/response-file-parsing.html?tabs=using-attributes
|
||||
ResponseFileHandling = ResponseFileHandling.ParseArgsAsLineSeparated, |
||||
|
||||
// Note: options are case-sensitive (!), and, default behavior would be UnrecognizedArgumentHandling.Throw on Parse()
|
||||
UnrecognizedArgumentHandling = UnrecognizedArgumentHandling.CollectAndContinue |
||||
}; |
||||
|
||||
app.HelpOption(); |
||||
var instance = new CommandLineArguments(app); |
||||
|
||||
try |
||||
{ |
||||
var oForceNewInstance = app.Option("--newinstance", |
||||
"Start a new instance of ILSpy even if the user configuration is set to single-instance", |
||||
CommandOptionType.NoValue); |
||||
|
||||
var oNavigateTo = app.Option<string>("-n|--navigateto <TYPENAME>", |
||||
"Navigates to the member specified by the given ID string.\r\nThe member is searched for only in the assemblies specified on the command line.\r\nExample: 'ILSpy ILSpy.exe --navigateto T:ICSharpCode.ILSpy.CommandLineArguments'", |
||||
CommandOptionType.SingleValue); |
||||
oNavigateTo.DefaultValue = null; |
||||
|
||||
var oSearch = app.Option<string>("-s|--search <SEARCHTERM>", |
||||
"Search for t:TypeName, m:Member or c:Constant; use exact match (=term), 'should not contain' (-term) or 'must contain' (+term); use /reg(ular)?Ex(pressions)?/ or both - t:/Type(Name)?/...", |
||||
CommandOptionType.SingleValue); |
||||
oSearch.DefaultValue = null; |
||||
|
||||
var oLanguage = app.Option<string>("-l|--language <LANGUAGEIDENTIFIER>", |
||||
"Selects the specified language.\r\nExample: 'ILSpy --language:C#' or 'ILSpy --language IL'", |
||||
CommandOptionType.SingleValue); |
||||
oLanguage.DefaultValue = null; |
||||
|
||||
var oConfig = app.Option<string>("-c|--config <CONFIGFILENAME>", |
||||
"Provide a specific configuration file.\r\nExample: 'ILSpy --config myconfig.xml'", |
||||
CommandOptionType.SingleValue); |
||||
oConfig.DefaultValue = null; |
||||
|
||||
var oNoActivate = app.Option("--noactivate", |
||||
"Do not activate the existing ILSpy instance. This option has no effect if a new ILSpy instance is being started.", |
||||
CommandOptionType.NoValue); |
||||
|
||||
// https://natemcmaster.github.io/CommandLineUtils/docs/arguments.html#variable-numbers-of-arguments
|
||||
// To enable this, MultipleValues must be set to true, and the argument must be the last one specified.
|
||||
var files = app.Argument("Assemblies", "Assemblies to load", multipleValues: true); |
||||
|
||||
app.Parse(arguments.ToArray()); |
||||
|
||||
if (oForceNewInstance.HasValue()) |
||||
instance.SingleInstance = false; |
||||
|
||||
instance.NavigateTo = oNavigateTo.ParsedValue; |
||||
instance.Search = oSearch.ParsedValue; |
||||
instance.Language = oLanguage.ParsedValue; |
||||
instance.ConfigFile = oConfig.ParsedValue; |
||||
|
||||
if (oNoActivate.HasValue()) |
||||
instance.NoActivate = true; |
||||
|
||||
foreach (var assembly in files.Values) |
||||
{ |
||||
if (!string.IsNullOrWhiteSpace(assembly)) |
||||
instance.AssembliesToLoad.Add(assembly); |
||||
} |
||||
} |
||||
catch (Exception) |
||||
{ |
||||
// Intentionally ignore exceptions if any, this is only added to always have an exception-free startup
|
||||
} |
||||
|
||||
return instance; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,290 @@
@@ -0,0 +1,290 @@
|
||||
// Source: https://github.com/medo64/Medo/blob/main/src/Medo/Application/SingleInstance.cs
|
||||
|
||||
/* Josip Medved <jmedved@jmedved.com> * www.medo64.com * MIT License */ |
||||
|
||||
//2022-12-01: Compatible with .NET 6 and 7
|
||||
//2012-11-24: Suppressing bogus CA5122 warning (http://connect.microsoft.com/VisualStudio/feedback/details/729254/bogus-ca5122-warning-about-p-invoke-declarations-should-not-be-safe-critical)
|
||||
//2010-10-07: Added IsOtherInstanceRunning method
|
||||
//2008-11-14: Reworked code to use SafeHandle
|
||||
//2008-04-11: Cleaned code to match FxCop 1.36 beta 2 (SpecifyMarshalingForPInvokeStringArguments, NestedTypesShouldNotBeVisible)
|
||||
//2008-04-10: NewInstanceEventArgs is not nested class anymore
|
||||
//2008-01-26: AutoExit parameter changed to NoAutoExit
|
||||
//2008-01-08: Main method is now called Attach
|
||||
//2008-01-06: System.Environment.Exit returns E_ABORT (0x80004004)
|
||||
//2008-01-03: Added Resources
|
||||
//2007-12-29: New version
|
||||
|
||||
#nullable enable |
||||
|
||||
namespace Medo.Application; |
||||
|
||||
using System; |
||||
using System.Diagnostics; |
||||
using System.IO.Pipes; |
||||
using System.Linq; |
||||
using System.Reflection; |
||||
using System.Runtime.InteropServices; |
||||
using System.Security.Cryptography; |
||||
using System.Text; |
||||
using System.Text.Json; |
||||
using System.Text.Json.Serialization; |
||||
using System.Threading; |
||||
|
||||
using ICSharpCode.ILSpy.AppEnv; |
||||
|
||||
/// <summary>
|
||||
/// Handles detection and communication of programs multiple instances.
|
||||
/// This class is thread safe.
|
||||
/// </summary>
|
||||
public static class SingleInstance |
||||
{ |
||||
|
||||
private static Mutex? _mtxFirstInstance; |
||||
private static Thread? _thread; |
||||
private static readonly object _syncRoot = new(); |
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this application is not already started.
|
||||
/// Another instance is contacted via named pipe.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">API call failed.</exception>
|
||||
public static bool Attach() |
||||
{ |
||||
return Attach(false); |
||||
} |
||||
|
||||
private static string[] GetILSpyCommandLineArgs() |
||||
{ |
||||
// Note: NO Skip(1) here because .Args property on SingleInstanceArguments does this for us
|
||||
return Environment.GetCommandLineArgs().AsEnumerable() |
||||
.Select(CommandLineTools.FullyQualifyPath) |
||||
.ToArray(); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Returns true if this application is not already started.
|
||||
/// Another instance is contacted via named pipe.
|
||||
/// </summary>
|
||||
/// <param name="noAutoExit">If true, application will exit after informing another instance.</param>
|
||||
/// <exception cref="InvalidOperationException">API call failed.</exception>
|
||||
public static bool Attach(bool noAutoExit) |
||||
{ |
||||
lock (_syncRoot) |
||||
{ |
||||
var isFirstInstance = false; |
||||
try |
||||
{ |
||||
_mtxFirstInstance = new Mutex(initiallyOwned: true, @"Global\" + MutexName, out isFirstInstance); |
||||
if (isFirstInstance == false) |
||||
{ //we need to contact previous instance
|
||||
var contentObject = new SingleInstanceArguments() { |
||||
CommandLine = Environment.CommandLine, |
||||
CommandLineArgs = GetILSpyCommandLineArgs(), |
||||
}; |
||||
var contentBytes = JsonSerializer.SerializeToUtf8Bytes(contentObject); |
||||
using var clientPipe = new NamedPipeClientStream(".", |
||||
MutexName, |
||||
PipeDirection.Out, |
||||
PipeOptions.CurrentUserOnly | PipeOptions.WriteThrough); |
||||
clientPipe.Connect(); |
||||
clientPipe.Write(contentBytes, 0, contentBytes.Length); |
||||
} |
||||
else |
||||
{ //there is no application already running.
|
||||
_thread = new Thread(Run) { |
||||
Name = typeof(SingleInstance).FullName, |
||||
IsBackground = true |
||||
}; |
||||
_thread.Start(); |
||||
} |
||||
} |
||||
catch (Exception ex) |
||||
{ |
||||
Trace.TraceWarning(ex.Message + " {Medo.Application.SingleInstance}"); |
||||
} |
||||
|
||||
if ((isFirstInstance == false) && (noAutoExit == false)) |
||||
{ |
||||
Trace.TraceInformation("Exit due to another instance running." + " [" + nameof(SingleInstance) + "]"); |
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
||||
{ |
||||
Environment.Exit(unchecked((int)0x80004004)); // E_ABORT(0x80004004)
|
||||
} |
||||
else |
||||
{ |
||||
Environment.Exit(114); // EALREADY(114)
|
||||
} |
||||
} |
||||
|
||||
return isFirstInstance; |
||||
} |
||||
} |
||||
|
||||
private static string? _mutexName; |
||||
private static string MutexName { |
||||
get { |
||||
lock (_syncRoot) |
||||
{ |
||||
if (_mutexName == null) |
||||
{ |
||||
var assembly = Assembly.GetEntryAssembly(); |
||||
|
||||
var sbMutextName = new StringBuilder(); |
||||
var assName = assembly?.GetName().Name; |
||||
if (assName != null) |
||||
{ |
||||
sbMutextName.Append(assName, 0, Math.Min(assName.Length, 31)); |
||||
sbMutextName.Append('.'); |
||||
} |
||||
|
||||
var sbHash = new StringBuilder(); |
||||
sbHash.AppendLine(Environment.MachineName); |
||||
sbHash.AppendLine(Environment.UserName); |
||||
if (assembly != null) |
||||
{ |
||||
sbHash.AppendLine(assembly.FullName); |
||||
sbHash.AppendLine(assembly.Location); |
||||
} |
||||
else |
||||
{ |
||||
var args = Environment.GetCommandLineArgs(); |
||||
if (args.Length > 0) |
||||
{ sbHash.AppendLine(args[0]); } |
||||
} |
||||
foreach (var b in SHA256.HashData(Encoding.UTF8.GetBytes(sbHash.ToString()))) |
||||
{ |
||||
if (sbMutextName.Length == 63) |
||||
{ sbMutextName.AppendFormat("{0:X1}", b >> 4); } // just take the first nubble
|
||||
if (sbMutextName.Length == 64) |
||||
{ break; } |
||||
sbMutextName.AppendFormat("{0:X2}", b); |
||||
} |
||||
_mutexName = sbMutextName.ToString(); |
||||
} |
||||
return _mutexName; |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Gets whether there is another instance running.
|
||||
/// It temporary creates mutex.
|
||||
/// </summary>
|
||||
public static bool IsOtherInstanceRunning { |
||||
get { |
||||
lock (_syncRoot) |
||||
{ |
||||
if (_mtxFirstInstance != null) |
||||
{ |
||||
return false; //no other instance is running
|
||||
} |
||||
else |
||||
{ |
||||
var tempInstance = new Mutex(true, MutexName, out var isFirstInstance); |
||||
tempInstance.Close(); |
||||
return (isFirstInstance == false); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Occurs in first instance when new instance is detected.
|
||||
/// </summary>
|
||||
public static event EventHandler<NewInstanceEventArgs>? NewInstanceDetected; |
||||
|
||||
|
||||
/// <summary>
|
||||
/// Thread function.
|
||||
/// </summary>
|
||||
private static void Run() |
||||
{ |
||||
using var serverPipe = new NamedPipeServerStream(MutexName, |
||||
PipeDirection.In, |
||||
maxNumberOfServerInstances: 1, |
||||
PipeTransmissionMode.Byte, |
||||
PipeOptions.CurrentUserOnly | PipeOptions.WriteThrough); |
||||
while (_mtxFirstInstance != null) |
||||
{ |
||||
try |
||||
{ |
||||
if (!serverPipe.IsConnected) |
||||
{ serverPipe.WaitForConnection(); } |
||||
var contentObject = JsonSerializer.Deserialize<SingleInstanceArguments>(serverPipe); |
||||
serverPipe.Disconnect(); |
||||
if (contentObject != null) |
||||
{ |
||||
NewInstanceDetected?.Invoke(null, |
||||
new NewInstanceEventArgs( |
||||
contentObject.CommandLine, |
||||
contentObject.CommandLineArgs)); |
||||
} |
||||
} |
||||
catch (Exception ex) |
||||
{ |
||||
Trace.TraceWarning(ex.Message + " [" + nameof(SingleInstance) + "]"); |
||||
Thread.Sleep(100); |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
[Serializable] |
||||
private sealed record SingleInstanceArguments |
||||
{ // just a storage
|
||||
[JsonInclude] |
||||
public required string CommandLine; |
||||
|
||||
[JsonInclude] |
||||
public required string[] CommandLineArgs; |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
/// <summary>
|
||||
/// Arguments for newly detected application instance.
|
||||
/// </summary>
|
||||
public sealed class NewInstanceEventArgs : EventArgs |
||||
{ |
||||
/// <summary>
|
||||
/// Creates new instance.
|
||||
/// </summary>
|
||||
/// <param name="commandLine">Command line.</param>
|
||||
/// <param name="commandLineArgs">String array containing the command line arguments in the same format as Environment.GetCommandLineArgs.</param>
|
||||
internal NewInstanceEventArgs(string commandLine, string[] commandLineArgs) |
||||
{ |
||||
CommandLine = commandLine; |
||||
_commandLineArgs = new string[commandLineArgs.Length]; |
||||
Array.Copy(commandLineArgs, _commandLineArgs, _commandLineArgs.Length); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Gets the command line.
|
||||
/// </summary>
|
||||
public string CommandLine { get; } |
||||
|
||||
private readonly string[] _commandLineArgs; |
||||
/// <summary>
|
||||
/// Returns a string array containing the command line arguments.
|
||||
/// </summary>
|
||||
public string[] GetCommandLineArgs() |
||||
{ |
||||
var argCopy = new string[_commandLineArgs.Length]; |
||||
Array.Copy(_commandLineArgs, argCopy, argCopy.Length); |
||||
return argCopy; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Gets a string array containing the command line arguments without the name of exectuable.
|
||||
/// </summary>
|
||||
public string[] Args { |
||||
get { |
||||
var argCopy = new string[_commandLineArgs.Length - 1]; |
||||
Array.Copy(_commandLineArgs, 1, argCopy, 0, argCopy.Length); |
||||
return argCopy; |
||||
} |
||||
} |
||||
|
||||
} |
@ -1,65 +0,0 @@
@@ -1,65 +0,0 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
|
||||
namespace ICSharpCode.ILSpy |
||||
{ |
||||
sealed class CommandLineArguments |
||||
{ |
||||
// see /doc/Command Line.txt for details
|
||||
public List<string> AssembliesToLoad = new List<string>(); |
||||
public bool? SingleInstance; |
||||
public string NavigateTo; |
||||
public string Search; |
||||
public string Language; |
||||
public bool NoActivate; |
||||
public string ConfigFile; |
||||
|
||||
public CommandLineArguments(IEnumerable<string> arguments) |
||||
{ |
||||
foreach (string arg in arguments) |
||||
{ |
||||
if (arg.Length == 0) |
||||
continue; |
||||
if (arg[0] == '/') |
||||
{ |
||||
if (arg.Equals("/singleInstance", StringComparison.OrdinalIgnoreCase)) |
||||
this.SingleInstance = true; |
||||
else if (arg.Equals("/separate", StringComparison.OrdinalIgnoreCase)) |
||||
this.SingleInstance = false; |
||||
else if (arg.StartsWith("/navigateTo:", StringComparison.OrdinalIgnoreCase)) |
||||
this.NavigateTo = arg.Substring("/navigateTo:".Length); |
||||
else if (arg.StartsWith("/search:", StringComparison.OrdinalIgnoreCase)) |
||||
this.Search = arg.Substring("/search:".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 if (arg.StartsWith("/config:", StringComparison.OrdinalIgnoreCase)) |
||||
this.ConfigFile = arg.Substring("/config:".Length); |
||||
} |
||||
else |
||||
{ |
||||
this.AssembliesToLoad.Add(arg); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,154 +0,0 @@
@@ -1,154 +0,0 @@
|
||||
// Copyright (c) 2022 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Diagnostics; |
||||
using System.IO; |
||||
using System.Linq; |
||||
using System.Threading; |
||||
|
||||
namespace ICSharpCode.ILSpy |
||||
{ |
||||
internal static class SingleInstanceHandling |
||||
{ |
||||
internal static Mutex SingleInstanceMutex; |
||||
|
||||
internal static void ForceSingleInstance(IEnumerable<string> cmdArgs) |
||||
{ |
||||
bool isFirst; |
||||
try |
||||
{ |
||||
SingleInstanceMutex = new Mutex(initiallyOwned: true, @"Local\ILSpyInstance", out isFirst); |
||||
} |
||||
catch (WaitHandleCannotBeOpenedException) |
||||
{ |
||||
isFirst = true; |
||||
} |
||||
if (!isFirst) |
||||
{ |
||||
try |
||||
{ |
||||
SingleInstanceMutex.WaitOne(10000); |
||||
} |
||||
catch (AbandonedMutexException) |
||||
{ |
||||
// continue, there is no concurrent start happening.
|
||||
} |
||||
} |
||||
cmdArgs = cmdArgs.Select(FullyQualifyPath); |
||||
string message = string.Join(Environment.NewLine, cmdArgs); |
||||
if (SendToPreviousInstance("ILSpy:\r\n" + message, !App.CommandLineArguments.NoActivate)) |
||||
{ |
||||
ReleaseSingleInstanceMutex(); |
||||
Environment.Exit(0); |
||||
} |
||||
} |
||||
|
||||
internal static 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; |
||||
} |
||||
} |
||||
|
||||
internal static void ReleaseSingleInstanceMutex() |
||||
{ |
||||
var mutex = SingleInstanceMutex; |
||||
SingleInstanceMutex = null; |
||||
if (mutex == null) |
||||
{ |
||||
return; |
||||
} |
||||
using (mutex) |
||||
{ |
||||
mutex.ReleaseMutex(); |
||||
} |
||||
} |
||||
|
||||
#region Pass Command Line Arguments to previous instance
|
||||
internal static bool SendToPreviousInstance(string message, bool activate) |
||||
{ |
||||
string ownProcessName; |
||||
using (var ownProcess = Process.GetCurrentProcess()) |
||||
{ |
||||
ownProcessName = ownProcess.ProcessName; |
||||
} |
||||
|
||||
bool success = false; |
||||
NativeMethods.EnumWindows( |
||||
(hWnd, lParam) => { |
||||
string windowTitle = NativeMethods.GetWindowText(hWnd, 100); |
||||
if (windowTitle.StartsWith("ILSpy", StringComparison.Ordinal)) |
||||
{ |
||||
string processName = NativeMethods.GetProcessNameFromWindow(hWnd); |
||||
Debug.WriteLine("Found {0:x4}: '{1}' in '{2}'", hWnd, windowTitle, processName); |
||||
if (string.Equals(processName, ownProcessName, StringComparison.OrdinalIgnoreCase)) |
||||
{ |
||||
IntPtr result = Send(hWnd, message); |
||||
Debug.WriteLine("WM_COPYDATA result: {0:x8}", result); |
||||
if (result == (IntPtr)1) |
||||
{ |
||||
if (activate) |
||||
NativeMethods.SetForegroundWindow(hWnd); |
||||
success = true; |
||||
return false; // stop enumeration
|
||||
} |
||||
} |
||||
} |
||||
return true; // continue enumeration
|
||||
}, IntPtr.Zero); |
||||
return success; |
||||
} |
||||
|
||||
unsafe static IntPtr Send(IntPtr hWnd, string message) |
||||
{ |
||||
const uint SMTO_NORMAL = 0; |
||||
|
||||
CopyDataStruct lParam; |
||||
lParam.Padding = IntPtr.Zero; |
||||
lParam.Size = message.Length * 2; |
||||
fixed (char* buffer = message) |
||||
{ |
||||
lParam.Buffer = (IntPtr)buffer; |
||||
IntPtr result; |
||||
// SendMessage with 3s timeout (e.g. when the target process is stopped in the debugger)
|
||||
if (NativeMethods.SendMessageTimeout( |
||||
hWnd, NativeMethods.WM_COPYDATA, IntPtr.Zero, ref lParam, |
||||
SMTO_NORMAL, 3000, out result) != IntPtr.Zero) |
||||
{ |
||||
return result; |
||||
} |
||||
else |
||||
{ |
||||
return IntPtr.Zero; |
||||
} |
||||
} |
||||
} |
||||
#endregion
|
||||
} |
||||
} |
@ -1,54 +1,27 @@
@@ -1,54 +1,27 @@
|
||||
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. |
||||
Usage: <Assemblies> [options] |
||||
@ResponseFile.rsp |
||||
|
||||
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. |
||||
Arguments: |
||||
Assemblies Assemblies to load |
||||
|
||||
/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 effect |
||||
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 ID string. |
||||
Options: |
||||
--newinstance Start a new instance of ILSpy even if the user configuration is set to single-instance |
||||
-n|--navigateto <TYPENAME> Navigates to the member specified by the given ID string. |
||||
The member is searched for only in the assemblies specified on the command line. |
||||
Example: 'ILSpy ILSpy.exe /navigateTo:T:ICSharpCode.ILSpy.CommandLineArguments' |
||||
|
||||
The syntax of ID strings is described in appendix A of the C# language specification. |
||||
|
||||
/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. |
||||
Example: 'ILSpy ILSpy.exe --navigateTo:T:ICSharpCode.ILSpy.CommandLineArguments' |
||||
-s|--search <SEARCHTERM> Search for t:TypeName, m:Member or c:Constant; use exact match (=term), |
||||
'should not contain' (-term) or 'must contain' (+term); use |
||||
/reg(ular)?Ex(pressions)?/ or both - t:/Type(Name)?/... |
||||
-l|--language <LANGUAGEIDENTIFIER> Selects the specified language. |
||||
Example: 'ILSpy --language:C#' or 'ILSpy --language:IL' |
||||
-c|--config <CONFIGFILENAME> Provide a specific configuration file. |
||||
Example: 'ILSpy --config:myconfig.xml' |
||||
--noactivate Do not activate the existing ILSpy instance. |
||||
This option has no effect if a new ILSpy instance is being started. |
||||
|
||||
Note on @ResponseFile.rsp: |
||||
|
||||
* The response file should contain the arguments, one argument per line (not space-separated!). |
||||
* Use it when the list of assemblies is too long to fit on the command line. |
@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
|
||||
# For local development of the VSIX package - build and publish (VS2022 also needs arm64) |
||||
|
||||
$output_x64 = "./ILSpy/bin/Debug/net8.0-windows/win-x64/publish/fwdependent" |
||||
|
||||
dotnet publish ./ILSpy/ILSpy.csproj -c Debug --no-restore --no-self-contained -r win-x64 -o $output_x64 |
||||
dotnet publish ./ILSpy.ReadyToRun/ILSpy.ReadyToRun.csproj -c Debug --no-restore --no-self-contained -r win-x64 -o $output_x64 |
||||
dotnet publish ./ILSpy.BamlDecompiler/ILSpy.BamlDecompiler.csproj -c Debug --no-restore --no-self-contained -r win-x64 -o $output_x64 |
||||
|
||||
$output_arm64 = "./ILSpy/bin/Debug/net8.0-windows/win-arm64/publish/fwdependent" |
||||
|
||||
dotnet publish ./ILSpy/ILSpy.csproj -c Debug --no-restore --no-self-contained -r win-arm64 -o $output_arm64 |
||||
dotnet publish ./ILSpy.ReadyToRun/ILSpy.ReadyToRun.csproj -c Debug --no-restore --no-self-contained -r win-arm64 -o $output_arm64 |
||||
dotnet publish ./ILSpy.BamlDecompiler/ILSpy.BamlDecompiler.csproj -c Debug --no-restore --no-self-contained -r win-arm64 -o $output_arm64 |
Loading…
Reference in new issue