Browse Source

Merge branch 'master' into feature/ReferenceAssemblyOverly

pull/3013/head
workgroupengineering 1 year ago committed by GitHub
parent
commit
8977b751f8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 7
      Directory.Packages.props
  2. 11
      ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs
  3. 21
      ICSharpCode.Decompiler/Metadata/WebCilFile.cs
  4. 2
      ICSharpCode.Decompiler/Properties/DecompilerVersionInfo.template.cs
  5. 7
      ICSharpCode.ILSpyX/FileLoaders/ArchiveFileLoader.cs
  6. 7
      ICSharpCode.ILSpyX/FileLoaders/BundleFileLoader.cs
  7. 6
      ICSharpCode.ILSpyX/FileLoaders/LoadResult.cs
  8. 2
      ICSharpCode.ILSpyX/FileLoaders/MetadataFileLoader.cs
  9. 9
      ICSharpCode.ILSpyX/FileLoaders/WebCilFileLoader.cs
  10. 4
      ICSharpCode.ILSpyX/FileLoaders/XamarinCompressedFileLoader.cs
  11. 25
      ICSharpCode.ILSpyX/LoadedAssembly.cs
  12. 2
      ILSpy.AddIn.Shared/Commands/OpenCodeItemCommand.cs
  13. 18
      ILSpy.AddIn.Shared/ILSpyAddInPackage.cs
  14. 94
      ILSpy.AddIn.Shared/ILSpyInstance.cs
  15. 1
      ILSpy.AddIn.VS2022/ILSpy.AddIn.VS2022.csproj
  16. 1
      ILSpy.AddIn/ILSpy.AddIn.csproj
  17. 2
      ILSpy.Installer/ILSpy.Installer.csproj
  18. 125
      ILSpy.Tests/CommandLineArgumentsTests.cs
  19. 1
      ILSpy.Tests/ILSpy.Tests.csproj
  20. 26
      ILSpy/App.xaml.cs
  21. 9
      ILSpy/AppEnv/AppEnvironment.cs
  22. 119
      ILSpy/AppEnv/CommandLineArguments.cs
  23. 23
      ILSpy/AppEnv/CommandLineTools.cs
  24. 290
      ILSpy/AppEnv/SingleInstance.cs
  25. 65
      ILSpy/CommandLineArguments.cs
  26. 7
      ILSpy/Commands/OpenFromGacCommand.cs
  27. 1
      ILSpy/Commands/ScopeSearchToAssembly.cs
  28. 1
      ILSpy/Commands/ScopeSearchToNamespace.cs
  29. 2
      ILSpy/ILSpy.csproj
  30. 52
      ILSpy/MainWindow.xaml.cs
  31. 90
      ILSpy/NativeMethods.cs
  32. 2
      ILSpy/Options/MiscSettingsPanel.xaml
  33. 6
      ILSpy/Options/MiscSettingsViewModel.cs
  34. 6
      ILSpy/Properties/launchSettings.json
  35. 1
      ILSpy/Search/SearchPane.cs
  36. 154
      ILSpy/SingleInstanceHandling.cs
  37. 3
      ILSpy/TreeNodes/PackageFolderTreeNode.cs
  38. 1
      SharpTreeView/ICSharpCode.TreeView.csproj
  39. 6
      SharpTreeView/SharpTreeViewTextSearch.cs
  40. 71
      doc/Command Line.txt
  41. 13
      publishlocaldev.ps1

7
Directory.Packages.props

