diff --git a/Directory.Packages.props b/Directory.Packages.props
index 1a9168125..15f165921 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -14,6 +14,7 @@
+
@@ -21,11 +22,11 @@
-
+
-
+
@@ -33,7 +34,7 @@
-
+
diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs
index 518c09760..81632f6a5 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs
@@ -20,7 +20,6 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
-using System.Reflection;
using ICSharpCode.Decompiler.CSharp.Resolver;
using ICSharpCode.Decompiler.Semantics;
@@ -1423,7 +1422,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms
value = call.Arguments[0];
if (call.Arguments.Count == 2)
return MatchGetTypeFromHandle(call.Arguments[1], out type);
- type = value.InferType(context.TypeSystem);
+ type = value switch {
+ LdNull => SpecialType.NullType,
+ LdStr => context.TypeSystem.FindType(KnownTypeCode.String),
+ LdcF4 => context.TypeSystem.FindType(KnownTypeCode.Single),
+ LdcF8 => context.TypeSystem.FindType(KnownTypeCode.Double),
+ LdcI4 => context.TypeSystem.FindType(KnownTypeCode.Int32),
+ LdcI8 => context.TypeSystem.FindType(KnownTypeCode.Int64),
+ _ => value.InferType(context.TypeSystem),
+ };
return true;
}
return false;
diff --git a/ICSharpCode.Decompiler/Metadata/WebCilFile.cs b/ICSharpCode.Decompiler/Metadata/WebCilFile.cs
index 291316085..d862c261b 100644
--- a/ICSharpCode.Decompiler/Metadata/WebCilFile.cs
+++ b/ICSharpCode.Decompiler/Metadata/WebCilFile.cs
@@ -47,9 +47,14 @@ namespace ICSharpCode.Decompiler.Metadata
this.WasmSections = wasmSections;
}
- public static WebCilFile? FromStream(string fileName, MetadataReaderOptions metadataOptions = MetadataReaderOptions.Default)
+ public static WebCilFile? FromFile(string fileName, MetadataReaderOptions metadataOptions = MetadataReaderOptions.Default)
{
- using var memoryMappedFile = MemoryMappedFile.CreateFromFile(fileName, FileMode.Open, null, 0, MemoryMappedFileAccess.Read);
+ using var memoryMappedFile = TryCreateFromFile(fileName);
+ if (memoryMappedFile == null)
+ {
+ return null;
+ }
+
var view = memoryMappedFile.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read);
try
{
@@ -124,6 +129,18 @@ namespace ICSharpCode.Decompiler.Metadata
{
view?.Dispose();
}
+
+ static MemoryMappedFile? TryCreateFromFile(string fileName)
+ {
+ try
+ {
+ return MemoryMappedFile.CreateFromFile(fileName, FileMode.Open, null, 0, MemoryMappedFileAccess.Read);
+ }
+ catch (IOException)
+ {
+ return null;
+ }
+ }
}
static unsafe bool TryReadWebCilSegment(BinaryReader reader, out WebcilHeader webcilHeader, out long metadataOffset, out long webcilOffset, [NotNullWhen(true)] out SectionHeader[]? sectionHeaders)
diff --git a/ICSharpCode.Decompiler/Properties/DecompilerVersionInfo.template.cs b/ICSharpCode.Decompiler/Properties/DecompilerVersionInfo.template.cs
index 5472ee6d0..ee12ad9b8 100644
--- a/ICSharpCode.Decompiler/Properties/DecompilerVersionInfo.template.cs
+++ b/ICSharpCode.Decompiler/Properties/DecompilerVersionInfo.template.cs
@@ -4,7 +4,7 @@
public const string Minor = "0";
public const string Build = "0";
public const string Revision = "$INSERTREVISION$";
- public const string VersionName = "preview1";
+ public const string VersionName = "preview2";
public const string FullVersion = Major + "." + Minor + "." + Build + ".$INSERTREVISION$$INSERTBRANCHPOSTFIX$$INSERTVERSIONNAMEPOSTFIX$";
public const string FullVersionWithShortCommitHash = FullVersion + "-$INSERTSHORTCOMMITHASH$";
diff --git a/ICSharpCode.ILSpyX/FileLoaders/ArchiveFileLoader.cs b/ICSharpCode.ILSpyX/FileLoaders/ArchiveFileLoader.cs
index 6d6cda73a..4fb869d80 100644
--- a/ICSharpCode.ILSpyX/FileLoaders/ArchiveFileLoader.cs
+++ b/ICSharpCode.ILSpyX/FileLoaders/ArchiveFileLoader.cs
@@ -23,8 +23,13 @@ namespace ICSharpCode.ILSpyX.FileLoaders
{
public sealed class ArchiveFileLoader : IFileLoader
{
- public Task Load(string fileName, Stream stream, FileLoadSettings settings)
+ public Task Load(string fileName, Stream stream, FileLoadContext settings)
{
+ if (settings.ParentBundle != null)
+ {
+ return Task.FromResult(null);
+ }
+
try
{
var zip = LoadedPackage.FromZipFile(fileName);
diff --git a/ICSharpCode.ILSpyX/FileLoaders/BundleFileLoader.cs b/ICSharpCode.ILSpyX/FileLoaders/BundleFileLoader.cs
index 4f0ec5bfe..9d5daaff7 100644
--- a/ICSharpCode.ILSpyX/FileLoaders/BundleFileLoader.cs
+++ b/ICSharpCode.ILSpyX/FileLoaders/BundleFileLoader.cs
@@ -23,8 +23,13 @@ namespace ICSharpCode.ILSpyX.FileLoaders
{
public sealed class BundleFileLoader : IFileLoader
{
- public Task Load(string fileName, Stream stream, FileLoadSettings settings)
+ public Task Load(string fileName, Stream stream, FileLoadContext settings)
{
+ if (settings.ParentBundle != null)
+ {
+ return Task.FromResult(null);
+ }
+
var bundle = LoadedPackage.FromBundle(fileName);
var result = bundle != null ? new LoadResult { Package = bundle } : null;
return Task.FromResult(result);
diff --git a/ICSharpCode.ILSpyX/FileLoaders/LoadResult.cs b/ICSharpCode.ILSpyX/FileLoaders/LoadResult.cs
index 73c771a8d..ee4fea611 100644
--- a/ICSharpCode.ILSpyX/FileLoaders/LoadResult.cs
+++ b/ICSharpCode.ILSpyX/FileLoaders/LoadResult.cs
@@ -29,12 +29,14 @@ namespace ICSharpCode.ILSpyX.FileLoaders
public MetadataFile? MetadataFile { get; init; }
public Exception? FileLoadException { get; init; }
public LoadedPackage? Package { get; init; }
+
+ public bool IsSuccess => FileLoadException == null;
}
- public record FileLoadSettings(bool ApplyWinRTProjections);
+ public record FileLoadContext(bool ApplyWinRTProjections, LoadedAssembly? ParentBundle);
public interface IFileLoader
{
- Task Load(string fileName, Stream stream, FileLoadSettings settings);
+ Task Load(string fileName, Stream stream, FileLoadContext context);
}
}
diff --git a/ICSharpCode.ILSpyX/FileLoaders/MetadataFileLoader.cs b/ICSharpCode.ILSpyX/FileLoaders/MetadataFileLoader.cs
index c7de1fb8d..f70d236b6 100644
--- a/ICSharpCode.ILSpyX/FileLoaders/MetadataFileLoader.cs
+++ b/ICSharpCode.ILSpyX/FileLoaders/MetadataFileLoader.cs
@@ -29,7 +29,7 @@ namespace ICSharpCode.ILSpyX.FileLoaders
{
public sealed class MetadataFileLoader : IFileLoader
{
- public Task Load(string fileName, Stream stream, FileLoadSettings settings)
+ public Task Load(string fileName, Stream stream, FileLoadContext settings)
{
try
{
diff --git a/ICSharpCode.ILSpyX/FileLoaders/WebCilFileLoader.cs b/ICSharpCode.ILSpyX/FileLoaders/WebCilFileLoader.cs
index 441ff10fb..4b20b3962 100644
--- a/ICSharpCode.ILSpyX/FileLoaders/WebCilFileLoader.cs
+++ b/ICSharpCode.ILSpyX/FileLoaders/WebCilFileLoader.cs
@@ -26,13 +26,18 @@ namespace ICSharpCode.ILSpyX.FileLoaders
{
public sealed class WebCilFileLoader : IFileLoader
{
- public Task Load(string fileName, Stream stream, FileLoadSettings settings)
+ public Task Load(string fileName, Stream stream, FileLoadContext settings)
{
+ if (settings.ParentBundle != null)
+ {
+ return Task.FromResult(null);
+ }
+
MetadataReaderOptions options = settings.ApplyWinRTProjections
- ? MetadataReaderOptions.ApplyWindowsRuntimeProjections
- : MetadataReaderOptions.None;
+ ? MetadataReaderOptions.ApplyWindowsRuntimeProjections
+ : MetadataReaderOptions.None;
- var wasm = WebCilFile.FromStream(fileName, options);
+ var wasm = WebCilFile.FromFile(fileName, options);
var result = wasm != null ? new LoadResult { MetadataFile = wasm } : null;
return Task.FromResult(result);
}
diff --git a/ICSharpCode.ILSpyX/FileLoaders/XamarinCompressedFileLoader.cs b/ICSharpCode.ILSpyX/FileLoaders/XamarinCompressedFileLoader.cs
index bce1c8918..349822f41 100644
--- a/ICSharpCode.ILSpyX/FileLoaders/XamarinCompressedFileLoader.cs
+++ b/ICSharpCode.ILSpyX/FileLoaders/XamarinCompressedFileLoader.cs
@@ -31,7 +31,7 @@ namespace ICSharpCode.ILSpyX.FileLoaders
{
public sealed class XamarinCompressedFileLoader : IFileLoader
{
- public async Task Load(string fileName, Stream stream, FileLoadSettings settings)
+ public async Task Load(string fileName, Stream stream, FileLoadContext context)
{
const uint CompressedDataMagic = 0x5A4C4158; // Magic used for Xamarin compressed module header ('XALZ', little-endian)
using var fileReader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true);
@@ -54,7 +54,7 @@ namespace ICSharpCode.ILSpyX.FileLoaders
// Load module from decompressed data buffer
using (var uncompressedStream = new MemoryStream(dst, writable: false))
{
- MetadataReaderOptions options = settings.ApplyWinRTProjections
+ MetadataReaderOptions options = context.ApplyWinRTProjections
? MetadataReaderOptions.ApplyWindowsRuntimeProjections
: MetadataReaderOptions.None;
diff --git a/ICSharpCode.ILSpyX/LoadedAssembly.cs b/ICSharpCode.ILSpyX/LoadedAssembly.cs
index cee856141..169012d44 100644
--- a/ICSharpCode.ILSpyX/LoadedAssembly.cs
+++ b/ICSharpCode.ILSpyX/LoadedAssembly.cs
@@ -314,7 +314,7 @@ namespace ICSharpCode.ILSpyX
async Task LoadAsync(Task? streamTask)
{
using var stream = await PrepareStream();
- FileLoadSettings settings = new FileLoadSettings(applyWinRTProjections);
+ FileLoadContext settings = new FileLoadContext(applyWinRTProjections, ParentBundle);
LoadResult? result = null;
@@ -322,16 +322,33 @@ namespace ICSharpCode.ILSpyX
{
foreach (var loader in fileLoaders.RegisteredLoaders)
{
+ // In each iteration any of the following things may happen:
+ // Load returns null because the loader is unable to handle the file, we simply continue without recording the result.
+ // Load returns a non-null value that is either a valid result or an exception:
+ // - if it's a success, we use that and end the loop,
+ // - if it's an error, we remember the error, discarding any previous errors.
+ // Load throws an exception, remember the error, discarding any previous errors.
stream.Position = 0;
- result = await loader.Load(fileName, stream, settings).ConfigureAwait(false);
- if (result != null)
+ try
{
- break;
+ var nextResult = await loader.Load(fileName, stream, settings).ConfigureAwait(false);
+ if (nextResult != null)
+ {
+ result = nextResult;
+ if (result.IsSuccess)
+ {
+ break;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ result = new LoadResult { FileLoadException = ex };
}
}
}
- if (result == null)
+ if (result?.IsSuccess != true)
{
stream.Position = 0;
try
diff --git a/ILSpy.AddIn.Shared/Commands/OpenCodeItemCommand.cs b/ILSpy.AddIn.Shared/Commands/OpenCodeItemCommand.cs
index 718304a33..8da05f8bb 100644
--- a/ILSpy.AddIn.Shared/Commands/OpenCodeItemCommand.cs
+++ b/ILSpy.AddIn.Shared/Commands/OpenCodeItemCommand.cs
@@ -140,7 +140,7 @@ namespace ICSharpCode.ILSpy.AddIn.Commands
return;
}
- OpenAssembliesInILSpy(new ILSpyParameters(validRefs.Select(r => r.AssemblyFile), "/navigateTo:" +
+ OpenAssembliesInILSpy(new ILSpyParameters(validRefs.Select(r => r.AssemblyFile), "--navigateto:" +
(symbol.OriginalDefinition ?? symbol).GetDocumentationCommentId()));
}
diff --git a/ILSpy.AddIn.Shared/ILSpyAddInPackage.cs b/ILSpy.AddIn.Shared/ILSpyAddInPackage.cs
index 195940b68..14f3f9227 100644
--- a/ILSpy.AddIn.Shared/ILSpyAddInPackage.cs
+++ b/ILSpy.AddIn.Shared/ILSpyAddInPackage.cs
@@ -97,6 +97,24 @@ namespace ICSharpCode.ILSpy.AddIn
OpenReferenceCommand.Register(this);
OpenCodeItemCommand.Register(this);
}
+
+ protected override int QueryClose(out bool canClose)
+ {
+ var tempFiles = ILSpyInstance.TempFiles;
+ while (tempFiles.TryPop(out var filename))
+ {
+ try
+ {
+ System.IO.File.Delete(filename);
+ }
+ catch (Exception)
+ {
+ }
+ }
+
+ return base.QueryClose(out canClose);
+ }
+
#endregion
public void ShowMessage(string format, params object[] items)
diff --git a/ILSpy.AddIn.Shared/ILSpyInstance.cs b/ILSpy.AddIn.Shared/ILSpyInstance.cs
index dc90e39a2..42059aa77 100644
--- a/ILSpy.AddIn.Shared/ILSpyInstance.cs
+++ b/ILSpy.AddIn.Shared/ILSpyInstance.cs
@@ -1,11 +1,9 @@
-using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
-using System.Text;
-using System.Threading.Tasks;
namespace ICSharpCode.ILSpy.AddIn
{
@@ -23,8 +21,9 @@ namespace ICSharpCode.ILSpy.AddIn
class ILSpyInstance
{
- readonly ILSpyParameters parameters;
+ internal static readonly ConcurrentStack TempFiles = new ConcurrentStack();
+ readonly ILSpyParameters parameters;
public ILSpyInstance(ILSpyParameters parameters = null)
{
this.parameters = parameters;
@@ -47,85 +46,30 @@ namespace ICSharpCode.ILSpy.AddIn
{
var commandLineArguments = parameters?.AssemblyFileNames?.Concat(parameters.Arguments);
string ilSpyExe = GetILSpyPath();
- var process = new Process() {
- StartInfo = new ProcessStartInfo() {
- FileName = ilSpyExe,
- UseShellExecute = false,
- Arguments = "/navigateTo:none"
- }
- };
- process.Start();
+
+ const string defaultOptions = "--navigateto:none";
+ string argumentsToPass = defaultOptions;
if ((commandLineArguments != null) && commandLineArguments.Any())
{
- // Only need a message to started process if there are any parameters to pass
- SendMessage(ilSpyExe, "ILSpy:\r\n" + string.Join(Environment.NewLine, commandLineArguments), true);
- }
- }
+ string assemblyArguments = string.Join("\r\n", commandLineArguments);
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD110:Observe result of async calls", Justification = "")]
- void SendMessage(string ilSpyExe, string message, bool activate)
- {
- string expectedProcessName = Path.GetFileNameWithoutExtension(ilSpyExe);
- // We wait asynchronously until target window can be found and try to find it multiple times
- Task.Run(async () => {
- bool success = false;
- int remainingAttempts = 20;
- do
- {
- 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, expectedProcessName, 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);
+ string filepath = Path.GetTempFileName();
+ File.WriteAllText(filepath, assemblyArguments);
- // Wait some time before next attempt
- await Task.Delay(500);
- remainingAttempts--;
- } while (!success && (remainingAttempts > 0));
- });
- }
+ TempFiles.Push(filepath);
- unsafe static IntPtr Send(IntPtr hWnd, string message)
- {
- const uint SMTO_NORMAL = 0;
+ argumentsToPass = $"@\"{filepath}\"";
+ }
- 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;
+ var process = new Process() {
+ StartInfo = new ProcessStartInfo() {
+ FileName = ilSpyExe,
+ UseShellExecute = false,
+ Arguments = argumentsToPass
}
- }
+ };
+ process.Start();
}
}
}
diff --git a/ILSpy.AddIn.VS2022/ILSpy.AddIn.VS2022.csproj b/ILSpy.AddIn.VS2022/ILSpy.AddIn.VS2022.csproj
index e0d1db28c..83404ae2f 100644
--- a/ILSpy.AddIn.VS2022/ILSpy.AddIn.VS2022.csproj
+++ b/ILSpy.AddIn.VS2022/ILSpy.AddIn.VS2022.csproj
@@ -70,7 +70,6 @@
-
diff --git a/ILSpy.AddIn/ILSpy.AddIn.csproj b/ILSpy.AddIn/ILSpy.AddIn.csproj
index 5ad17dfb5..9b41766fd 100644
--- a/ILSpy.AddIn/ILSpy.AddIn.csproj
+++ b/ILSpy.AddIn/ILSpy.AddIn.csproj
@@ -76,7 +76,6 @@
-
diff --git a/ILSpy.Installer/ILSpy.Installer.csproj b/ILSpy.Installer/ILSpy.Installer.csproj
index d5cab6a35..6edfaab49 100644
--- a/ILSpy.Installer/ILSpy.Installer.csproj
+++ b/ILSpy.Installer/ILSpy.Installer.csproj
@@ -15,7 +15,7 @@
-
+
diff --git a/ILSpy.Tests/CommandLineArgumentsTests.cs b/ILSpy.Tests/CommandLineArgumentsTests.cs
new file mode 100644
index 000000000..20704250c
--- /dev/null
+++ b/ILSpy.Tests/CommandLineArgumentsTests.cs
@@ -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);
+ }
+ }
+}
diff --git a/ILSpy.Tests/ILSpy.Tests.csproj b/ILSpy.Tests/ILSpy.Tests.csproj
index 79e95586a..17337e7b2 100644
--- a/ILSpy.Tests/ILSpy.Tests.csproj
+++ b/ILSpy.Tests/ILSpy.Tests.csproj
@@ -39,6 +39,7 @@
+
diff --git a/ILSpy/App.xaml.cs b/ILSpy/App.xaml.cs
index 0e88f19ca..26dc4d1a8 100644
--- a/ILSpy/App.xaml.cs
+++ b/ILSpy/App.xaml.cs
@@ -30,10 +30,13 @@ using System.Windows.Documents;
using System.Windows.Navigation;
using System.Windows.Threading;
+using ICSharpCode.ILSpy.AppEnv;
using ICSharpCode.ILSpy.Options;
using ICSharpCode.ILSpyX.Analyzers;
using ICSharpCode.ILSpyX.Settings;
+using Medo.Application;
+
using Microsoft.VisualStudio.Composition;
using TomsToolbox.Wpf.Styles;
@@ -62,13 +65,14 @@ namespace ICSharpCode.ILSpy
ILSpySettings.SettingsFilePathProvider = new ILSpySettingsFilePathProvider();
var cmdArgs = Environment.GetCommandLineArgs().Skip(1);
- App.CommandLineArguments = new CommandLineArguments(cmdArgs);
+ App.CommandLineArguments = CommandLineArguments.Create(cmdArgs);
bool forceSingleInstance = (App.CommandLineArguments.SingleInstance ?? true)
&& !MiscSettingsPanel.CurrentMiscSettings.AllowMultipleInstances;
if (forceSingleInstance)
{
- SingleInstanceHandling.ForceSingleInstance(cmdArgs);
+ SingleInstance.Attach(); // will auto-exit for second instance
+ SingleInstance.NewInstanceDetected += SingleInstance_NewInstanceDetected;
}
InitializeComponent();
@@ -87,6 +91,24 @@ namespace ICSharpCode.ILSpy
Hyperlink.RequestNavigateEvent,
new RequestNavigateEventHandler(Window_RequestNavigate));
ILSpyTraceListener.Install();
+
+ if (App.CommandLineArguments.ArgumentsParser.IsShowingInformation)
+ {
+ MessageBox.Show(App.CommandLineArguments.ArgumentsParser.GetHelpText(), "ILSpy Command Line Arguments");
+ }
+
+ if (App.CommandLineArguments.ArgumentsParser.RemainingArguments.Any())
+ {
+ string unknownArguments = string.Join(", ", App.CommandLineArguments.ArgumentsParser.RemainingArguments);
+ MessageBox.Show(unknownArguments, "ILSpy Unknown Command Line Arguments Passed");
+ }
+ }
+
+ private static void SingleInstance_NewInstanceDetected(object sender, NewInstanceEventArgs e)
+ {
+#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
+ ICSharpCode.ILSpy.MainWindow.Instance.HandleSingleInstanceCommandLineArguments(e.Args);
+#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}
static Assembly ResolvePluginDependencies(AssemblyLoadContext context, AssemblyName assemblyName)
diff --git a/ILSpy/AppEnv/AppEnvironment.cs b/ILSpy/AppEnv/AppEnvironment.cs
new file mode 100644
index 000000000..15d779f4e
--- /dev/null
+++ b/ILSpy/AppEnv/AppEnvironment.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace ICSharpCode.ILSpy.AppEnv
+{
+ public static class AppEnvironment
+ {
+ public static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+ }
+}
diff --git a/ILSpy/AppEnv/CommandLineArguments.cs b/ILSpy/AppEnv/CommandLineArguments.cs
new file mode 100644
index 000000000..dea96c13f
--- /dev/null
+++ b/ILSpy/AppEnv/CommandLineArguments.cs
@@ -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 AssembliesToLoad = new List();
+ 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 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("-n|--navigateto ",
+ "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("-s|--search ",
+ "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("-l|--language ",
+ "Selects the specified language.\r\nExample: 'ILSpy --language:C#' or 'ILSpy --language IL'",
+ CommandOptionType.SingleValue);
+ oLanguage.DefaultValue = null;
+
+ var oConfig = app.Option("-c|--config ",
+ "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;
+ }
+ }
+}
diff --git a/ILSpy/CommandLineTools.cs b/ILSpy/AppEnv/CommandLineTools.cs
similarity index 92%
rename from ILSpy/CommandLineTools.cs
rename to ILSpy/AppEnv/CommandLineTools.cs
index 898981996..7379db12e 100644
--- a/ILSpy/CommandLineTools.cs
+++ b/ILSpy/AppEnv/CommandLineTools.cs
@@ -18,9 +18,10 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Text;
-namespace ICSharpCode.ILSpy
+namespace ICSharpCode.ILSpy.AppEnv
{
public class CommandLineTools
{
@@ -110,6 +111,26 @@ namespace ICSharpCode.ILSpy
b.Append('"');
}
}
+
+ public 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
+ {
+ if (argument.StartsWith("@"))
+ {
+ return "@" + FullyQualifyPath(argument.Substring(1));
+ }
+ return Path.Combine(Environment.CurrentDirectory, argument);
+ }
+ catch (ArgumentException)
+ {
+ return argument;
+ }
+ }
}
// Source: https://github.com/dotnet/runtime/blob/bc9fc5a774d96f95abe0ea5c90fac48b38ed2e67/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs#L574-L606
diff --git a/ILSpy/AppEnv/SingleInstance.cs b/ILSpy/AppEnv/SingleInstance.cs
new file mode 100644
index 000000000..e264c6b62
--- /dev/null
+++ b/ILSpy/AppEnv/SingleInstance.cs
@@ -0,0 +1,290 @@
+// Source: https://github.com/medo64/Medo/blob/main/src/Medo/Application/SingleInstance.cs
+
+/* Josip Medved * 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;
+
+///
+/// Handles detection and communication of programs multiple instances.
+/// This class is thread safe.
+///
+public static class SingleInstance
+{
+
+ private static Mutex? _mtxFirstInstance;
+ private static Thread? _thread;
+ private static readonly object _syncRoot = new();
+
+
+ ///
+ /// Returns true if this application is not already started.
+ /// Another instance is contacted via named pipe.
+ ///
+ /// API call failed.
+ 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();
+ }
+
+ ///
+ /// Returns true if this application is not already started.
+ /// Another instance is contacted via named pipe.
+ ///
+ /// If true, application will exit after informing another instance.
+ /// API call failed.
+ 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;
+ }
+ }
+ }
+
+ ///
+ /// Gets whether there is another instance running.
+ /// It temporary creates mutex.
+ ///
+ 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);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Occurs in first instance when new instance is detected.
+ ///
+ public static event EventHandler? NewInstanceDetected;
+
+
+ ///
+ /// Thread function.
+ ///
+ 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(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;
+ }
+
+}
+
+
+///
+/// Arguments for newly detected application instance.
+///
+public sealed class NewInstanceEventArgs : EventArgs
+{
+ ///
+ /// Creates new instance.
+ ///
+ /// Command line.
+ /// String array containing the command line arguments in the same format as Environment.GetCommandLineArgs.
+ internal NewInstanceEventArgs(string commandLine, string[] commandLineArgs)
+ {
+ CommandLine = commandLine;
+ _commandLineArgs = new string[commandLineArgs.Length];
+ Array.Copy(commandLineArgs, _commandLineArgs, _commandLineArgs.Length);
+ }
+
+ ///
+ /// Gets the command line.
+ ///
+ public string CommandLine { get; }
+
+ private readonly string[] _commandLineArgs;
+ ///
+ /// Returns a string array containing the command line arguments.
+ ///
+ public string[] GetCommandLineArgs()
+ {
+ var argCopy = new string[_commandLineArgs.Length];
+ Array.Copy(_commandLineArgs, argCopy, argCopy.Length);
+ return argCopy;
+ }
+
+ ///
+ /// Gets a string array containing the command line arguments without the name of exectuable.
+ ///
+ public string[] Args {
+ get {
+ var argCopy = new string[_commandLineArgs.Length - 1];
+ Array.Copy(_commandLineArgs, 1, argCopy, 0, argCopy.Length);
+ return argCopy;
+ }
+ }
+
+}
diff --git a/ILSpy/CommandLineArguments.cs b/ILSpy/CommandLineArguments.cs
deleted file mode 100644
index c6c1b5947..000000000
--- a/ILSpy/CommandLineArguments.cs
+++ /dev/null
@@ -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 AssembliesToLoad = new List();
- public bool? SingleInstance;
- public string NavigateTo;
- public string Search;
- public string Language;
- public bool NoActivate;
- public string ConfigFile;
-
- public CommandLineArguments(IEnumerable 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);
- }
- }
- }
- }
-}
diff --git a/ILSpy/Commands/OpenFromGacCommand.cs b/ILSpy/Commands/OpenFromGacCommand.cs
index 8f72d3b6a..a077ceb9d 100644
--- a/ILSpy/Commands/OpenFromGacCommand.cs
+++ b/ILSpy/Commands/OpenFromGacCommand.cs
@@ -16,12 +16,19 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
+using ICSharpCode.ILSpy.AppEnv;
using ICSharpCode.ILSpy.Properties;
+
namespace ICSharpCode.ILSpy
{
[ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.OpenFrom_GAC), MenuIcon = "Images/AssemblyListGAC", MenuCategory = nameof(Resources.Open), MenuOrder = 1)]
sealed class OpenFromGacCommand : SimpleCommand
{
+ public override bool CanExecute(object parameter)
+ {
+ return AppEnvironment.IsWindows;
+ }
+
public override void Execute(object parameter)
{
OpenFromGacDialog dlg = new OpenFromGacDialog();
diff --git a/ILSpy/Commands/ScopeSearchToAssembly.cs b/ILSpy/Commands/ScopeSearchToAssembly.cs
index 861e7c8f5..1ec6ef73b 100644
--- a/ILSpy/Commands/ScopeSearchToAssembly.cs
+++ b/ILSpy/Commands/ScopeSearchToAssembly.cs
@@ -20,6 +20,7 @@
using System;
using ICSharpCode.Decompiler.TypeSystem;
+using ICSharpCode.ILSpy.AppEnv;
using ICSharpCode.ILSpy.Properties;
using ICSharpCode.ILSpy.TreeNodes;
diff --git a/ILSpy/Commands/ScopeSearchToNamespace.cs b/ILSpy/Commands/ScopeSearchToNamespace.cs
index 2c5fead92..11a411ed6 100644
--- a/ILSpy/Commands/ScopeSearchToNamespace.cs
+++ b/ILSpy/Commands/ScopeSearchToNamespace.cs
@@ -18,6 +18,7 @@
using System;
using ICSharpCode.Decompiler.TypeSystem;
+using ICSharpCode.ILSpy.AppEnv;
using ICSharpCode.ILSpy.Properties;
using ICSharpCode.ILSpy.TreeNodes;
diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj
index 93feab3a1..0ecb0caad 100644
--- a/ILSpy/ILSpy.csproj
+++ b/ILSpy/ILSpy.csproj
@@ -45,6 +45,7 @@
+
@@ -65,7 +66,6 @@
-
diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs
index 999abdca3..10525cbb8 100644
--- a/ILSpy/MainWindow.xaml.cs
+++ b/ILSpy/MainWindow.xaml.cs
@@ -44,6 +44,7 @@ using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.TypeSystem.Implementation;
using ICSharpCode.ILSpy.Analyzers;
+using ICSharpCode.ILSpy.AppEnv;
using ICSharpCode.ILSpy.Commands;
using ICSharpCode.ILSpy.Docking;
using ICSharpCode.ILSpy.Options;
@@ -599,12 +600,7 @@ namespace ICSharpCode.ILSpy
{
base.OnSourceInitialized(e);
PresentationSource source = PresentationSource.FromVisual(this);
- HwndSource hwndSource = source as HwndSource;
- if (hwndSource != null)
- {
- hwndSource.AddHook(WndProc);
- }
- SingleInstanceHandling.ReleaseSingleInstanceMutex();
+
// Validate and Set Window Bounds
Rect bounds = Rect.Transform(sessionSettings.WindowBounds, source.CompositionTarget.TransformToDevice);
var boundsRect = new System.Drawing.Rectangle((int)bounds.Left, (int)bounds.Top, (int)bounds.Width, (int)bounds.Height);
@@ -623,35 +619,6 @@ namespace ICSharpCode.ILSpy
this.WindowState = sessionSettings.WindowState;
}
- unsafe IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
- {
- if (msg == NativeMethods.WM_COPYDATA)
- {
- CopyDataStruct* copyData = (CopyDataStruct*)lParam;
- string data = new string((char*)copyData->Buffer, 0, copyData->Size / sizeof(char));
- if (data.StartsWith("ILSpy:\r\n", StringComparison.Ordinal))
- {
- data = data.Substring(8);
- List lines = new List();
- using (StringReader r = new StringReader(data))
- {
- string line;
- while ((line = r.ReadLine()) != null)
- lines.Add(line);
- }
- var args = new CommandLineArguments(lines);
- if (HandleCommandLineArguments(args))
- {
- if (!args.NoActivate && WindowState == WindowState.Minimized)
- WindowState = WindowState.Normal;
- HandleCommandLineArgumentsAfterShowList(args);
- handled = true;
- return (IntPtr)1;
- }
- }
- }
- return IntPtr.Zero;
- }
#endregion
protected override void OnKeyDown(KeyEventArgs e)
@@ -685,6 +652,21 @@ namespace ICSharpCode.ILSpy
List commandLineLoadedAssemblies = new List();
+ internal async Task HandleSingleInstanceCommandLineArguments(string[] args)
+ {
+ var cmdArgs = CommandLineArguments.Create(args);
+
+ await Dispatcher.InvokeAsync(() => {
+ if (HandleCommandLineArguments(cmdArgs))
+ {
+ if (!cmdArgs.NoActivate && WindowState == WindowState.Minimized)
+ WindowState = WindowState.Normal;
+
+ HandleCommandLineArgumentsAfterShowList(cmdArgs);
+ }
+ });
+ }
+
bool HandleCommandLineArguments(CommandLineArguments args)
{
LoadAssemblies(args.AssembliesToLoad, commandLineLoadedAssemblies, focusNode: false);
diff --git a/ILSpy/NativeMethods.cs b/ILSpy/NativeMethods.cs
index d24aeba6d..a5c0cea62 100644
--- a/ILSpy/NativeMethods.cs
+++ b/ILSpy/NativeMethods.cs
@@ -17,99 +17,23 @@
// DEALINGS IN THE SOFTWARE.
using System;
-using System.ComponentModel;
-using System.Diagnostics;
using System.Runtime.InteropServices;
-using System.Text;
namespace ICSharpCode.ILSpy
{
- static class NativeMethods
+ // Uses https://learn.microsoft.com/en-us/dotnet/standard/native-interop/pinvoke-source-generation
+ internal static partial class NativeMethods
{
- public const uint WM_COPYDATA = 0x4a;
+ const int S_OK = 0;
- [DllImport("user32.dll", CharSet = CharSet.Auto)]
- [return: MarshalAs(UnmanagedType.Bool)]
- internal static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
-
- [DllImport("user32.dll", CharSet = CharSet.Auto)]
- internal static extern unsafe int GetWindowThreadProcessId(IntPtr hWnd, int* lpdwProcessId);
-
- [DllImport("user32.dll", CharSet = CharSet.Unicode)]
- static extern int GetWindowText(IntPtr hWnd, [Out] StringBuilder title, int size);
-
- public static string GetWindowText(IntPtr hWnd, int maxLength)
- {
- StringBuilder b = new StringBuilder(maxLength + 1);
- if (GetWindowText(hWnd, b, b.Capacity) != 0)
- return b.ToString();
- else
- return string.Empty;
- }
-
- [DllImport("user32.dll", CharSet = CharSet.Auto)]
- internal static extern IntPtr SendMessageTimeout(
- IntPtr hWnd, uint msg, IntPtr wParam, ref CopyDataStruct lParam,
- uint flags, uint timeout, out IntPtr result);
-
- [DllImport("user32.dll", CharSet = CharSet.Auto)]
- [return: MarshalAs(UnmanagedType.Bool)]
- internal static extern bool SetForegroundWindow(IntPtr hWnd);
-
- public unsafe static string GetProcessNameFromWindow(IntPtr hWnd)
- {
- int processId;
- GetWindowThreadProcessId(hWnd, &processId);
- try
- {
- using (var p = Process.GetProcessById(processId))
- {
- return p.ProcessName;
- }
- }
- catch (ArgumentException ex)
- {
- Debug.WriteLine(ex.Message);
- return null;
- }
- catch (InvalidOperationException ex)
- {
- Debug.WriteLine(ex.Message);
- return null;
- }
- catch (Win32Exception ex)
- {
- Debug.WriteLine(ex.Message);
- return null;
- }
- }
-
- [DllImport("dwmapi.dll", PreserveSig = true)]
- public static extern int DwmSetWindowAttribute(IntPtr hwnd, DwmWindowAttribute attr, ref int attrValue, int attrSize);
+ [LibraryImport("dwmapi.dll")]
+ internal static partial int DwmSetWindowAttribute(IntPtr hwnd, DwmWindowAttribute attr, ref int attrValue, int attrSize);
public static bool UseImmersiveDarkMode(IntPtr hWnd, bool enable)
{
int darkMode = enable ? 1 : 0;
- int hr = DwmSetWindowAttribute(hWnd, DwmWindowAttribute.UseImmersiveDarkMode, ref darkMode, sizeof(int));
- return hr >= 0;
- }
- }
-
- [return: MarshalAs(UnmanagedType.Bool)]
- delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
-
- [StructLayout(LayoutKind.Sequential)]
- struct CopyDataStruct
- {
- public IntPtr Padding;
- public int Size;
- public IntPtr Buffer;
-
- public CopyDataStruct(IntPtr padding, int size, IntPtr buffer)
- {
- this.Padding = padding;
- this.Size = size;
- this.Buffer = buffer;
+ int hResult = DwmSetWindowAttribute(hWnd, DwmWindowAttribute.UseImmersiveDarkMode, ref darkMode, sizeof(int));
+ return hResult > S_OK;
}
}
diff --git a/ILSpy/Options/MiscSettingsPanel.xaml b/ILSpy/Options/MiscSettingsPanel.xaml
index 09a9c5485..502f65fcc 100644
--- a/ILSpy/Options/MiscSettingsPanel.xaml
+++ b/ILSpy/Options/MiscSettingsPanel.xaml
@@ -11,7 +11,7 @@
-
+
diff --git a/ILSpy/Options/MiscSettingsViewModel.cs b/ILSpy/Options/MiscSettingsViewModel.cs
index 6e9562e1b..de505c349 100644
--- a/ILSpy/Options/MiscSettingsViewModel.cs
+++ b/ILSpy/Options/MiscSettingsViewModel.cs
@@ -16,7 +16,6 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
-using System;
using System.ComponentModel;
using System.IO;
using System.Reflection;
@@ -24,6 +23,7 @@ using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Input;
+using ICSharpCode.ILSpy.AppEnv;
using ICSharpCode.ILSpy.Commands;
using ICSharpCode.ILSpyX.Settings;
@@ -41,7 +41,10 @@ namespace ICSharpCode.ILSpy.Options
AllowMultipleInstances = s.AllowMultipleInstances;
LoadPreviousAssemblies = s.LoadPreviousAssemblies;
- AddRemoveShellIntegrationCommand = new DelegateCommand