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. 13
      ICSharpCode.ILSpyX/FileLoaders/WebCilFileLoader.cs
  10. 4
      ICSharpCode.ILSpyX/FileLoaders/XamarinCompressedFileLoader.cs
  11. 27
      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. 8
      ILSpy/Options/MiscSettingsViewModel.cs
  34. 22
      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. 77
      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
{ {

13
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;

27
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)
{ {
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; 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() { const string defaultOptions = "--navigateto:none";
FileName = ilSpyExe, string argumentsToPass = defaultOptions;
UseShellExecute = false,
Arguments = "/navigateTo:none"
}
};
process.Start();
if ((commandLineArguments != null) && commandLineArguments.Any()) if ((commandLineArguments != null) && commandLineArguments.Any())
{ {
// Only need a message to started process if there are any parameters to pass string assemblyArguments = string.Join("\r\n", commandLineArguments);
SendMessage(ilSpyExe, "ILSpy:\r\n" + string.Join(Environment.NewLine, commandLineArguments), true);
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD110:Observe result of async calls", Justification = "<Pending>")] string filepath = Path.GetTempFileName();
void SendMessage(string ilSpyExe, string message, bool activate) File.WriteAllText(filepath, assemblyArguments);
{
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);
// Wait some time before next attempt TempFiles.Push(filepath);
await Task.Delay(500);
remainingAttempts--;
} while (!success && (remainingAttempts > 0));
});
}
unsafe static IntPtr Send(IntPtr hWnd, string message) argumentsToPass = $"@\"{filepath}\"";
{ }
const uint SMTO_NORMAL = 0;
CopyDataStruct lParam; var process = new Process() {
lParam.Padding = IntPtr.Zero; StartInfo = new ProcessStartInfo() {
lParam.Size = message.Length * 2; FileName = ilSpyExe,
fixed (char* buffer = message) UseShellExecute = false,
{ Arguments = argumentsToPass
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;
} }
} };
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>

8
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,7 +41,10 @@ namespace ICSharpCode.ILSpy.Options
AllowMultipleInstances = s.AllowMultipleInstances; AllowMultipleInstances = s.AllowMultipleInstances;
LoadPreviousAssemblies = s.LoadPreviousAssemblies; LoadPreviousAssemblies = s.LoadPreviousAssemblies;
AddRemoveShellIntegrationCommand = new DelegateCommand<object>(AddRemoveShellIntegration); if (EnableShellIntegrationCommand)
{
AddRemoveShellIntegrationCommand = new DelegateCommand<object>(AddRemoveShellIntegration);
}
} }
/// <summary> /// <summary>
@ -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";

22
ILSpy/Properties/launchSettings.json

@ -1,13 +1,13 @@
{ {
"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));

77
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. Options:
--newinstance Start a new instance of ILSpy even if the user configuration is set to single-instance
/separate Start up a separate ILSpy instance even if it is already running. -n|--navigateto <TYPENAME> Navigates to the member specified by the given ID string.
The member is searched for only in the assemblies specified on the command line.
/noActivate Do not activate the existing ILSpy instance. This option has no effect Example: 'ILSpy ILSpy.exe --navigateTo:T:ICSharpCode.ILSpy.CommandLineArguments'
if a new ILSpy instance is being started. -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
/list:listname Specifies the name of the assembly list that is loaded initially. /reg(ular)?Ex(pressions)?/ or both - t:/Type(Name)?/...
When this option is not specified, ILSpy loads the previously opened list. -l|--language <LANGUAGEIDENTIFIER> Selects the specified language.
Specify "/list" (without value) to open the default list. Example: 'ILSpy --language:C#' or 'ILSpy --language:IL'
-c|--config <CONFIGFILENAME> Provide a specific configuration file.
When this option is used, ILSpy will activate an existing instance Example: 'ILSpy --config:myconfig.xml'
only if it uses the same list as specified. --noactivate Do not activate the existing ILSpy instance.
This option has no effect if a new ILSpy instance is being started.
[Note: Assembly Lists are not yet implemented]
Note on @ResponseFile.rsp:
/clearList Clears the assembly list before loading the specified assemblies.
[Note: Assembly Lists are not yet implemented] * The response file should contain the arguments, one argument per line (not space-separated!).
* Use it when the list of assemblies is too long to fit on the command line.
/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.
Example: 'ILSpy ILSpy.exe /navigateTo:T:ICSharpCode.ILSpy.CommandLineArguments'
The syntax of ID strings is described in appendix A of the C# language specification.
/language:name Selects the specified language.
Example: 'ILSpy /language:C#' or 'ILSpy /language:IL'
WM_COPYDATA (SendMessage API):
ILSpy can be controlled by other programs that send a WM_COPYDATA message to its main window.
The message data must be an Unicode (UTF-16) string starting with "ILSpy:\r\n".
All lines except the first ("ILSpy:") in that string are handled as command-line arguments.
There must be exactly one argument per line.
That is, by sending this message:
ILSpy:
C:\Assembly.dll
/navigateTo:T:Type
The target ILSpy instance will open C:\Assembly.dll and navigate to the specified type.
ILSpy will return TRUE (1) if it handles the message, and FALSE (0) otherwise.
The /separate option will be ignored; WM_COPYDATA will never start up a new instance.
The /noActivate option has no effect, sending WM_COPYDATA will never activate the window.
Instead, the calling process should use SetForegroundWindow().
If you use /list with WM_COPYDATA, you need to specify /singleInstance as well, otherwise
ILSpy will not handle the message if it has opened a different assembly list.

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