@ -14,6 +14,7 @@
<PackageVersion Include="Iced" Version="1.21.0" /> <PackageVersion Include="Iced" Version="1.21.0" />
<PackageVersion Include="JunitXml.TestLogger" Version="3.1.12" /> <PackageVersion Include="JunitXml.TestLogger" Version="3.1.12" />
<PackageVersion Include="K4os.Compression.LZ4" Version="1.3.8" /> <PackageVersion Include="K4os.Compression.LZ4" Version="1.3.8" />
<PackageVersion Include="McMaster.Extensions.CommandLineUtils" Version="4.1.1" />
<PackageVersion Include="McMaster.Extensions.Hosting.CommandLine" Version="4.1.1" /> <PackageVersion Include="McMaster.Extensions.Hosting.CommandLine" Version="4.1.1" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" /> <PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
<PackageVersion Include="Microsoft.CodeAnalysis.VisualBasic" Version="4.9.2" /> <PackageVersion Include="Microsoft.CodeAnalysis.VisualBasic" Version="4.9.2" />
@ -21,11 +22,11 @@
<PackageVersion Include="Microsoft.DiaSymReader" Version="1.4.0" /> <PackageVersion Include="Microsoft.DiaSymReader" Version="1.4.0" />
<PackageVersion Include="Microsoft.DiaSymReader.Native" Version="17.0.0-beta1.21524.1" /> <PackageVersion Include="Microsoft.DiaSymReader.Native" Version="17.0.0-beta1.21524.1" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="8.0.0" /> <PackageVersion Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" /> <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageVersion Include="Microsoft.NETCore.ILAsm" Version="8.0.0" /> <PackageVersion Include="Microsoft.NETCore.ILAsm" Version="8.0.0" />
<PackageVersion Include="Microsoft.NETCore.ILDAsm" Version="8.0.0" /> <PackageVersion Include="Microsoft.NETCore.ILDAsm" Version="8.0.0" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" /> <PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<PackageVersion Include="Microsoft.VisualStudio.Composition" Version="17.7.40" /> <PackageVersion Include="Microsoft.VisualStudio.Composition" Version="17.10.37" />
<PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.77" /> <PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.77" />
<PackageVersion Include="Mono.Cecil" Version="0.11.5" /> <PackageVersion Include="Mono.Cecil" Version="0.11.5" />
<PackageVersion Include="NaturalSort.Extension" Version="4.3.0" /> <PackageVersion Include="NaturalSort.Extension" Version="4.3.0" />
@ -33,7 +34,7 @@
<PackageVersion Include="NSubstitute.Analyzers.CSharp" Version="1.0.17" /> <PackageVersion Include="NSubstitute.Analyzers.CSharp" Version="1.0.17" />
<PackageVersion Include="NUnit" Version="4.1.0" /> <PackageVersion Include="NUnit" Version="4.1.0" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.5.0" /> <PackageVersion Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageVersion Include="NuGet.Protocol" Version="6.9.1" /> <PackageVersion Include="NuGet.Protocol" Version="6.10.0" />
<PackageVersion Include="PowerShellStandard.Library" Version="5.1.1" /> <PackageVersion Include="PowerShellStandard.Library" Version="5.1.1" />
<PackageVersion Include="System.Collections.Immutable" Version="8.0.0" /> <PackageVersion Include="System.Collections.Immutable" Version="8.0.0" />
<PackageVersion Include="System.ComponentModel.Composition" Version="8.0.0" /> <PackageVersion Include="System.ComponentModel.Composition" Version="8.0.0" />

11
ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs

@ -20,7 +20,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Reflection;
using ICSharpCode.Decompiler.CSharp.Resolver; using ICSharpCode.Decompiler.CSharp.Resolver;
using ICSharpCode.Decompiler.Semantics; using ICSharpCode.Decompiler.Semantics;
@ -1423,7 +1422,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms
value = call.Arguments[0]; value = call.Arguments[0];
if (call.Arguments.Count == 2) if (call.Arguments.Count == 2)
return MatchGetTypeFromHandle(call.Arguments[1], out type); 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 true;
} }
return false; return false;

21
ICSharpCode.Decompiler/Metadata/WebCilFile.cs

@ -47,9 +47,14 @@ namespace ICSharpCode.Decompiler.Metadata
this.WasmSections = wasmSections; 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); var view = memoryMappedFile.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read);
try try
{ {
@ -124,6 +129,18 @@ namespace ICSharpCode.Decompiler.Metadata
{ {
view?.Dispose(); 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) static unsafe bool TryReadWebCilSegment(BinaryReader reader, out WebcilHeader webcilHeader, out long metadataOffset, out long webcilOffset, [NotNullWhen(true)] out SectionHeader[]? sectionHeaders)

2
ICSharpCode.Decompiler/Properties/DecompilerVersionInfo.template.cs

@ -4,7 +4,7 @@
public const string Minor = "0"; public const string Minor = "0";
public const string Build = "0"; public const string Build = "0";
public const string Revision = "$INSERTREVISION$"; 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 FullVersion = Major + "." + Minor + "." + Build + ".$INSERTREVISION$$INSERTBRANCHPOSTFIX$$INSERTVERSIONNAMEPOSTFIX$";
public const string FullVersionWithShortCommitHash = FullVersion + "-$INSERTSHORTCOMMITHASH$"; public const string FullVersionWithShortCommitHash = FullVersion + "-$INSERTSHORTCOMMITHASH$";

7
ICSharpCode.ILSpyX/FileLoaders/ArchiveFileLoader.cs

@ -23,8 +23,13 @@ namespace ICSharpCode.ILSpyX.FileLoaders
{ {
public sealed class ArchiveFileLoader : IFileLoader public sealed class ArchiveFileLoader : IFileLoader
{ {
public Task<LoadResult?> Load(string fileName, Stream stream, FileLoadSettings settings) public Task<LoadResult?> Load(string fileName, Stream stream, FileLoadContext settings)
{ {
if (settings.ParentBundle != null)
{
return Task.FromResult<LoadResult?>(null);
}
try try
{ {
var zip = LoadedPackage.FromZipFile(fileName); var zip = LoadedPackage.FromZipFile(fileName);

7
ICSharpCode.ILSpyX/FileLoaders/BundleFileLoader.cs

@ -23,8 +23,13 @@ namespace ICSharpCode.ILSpyX.FileLoaders
{ {
public sealed class BundleFileLoader : IFileLoader public sealed class BundleFileLoader : IFileLoader
{ {
public Task<LoadResult?> Load(string fileName, Stream stream, FileLoadSettings settings) public Task<LoadResult?> Load(string fileName, Stream stream, FileLoadContext settings)
{ {
if (settings.ParentBundle != null)
{
return Task.FromResult<LoadResult?>(null);
}
var bundle = LoadedPackage.FromBundle(fileName); var bundle = LoadedPackage.FromBundle(fileName);
var result = bundle != null ? new LoadResult { Package = bundle } : null; var result = bundle != null ? new LoadResult { Package = bundle } : null;
return Task.FromResult(result); return Task.FromResult(result);

6
ICSharpCode.ILSpyX/FileLoaders/LoadResult.cs

@ -29,12 +29,14 @@ namespace ICSharpCode.ILSpyX.FileLoaders
public MetadataFile? MetadataFile { get; init; } public MetadataFile? MetadataFile { get; init; }
public Exception? FileLoadException { get; init; } public Exception? FileLoadException { get; init; }
public LoadedPackage? Package { 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 public interface IFileLoader
{ {
Task<LoadResult?> Load(string fileName, Stream stream, FileLoadSettings settings); Task<LoadResult?> Load(string fileName, Stream stream, FileLoadContext context);
} }
} }

2
ICSharpCode.ILSpyX/FileLoaders/MetadataFileLoader.cs

@ -29,7 +29,7 @@ namespace ICSharpCode.ILSpyX.FileLoaders
{ {
public sealed class MetadataFileLoader : IFileLoader public sealed class MetadataFileLoader : IFileLoader
{ {
public Task<LoadResult?> Load(string fileName, Stream stream, FileLoadSettings settings) public Task<LoadResult?> Load(string fileName, Stream stream, FileLoadContext settings)
{ {
try try
{ {

9
ICSharpCode.ILSpyX/FileLoaders/WebCilFileLoader.cs

@ -26,13 +26,18 @@ namespace ICSharpCode.ILSpyX.FileLoaders
{ {
public sealed class WebCilFileLoader : IFileLoader public sealed class WebCilFileLoader : IFileLoader
{ {
public Task<LoadResult?> Load(string fileName, Stream stream, FileLoadSettings settings) public Task<LoadResult?> Load(string fileName, Stream stream, FileLoadContext settings)
{ {
if (settings.ParentBundle != null)
{
return Task.FromResult<LoadResult?>(null);
}
MetadataReaderOptions options = settings.ApplyWinRTProjections MetadataReaderOptions options = settings.ApplyWinRTProjections
? MetadataReaderOptions.ApplyWindowsRuntimeProjections ? MetadataReaderOptions.ApplyWindowsRuntimeProjections
: MetadataReaderOptions.None; : MetadataReaderOptions.None;
var wasm = WebCilFile.FromStream(fileName, options); var wasm = WebCilFile.FromFile(fileName, options);
var result = wasm != null ? new LoadResult { MetadataFile = wasm } : null; var result = wasm != null ? new LoadResult { MetadataFile = wasm } : null;
return Task.FromResult(result); return Task.FromResult(result);
} }

4
ICSharpCode.ILSpyX/FileLoaders/XamarinCompressedFileLoader.cs

@ -31,7 +31,7 @@ namespace ICSharpCode.ILSpyX.FileLoaders
{ {
public sealed class XamarinCompressedFileLoader : IFileLoader public sealed class XamarinCompressedFileLoader : IFileLoader
{ {
public async Task<LoadResult?> Load(string fileName, Stream stream, FileLoadSettings settings) public async Task<LoadResult?> Load(string fileName, Stream stream, FileLoadContext context)
{ {
const uint CompressedDataMagic = 0x5A4C4158; // Magic used for Xamarin compressed module header ('XALZ', little-endian) const uint CompressedDataMagic = 0x5A4C4158; // Magic used for Xamarin compressed module header ('XALZ', little-endian)
using var fileReader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true); using var fileReader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true);
@ -54,7 +54,7 @@ namespace ICSharpCode.ILSpyX.FileLoaders
// Load module from decompressed data buffer // Load module from decompressed data buffer
using (var uncompressedStream = new MemoryStream(dst, writable: false)) using (var uncompressedStream = new MemoryStream(dst, writable: false))
{ {
MetadataReaderOptions options = settings.ApplyWinRTProjections MetadataReaderOptions options = context.ApplyWinRTProjections
? MetadataReaderOptions.ApplyWindowsRuntimeProjections ? MetadataReaderOptions.ApplyWindowsRuntimeProjections
: MetadataReaderOptions.None; : MetadataReaderOptions.None;

25
ICSharpCode.ILSpyX/LoadedAssembly.cs

@ -314,7 +314,7 @@ namespace ICSharpCode.ILSpyX
async Task<LoadResult> LoadAsync(Task<Stream?>? streamTask) async Task<LoadResult> LoadAsync(Task<Stream?>? streamTask)
{ {
using var stream = await PrepareStream(); using var stream = await PrepareStream();
FileLoadSettings settings = new FileLoadSettings(applyWinRTProjections); FileLoadContext settings = new FileLoadContext(applyWinRTProjections, ParentBundle);
LoadResult? result = null; LoadResult? result = null;
@ -322,16 +322,33 @@ namespace ICSharpCode.ILSpyX
{ {
foreach (var loader in fileLoaders.RegisteredLoaders) 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; stream.Position = 0;
result = await loader.Load(fileName, stream, settings).ConfigureAwait(false); try
if (result != null) {
var nextResult = await loader.Load(fileName, stream, settings).ConfigureAwait(false);
if (nextResult != null)
{
result = nextResult;
if (result.IsSuccess)
{ {
break; break;
} }
} }
} }
catch (Exception ex)
{
result = new LoadResult { FileLoadException = ex };
}
}
}
if (result == null) if (result?.IsSuccess != true)
{ {
stream.Position = 0; stream.Position = 0;
try try

2
ILSpy.AddIn.Shared/Commands/OpenCodeItemCommand.cs

@ -140,7 +140,7 @@ namespace ICSharpCode.ILSpy.AddIn.Commands
return; return;
} }
OpenAssembliesInILSpy(new ILSpyParameters(validRefs.Select(r => r.AssemblyFile), "/navigateTo:" + OpenAssembliesInILSpy(new ILSpyParameters(validRefs.Select(r => r.AssemblyFile), "--navigateto:" +
(symbol.OriginalDefinition ?? symbol).GetDocumentationCommentId())); (symbol.OriginalDefinition ?? symbol).GetDocumentationCommentId()));
} }

18
ILSpy.AddIn.Shared/ILSpyAddInPackage.cs

@ -97,6 +97,24 @@ namespace ICSharpCode.ILSpy.AddIn
OpenReferenceCommand.Register(this); OpenReferenceCommand.Register(this);
OpenCodeItemCommand.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 #endregion
public void ShowMessage(string format, params object[] items) public void ShowMessage(string format, params object[] items)

94
ILSpy.AddIn.Shared/ILSpyInstance.cs

@ -1,11 +1,9 @@
using System; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace ICSharpCode.ILSpy.AddIn namespace ICSharpCode.ILSpy.AddIn
{ {
@ -23,8 +21,9 @@ namespace ICSharpCode.ILSpy.AddIn
class ILSpyInstance class ILSpyInstance
{ {
readonly ILSpyParameters parameters; internal static readonly ConcurrentStack<string> TempFiles = new ConcurrentStack<string>();
readonly ILSpyParameters parameters;
public ILSpyInstance(ILSpyParameters parameters = null) public ILSpyInstance(ILSpyParameters parameters = null)
{ {
this.parameters = parameters; this.parameters = parameters;
@ -47,85 +46,30 @@ namespace ICSharpCode.ILSpy.AddIn
{ {
var commandLineArguments = parameters?.AssemblyFileNames?.Concat(parameters.Arguments); var commandLineArguments = parameters?.AssemblyFileNames?.Concat(parameters.Arguments);
string ilSpyExe = GetILSpyPath(); string ilSpyExe = GetILSpyPath();
var process = new Process() {
StartInfo = new ProcessStartInfo() {
FileName = ilSpyExe,
UseShellExecute = false,
Arguments = "/navigateTo:none"
}
};
process.Start();
if ((commandLineArguments != null) && commandLineArguments.Any()) const string defaultOptions = "--navigateto:none";
{ string argumentsToPass = defaultOptions;
// 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);
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD110:Observe result of async calls", Justification = "<Pending>")] if ((commandLineArguments != null) && commandLineArguments.Any())
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) string assemblyArguments = string.Join("\r\n", commandLineArguments);
NativeMethods.SetForegroundWindow(hWnd);
success = true;
return false; // stop enumeration
}
}
}
return true; // continue enumeration
}, IntPtr.Zero);
// Wait some time before next attempt string filepath = Path.GetTempFileName();
await Task.Delay(500); File.WriteAllText(filepath, assemblyArguments);
remainingAttempts--;
} while (!success && (remainingAttempts > 0));
});
}
unsafe static IntPtr Send(IntPtr hWnd, string message) TempFiles.Push(filepath);
{
const uint SMTO_NORMAL = 0;
CopyDataStruct lParam; argumentsToPass = $"@\"{filepath}\"";
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();
} }
} }
} }

1
ILSpy.AddIn.VS2022/ILSpy.AddIn.VS2022.csproj

@ -70,7 +70,6 @@
<Compile Include="..\ICSharpCode.Decompiler\Metadata\LightJson\Serialization\TextScanner.cs" Link="Decompiler\LightJson\Serialization\TextScanner.cs" /> <Compile Include="..\ICSharpCode.Decompiler\Metadata\LightJson\Serialization\TextScanner.cs" Link="Decompiler\LightJson\Serialization\TextScanner.cs" />
<Compile Include="..\ICSharpCode.Decompiler\Metadata\UniversalAssemblyResolver.cs" Link="UniversalAssemblyResolver.cs" /> <Compile Include="..\ICSharpCode.Decompiler\Metadata\UniversalAssemblyResolver.cs" Link="UniversalAssemblyResolver.cs" />
<Compile Include="..\ICSharpCode.Decompiler\Util\EmptyList.cs" Link="Decompiler\EmptyList.cs" /> <Compile Include="..\ICSharpCode.Decompiler\Util\EmptyList.cs" Link="Decompiler\EmptyList.cs" />
<Compile Include="..\ILSpy\NativeMethods.cs" Link="NativeMethods.cs" />
<Compile Include="Decompiler\Dummy.cs" /> <Compile Include="Decompiler\Dummy.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>

1
ILSpy.AddIn/ILSpy.AddIn.csproj

@ -76,7 +76,6 @@
<Compile Include="..\ICSharpCode.Decompiler\Metadata\LightJson\Serialization\TextScanner.cs" Link="Decompiler\LightJson\Serialization\TextScanner.cs" /> <Compile Include="..\ICSharpCode.Decompiler\Metadata\LightJson\Serialization\TextScanner.cs" Link="Decompiler\LightJson\Serialization\TextScanner.cs" />
<Compile Include="..\ICSharpCode.Decompiler\Metadata\UniversalAssemblyResolver.cs" Link="UniversalAssemblyResolver.cs" /> <Compile Include="..\ICSharpCode.Decompiler\Metadata\UniversalAssemblyResolver.cs" Link="UniversalAssemblyResolver.cs" />
<Compile Include="..\ICSharpCode.Decompiler\Util\EmptyList.cs" Link="Decompiler\EmptyList.cs" /> <Compile Include="..\ICSharpCode.Decompiler\Util\EmptyList.cs" Link="Decompiler\EmptyList.cs" />
<Compile Include="..\ILSpy\NativeMethods.cs" Link="NativeMethods.cs" />
<Compile Include="Decompiler\Dummy.cs" /> <Compile Include="Decompiler\Dummy.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>

2
ILSpy.Installer/ILSpy.Installer.csproj

@ -15,7 +15,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="WixSharp" Version="1.25.2" /> <PackageReference Include="WixSharp" Version="1.25.3" />
<PackageReference Include="WixSharp.wix.bin" Version="3.14.1" /> <PackageReference Include="WixSharp.wix.bin" Version="3.14.1" />
</ItemGroup> </ItemGroup>

125
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);
}
}
}

1
ILSpy.Tests/ILSpy.Tests.csproj

@ -39,6 +39,7 @@
<Compile Include="Analyzers\MethodUsesAnalyzerTests.cs" /> <Compile Include="Analyzers\MethodUsesAnalyzerTests.cs" />
<Compile Include="Analyzers\TestCases\MainAssembly.cs" /> <Compile Include="Analyzers\TestCases\MainAssembly.cs" />
<Compile Include="Analyzers\TypeUsedByAnalyzerTests.cs" /> <Compile Include="Analyzers\TypeUsedByAnalyzerTests.cs" />
<Compile Include="CommandLineArgumentsTests.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

26
ILSpy/App.xaml.cs

@ -30,10 +30,13 @@ using System.Windows.Documents;
using System.Windows.Navigation; using System.Windows.Navigation;
using System.Windows.Threading; using System.Windows.Threading;
using ICSharpCode.ILSpy.AppEnv;
using ICSharpCode.ILSpy.Options; using ICSharpCode.ILSpy.Options;
using ICSharpCode.ILSpyX.Analyzers; using ICSharpCode.ILSpyX.Analyzers;
using ICSharpCode.ILSpyX.Settings; using ICSharpCode.ILSpyX.Settings;
using Medo.Application;
using Microsoft.VisualStudio.Composition; using Microsoft.VisualStudio.Composition;
using TomsToolbox.Wpf.Styles; using TomsToolbox.Wpf.Styles;
@ -62,13 +65,14 @@ namespace ICSharpCode.ILSpy
ILSpySettings.SettingsFilePathProvider = new ILSpySettingsFilePathProvider(); ILSpySettings.SettingsFilePathProvider = new ILSpySettingsFilePathProvider();
var cmdArgs = Environment.GetCommandLineArgs().Skip(1); var cmdArgs = Environment.GetCommandLineArgs().Skip(1);
App.CommandLineArguments = new CommandLineArguments(cmdArgs); App.CommandLineArguments = CommandLineArguments.Create(cmdArgs);
bool forceSingleInstance = (App.CommandLineArguments.SingleInstance ?? true) bool forceSingleInstance = (App.CommandLineArguments.SingleInstance ?? true)
&& !MiscSettingsPanel.CurrentMiscSettings.AllowMultipleInstances; && !MiscSettingsPanel.CurrentMiscSettings.AllowMultipleInstances;
if (forceSingleInstance) if (forceSingleInstance)
{ {
SingleInstanceHandling.ForceSingleInstance(cmdArgs); SingleInstance.Attach(); // will auto-exit for second instance
SingleInstance.NewInstanceDetected += SingleInstance_NewInstanceDetected;
} }
InitializeComponent(); InitializeComponent();
@ -87,6 +91,24 @@ namespace ICSharpCode.ILSpy
Hyperlink.RequestNavigateEvent, Hyperlink.RequestNavigateEvent,
new RequestNavigateEventHandler(Window_RequestNavigate)); new RequestNavigateEventHandler(Window_RequestNavigate));
ILSpyTraceListener.Install(); 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) static Assembly ResolvePluginDependencies(AssemblyLoadContext context, AssemblyName assemblyName)

9
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);
}
}

119
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<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;
}
}
}

23
ILSpy/CommandLineTools.cs → ILSpy/AppEnv/CommandLineTools.cs

@ -18,9 +18,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Text; using System.Text;
namespace ICSharpCode.ILSpy namespace ICSharpCode.ILSpy.AppEnv
{ {
public class CommandLineTools public class CommandLineTools
{ {
@ -110,6 +111,26 @@ namespace ICSharpCode.ILSpy
b.Append('"'); 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 // Source: https://github.com/dotnet/runtime/blob/bc9fc5a774d96f95abe0ea5c90fac48b38ed2e67/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs#L574-L606

290
ILSpy/AppEnv/SingleInstance.cs

@ -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;
}
}
}

65
ILSpy/CommandLineArguments.cs

@ -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);
}
}
}
}
}

7
ILSpy/Commands/OpenFromGacCommand.cs

@ -16,12 +16,19 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using ICSharpCode.ILSpy.AppEnv;
using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.Properties;
namespace ICSharpCode.ILSpy namespace ICSharpCode.ILSpy
{ {
[ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.OpenFrom_GAC), MenuIcon = "Images/AssemblyListGAC", MenuCategory = nameof(Resources.Open), MenuOrder = 1)] [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.OpenFrom_GAC), MenuIcon = "Images/AssemblyListGAC", MenuCategory = nameof(Resources.Open), MenuOrder = 1)]
sealed class OpenFromGacCommand : SimpleCommand sealed class OpenFromGacCommand : SimpleCommand
{ {
public override bool CanExecute(object parameter)
{
return AppEnvironment.IsWindows;
}
public override void Execute(object parameter) public override void Execute(object parameter)
{ {
OpenFromGacDialog dlg = new OpenFromGacDialog(); OpenFromGacDialog dlg = new OpenFromGacDialog();

1
ILSpy/Commands/ScopeSearchToAssembly.cs

@ -20,6 +20,7 @@
using System; using System;
using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.ILSpy.AppEnv;
using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.Properties;
using ICSharpCode.ILSpy.TreeNodes; using ICSharpCode.ILSpy.TreeNodes;

1
ILSpy/Commands/ScopeSearchToNamespace.cs

@ -18,6 +18,7 @@
using System; using System;
using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.ILSpy.AppEnv;
using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.Properties;
using ICSharpCode.ILSpy.TreeNodes; using ICSharpCode.ILSpy.TreeNodes;

2
ILSpy/ILSpy.csproj

@ -45,6 +45,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="AvalonEdit" /> <PackageReference Include="AvalonEdit" />
<PackageReference Include="Dirkster.AvalonDock.Themes.VS2013" /> <PackageReference Include="Dirkster.AvalonDock.Themes.VS2013" />
<PackageReference Include="McMaster.Extensions.CommandLineUtils" />
<PackageReference Include="Microsoft.VisualStudio.Composition" /> <PackageReference Include="Microsoft.VisualStudio.Composition" />
<PackageReference Include="DataGridExtensions" /> <PackageReference Include="DataGridExtensions" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" /> <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" />
@ -65,7 +66,6 @@
<EmbeddedResource Include="TextView\ILAsm-Mode.xshd" /> <EmbeddedResource Include="TextView\ILAsm-Mode.xshd" />
<EmbeddedResource Include="TextView\Asm-Mode.xshd" /> <EmbeddedResource Include="TextView\Asm-Mode.xshd" />
<EmbeddedResource Include="TextView\XML-Mode.xshd" /> <EmbeddedResource Include="TextView\XML-Mode.xshd" />
<None Remove="Properties\launchSettings.json" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

52
ILSpy/MainWindow.xaml.cs

@ -44,6 +44,7 @@ using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.TypeSystem.Implementation; using ICSharpCode.Decompiler.TypeSystem.Implementation;
using ICSharpCode.ILSpy.Analyzers; using ICSharpCode.ILSpy.Analyzers;
using ICSharpCode.ILSpy.AppEnv;
using ICSharpCode.ILSpy.Commands; using ICSharpCode.ILSpy.Commands;
using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.Docking;
using ICSharpCode.ILSpy.Options; using ICSharpCode.ILSpy.Options;
@ -599,12 +600,7 @@ namespace ICSharpCode.ILSpy
{ {
base.OnSourceInitialized(e); base.OnSourceInitialized(e);
PresentationSource source = PresentationSource.FromVisual(this); PresentationSource source = PresentationSource.FromVisual(this);
HwndSource hwndSource = source as HwndSource;
if (hwndSource != null)
{
hwndSource.AddHook(WndProc);
}
SingleInstanceHandling.ReleaseSingleInstanceMutex();
// Validate and Set Window Bounds // Validate and Set Window Bounds
Rect bounds = Rect.Transform(sessionSettings.WindowBounds, source.CompositionTarget.TransformToDevice); 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); 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; 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<string> lines = new List<string>();
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 #endregion
protected override void OnKeyDown(KeyEventArgs e) protected override void OnKeyDown(KeyEventArgs e)
@ -685,6 +652,21 @@ namespace ICSharpCode.ILSpy
List<LoadedAssembly> commandLineLoadedAssemblies = new List<LoadedAssembly>(); List<LoadedAssembly> commandLineLoadedAssemblies = new List<LoadedAssembly>();
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) bool HandleCommandLineArguments(CommandLineArguments args)
{ {
LoadAssemblies(args.AssembliesToLoad, commandLineLoadedAssemblies, focusNode: false); LoadAssemblies(args.AssembliesToLoad, commandLineLoadedAssemblies, focusNode: false);

90
ILSpy/NativeMethods.cs

@ -17,99 +17,23 @@
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System; using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text;
namespace ICSharpCode.ILSpy 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)] [LibraryImport("dwmapi.dll")]
[return: MarshalAs(UnmanagedType.Bool)] internal static partial int DwmSetWindowAttribute(IntPtr hwnd, DwmWindowAttribute attr, ref int attrValue, int attrSize);
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);
public static bool UseImmersiveDarkMode(IntPtr hWnd, bool enable) public static bool UseImmersiveDarkMode(IntPtr hWnd, bool enable)
{ {
int darkMode = enable ? 1 : 0; int darkMode = enable ? 1 : 0;
int hr = DwmSetWindowAttribute(hWnd, DwmWindowAttribute.UseImmersiveDarkMode, ref darkMode, sizeof(int)); int hResult = DwmSetWindowAttribute(hWnd, DwmWindowAttribute.UseImmersiveDarkMode, ref darkMode, sizeof(int));
return hr >= 0; return hResult > S_OK;
}
}
[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;
} }
} }

2
ILSpy/Options/MiscSettingsPanel.xaml

@ -11,7 +11,7 @@
<StackPanel Margin="10"> <StackPanel Margin="10">
<CheckBox IsChecked="{Binding AllowMultipleInstances}" Content="{x:Static properties:Resources.AllowMultipleInstances}" /> <CheckBox IsChecked="{Binding AllowMultipleInstances}" Content="{x:Static properties:Resources.AllowMultipleInstances}" />
<CheckBox IsChecked="{Binding LoadPreviousAssemblies}" Content="{x:Static properties:Resources.LoadAssembliesThatWereLoadedInTheLastInstance}"/> <CheckBox IsChecked="{Binding LoadPreviousAssemblies}" Content="{x:Static properties:Resources.LoadAssembliesThatWereLoadedInTheLastInstance}"/>
<Button Command="{Binding AddRemoveShellIntegrationCommand}" Content="{Binding AddRemoveShellIntegrationText}" Margin="3" /> <Button Command="{Binding AddRemoveShellIntegrationCommand}" IsEnabled="{Binding EnableShellIntegrationCommand}" Content="{Binding AddRemoveShellIntegrationText}" Margin="3" />
</StackPanel> </StackPanel>
</GroupBox> </GroupBox>
</StackPanel> </StackPanel>

6
ILSpy/Options/MiscSettingsViewModel.cs

@ -16,7 +16,6 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System;
using System.ComponentModel; using System.ComponentModel;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
@ -24,6 +23,7 @@ using System.Runtime.CompilerServices;
using System.Windows; using System.Windows;
using System.Windows.Input; using System.Windows.Input;
using ICSharpCode.ILSpy.AppEnv;
using ICSharpCode.ILSpy.Commands; using ICSharpCode.ILSpy.Commands;
using ICSharpCode.ILSpyX.Settings; using ICSharpCode.ILSpyX.Settings;
@ -41,8 +41,11 @@ namespace ICSharpCode.ILSpy.Options
AllowMultipleInstances = s.AllowMultipleInstances; AllowMultipleInstances = s.AllowMultipleInstances;
LoadPreviousAssemblies = s.LoadPreviousAssemblies; LoadPreviousAssemblies = s.LoadPreviousAssemblies;
if (EnableShellIntegrationCommand)
{
AddRemoveShellIntegrationCommand = new DelegateCommand<object>(AddRemoveShellIntegration); AddRemoveShellIntegrationCommand = new DelegateCommand<object>(AddRemoveShellIntegration);
} }
}
/// <summary> /// <summary>
/// Allow multiple instances. /// Allow multiple instances.
@ -73,6 +76,7 @@ namespace ICSharpCode.ILSpy.Options
} }
public ICommand AddRemoveShellIntegrationCommand { get; } public ICommand AddRemoveShellIntegrationCommand { get; }
public bool EnableShellIntegrationCommand => AppEnvironment.IsWindows;
const string rootPath = @"Software\Classes\{0}\shell"; const string rootPath = @"Software\Classes\{0}\shell";
const string fullPath = @"Software\Classes\{0}\shell\Open with ILSpy\command"; const string fullPath = @"Software\Classes\{0}\shell\Open with ILSpy\command";

6
ILSpy/Properties/launchSettings.json

@ -2,12 +2,12 @@
"profiles": { "profiles": {
"ILSpy": { "ILSpy": {
"commandName": "Executable", "commandName": "Executable",
"executablePath": ".\\ILSpy.exe", "executablePath": "./ilspy.exe",
"commandLineArgs": "/separate" "commandLineArgs": "--newinstance"
}, },
"ILSpy single-instance": { "ILSpy single-instance": {
"commandName": "Executable", "commandName": "Executable",
"executablePath": ".\\ILSpy.exe" "executablePath": "./ilspy.exe"
} }
} }
} }

1
ILSpy/Search/SearchPane.cs

@ -32,6 +32,7 @@ using System.Windows.Input;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Threading; using System.Windows.Threading;
using ICSharpCode.ILSpy.AppEnv;
using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.Docking;
using ICSharpCode.ILSpy.ViewModels; using ICSharpCode.ILSpy.ViewModels;
using ICSharpCode.ILSpyX; using ICSharpCode.ILSpyX;

154
ILSpy/SingleInstanceHandling.cs

@ -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
}
}

3
ILSpy/TreeNodes/PackageFolderTreeNode.cs

@ -67,7 +67,8 @@ namespace ICSharpCode.ILSpy.TreeNodes
} }
foreach (var entry in root.Entries.OrderBy(e => e.Name)) foreach (var entry in root.Entries.OrderBy(e => e.Name))
{ {
if (entry.Name.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) if (entry.Name.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)
|| entry.Name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
{ {
var asm = root.ResolveFileName(entry.Name); var asm = root.ResolveFileName(entry.Name);
if (asm != null) if (asm != null)

1
SharpTreeView/ICSharpCode.TreeView.csproj

@ -8,6 +8,7 @@
<TargetFramework>net8.0-windows</TargetFramework> <TargetFramework>net8.0-windows</TargetFramework>
<AssemblyOriginatorKeyFile>..\ICSharpCode.Decompiler\ICSharpCode.Decompiler.snk</AssemblyOriginatorKeyFile> <AssemblyOriginatorKeyFile>..\ICSharpCode.Decompiler\ICSharpCode.Decompiler.snk</AssemblyOriginatorKeyFile>
<EnableWindowsTargeting>true</EnableWindowsTargeting> <EnableWindowsTargeting>true</EnableWindowsTargeting>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Debug'"> <PropertyGroup Condition="'$(Configuration)' == 'Debug'">

6
SharpTreeView/SharpTreeViewTextSearch.cs

@ -28,10 +28,10 @@ namespace ICSharpCode.TreeView
/// Custom TextSearch-implementation. /// Custom TextSearch-implementation.
/// Fixes #67 - Moving to class member in tree view by typing in first character of member name selects parent assembly /// Fixes #67 - Moving to class member in tree view by typing in first character of member name selects parent assembly
/// </summary> /// </summary>
public class SharpTreeViewTextSearch : DependencyObject public partial class SharpTreeViewTextSearch : DependencyObject
{ {
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] [LibraryImport("user32.dll")]
static extern int GetDoubleClickTime(); internal static partial int GetDoubleClickTime();
static readonly DependencyPropertyKey TextSearchInstancePropertyKey = DependencyProperty.RegisterAttachedReadOnly("TextSearchInstance", static readonly DependencyPropertyKey TextSearchInstancePropertyKey = DependencyProperty.RegisterAttachedReadOnly("TextSearchInstance",
typeof(SharpTreeViewTextSearch), typeof(SharpTreeViewTextSearch), new FrameworkPropertyMetadata(null)); typeof(SharpTreeViewTextSearch), typeof(SharpTreeViewTextSearch), new FrameworkPropertyMetadata(null));

71
doc/Command Line.txt

@ -1,54 +1,27 @@
ILSpy Command Line Arguments ILSpy Command Line Arguments
Command line arguments can be either options or file names. Usage: <Assemblies> [options]
If an argument is a file name, the file will be opened as assembly and added to the current assembly list. @ResponseFile.rsp
Available options: Arguments:
/singleInstance If ILSpy is already running, activates the existing instance Assemblies Assemblies to load
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. Options:
--newinstance Start a new instance of ILSpy even if the user configuration is set to single-instance
/noActivate Do not activate the existing ILSpy instance. This option has no effect -n|--navigateto <TYPENAME> Navigates to the member specified by the given ID string.
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.
The member is searched for only in the assemblies specified on the command line. The member is searched for only in the assemblies specified on the command line.
Example: 'ILSpy ILSpy.exe /navigateTo:T:ICSharpCode.ILSpy.CommandLineArguments' 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),
The syntax of ID strings is described in appendix A of the C# language specification. 'should not contain' (-term) or 'must contain' (+term); use
/reg(ular)?Ex(pressions)?/ or both - t:/Type(Name)?/...
/language:name Selects the specified language. -l|--language <LANGUAGEIDENTIFIER> Selects the specified language.
Example: 'ILSpy /language:C#' or 'ILSpy /language:IL' Example: 'ILSpy --language:C#' or 'ILSpy --language:IL'
-c|--config <CONFIGFILENAME> Provide a specific configuration file.
WM_COPYDATA (SendMessage API): Example: 'ILSpy --config:myconfig.xml'
ILSpy can be controlled by other programs that send a WM_COPYDATA message to its main window. --noactivate Do not activate the existing ILSpy instance.
The message data must be an Unicode (UTF-16) string starting with "ILSpy:\r\n". This option has no effect if a new ILSpy instance is being started.
All lines except the first ("ILSpy:") in that string are handled as command-line arguments.
There must be exactly one argument per line. Note on @ResponseFile.rsp:
That is, by sending this message: * The response file should contain the arguments, one argument per line (not space-separated!).
ILSpy: * Use it when the list of assemblies is too long to fit on the command line.
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.

13
publishlocaldev.ps1

@ -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…
Cancel
Save