2021-09-20 18:20:01 +02:00

206 lines
6.2 KiB
C#

/*
Copyright (C) 2014-2019 de4dot@gmail.com
This file is part of dnSpy
dnSpy is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
dnSpy is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with dnSpy. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Input;
using dnlib.Threading;
using dnSpy.Contracts.App;
using dnSpy.Contracts.Debugger;
using dnSpy.Contracts.Debugger.DotNet.Metadata;
using dnSpy.Contracts.MVVM;
using dnSpy.Debugger.DotNet.Properties;
using dnSpy.Debugger.DotNet.UI;
namespace dnSpy.Debugger.DotNet.Metadata {
sealed class ModuleLoaderVM : ViewModelBase, IDisposable {
public ICommand CancelCommand => new RelayCommand(a => AskCancel(), a => CanCancel);
readonly UIDispatcher uiDispatcher;
readonly DbgRuntime runtime;
readonly DbgDynamicModuleProvider dbgDynamicModuleProvider;
readonly DynamicModuleDefDocument[] documents;
readonly IMessageBoxService messageBoxService;
readonly CancellationTokenSource cancellationTokenSource;
readonly CancellationToken cancellationToken;
sealed class CancellationTokenImpl : ICancellationToken {
/*readonly*/ CancellationToken cancellationToken;
public CancellationTokenImpl(CancellationToken cancellationToken) => this.cancellationToken = cancellationToken;
public void ThrowIfCancellationRequested() => cancellationToken.ThrowIfCancellationRequested();
}
public ModuleLoaderVM(UIDispatcher uiDispatcher, IMessageBoxService messageBoxService, DbgRuntime runtime, DbgDynamicModuleProvider dbgDynamicModuleProvider, DynamicModuleDefDocument[] documents) {
this.uiDispatcher = uiDispatcher ?? throw new ArgumentNullException(nameof(uiDispatcher));
this.runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
this.dbgDynamicModuleProvider = dbgDynamicModuleProvider ?? throw new ArgumentNullException(nameof(dbgDynamicModuleProvider));
this.documents = documents ?? throw new ArgumentNullException(nameof(documents));
this.messageBoxService = messageBoxService ?? throw new ArgumentNullException(nameof(messageBoxService));
cancellationTokenSource = new CancellationTokenSource();
cancellationToken = cancellationTokenSource.Token;
runtime.Closed += DbgRuntime_Closed;
if (runtime.IsClosed)
Cancel();
else
dbgDynamicModuleProvider.BeginInvoke(LoadFiles_EngineThread);
}
void DbgRuntime_Closed(object? sender, EventArgs e) => Cancel();
public bool CanCancel => cancelling == 0;
volatile int cancelling;
public void AskCancel() {
uiDispatcher.VerifyAccess();
if (cancelling != 0)
return;
if (!HasCompleted) {
var res = messageBoxService.Show(dnSpy_Debugger_DotNet_Resources.CancelLoadingModulesMessage, MsgBoxButton.Yes | MsgBoxButton.No);
if (res != MsgBoxButton.Yes)
return;
}
Cancel();
}
void UI(Action callback) => uiDispatcher.UI(callback);
void Cancel() {
if (Interlocked.Exchange(ref cancelling, 1) != 0)
return;
cancellationTokenSource.Cancel();
cancellationTokenSource.Dispose();
UI(() => OnPropertyChanged(nameof(CanCancel)));
}
public void Dispose() {
runtime.Closed -= DbgRuntime_Closed;
Cancel();
}
public bool WasCanceled {
get => wasCanceled;
set {
uiDispatcher.VerifyAccess();
if (wasCanceled != value) {
wasCanceled = value;
OnPropertyChanged(nameof(WasCanceled));
}
}
}
bool wasCanceled;
public bool HasCompleted {
get => hasCompleted;
set {
uiDispatcher.VerifyAccess();
if (hasCompleted != value) {
hasCompleted = value;
OnPropertyChanged(nameof(HasCompleted));
}
}
}
bool hasCompleted;
public string? CurrentItemName {
get => currentItemName;
set {
uiDispatcher.VerifyAccess();
if (currentItemName != value) {
currentItemName = value;
OnPropertyChanged(nameof(CurrentItemName));
}
}
}
string? currentItemName;
void LoadFiles_EngineThread() {
bool wasCanceled;
try {
wasCanceled = LoadFilesCore_EngineThread();
}
catch (Exception ex) {
wasCanceled = false;
UI(() => messageBoxService.Show(ex));
}
UI(() => {
WasCanceled = wasCanceled;
HasCompleted = true;
OnCompleted?.Invoke(this, EventArgs.Empty);
});
}
bool LoadFilesCore_EngineThread() {
bool wasCanceled = false;
var modules = documents.Select(a => a.DbgModule).ToArray();
dbgDynamicModuleProvider.LoadEverything(modules, started: true);
try {
foreach (var document in documents) {
try {
cancellationToken.ThrowIfCancellationRequested();
if (document.DbgModule.IsClosed)
continue;
UI(() => CurrentItemName = CalculateCurrentItemName(document));
document.ModuleDef!.LoadEverything(new CancellationTokenImpl(cancellationToken));
// Make sure the cache is cleared since there could be new types
if (document.ModuleDef.EnableTypeDefFindCache) {
document.ModuleDef.EnableTypeDefFindCache = false;
document.ModuleDef.EnableTypeDefFindCache = true;
}
}
catch (OperationCanceledException) {
wasCanceled = true;
break;
}
}
}
finally {
dbgDynamicModuleProvider.LoadEverything(modules, started: false);
}
return wasCanceled;
}
public event EventHandler? OnCompleted;
string CalculateCurrentItemName(DynamicModuleDefDocument document) {
var module = document.ModuleDef!;
var sb = new StringBuilder();
sb.Append($"({Array.IndexOf(documents, document) + 1}/{documents.Length}): ");
var asm = module.Assembly;
if (asm is not null) {
if (module.IsManifestModule)
sb.Append(asm.FullName);
else
sb.Append($"{module.Name} ({asm.FullName})");
}
else
sb.Append(module.Name);
return sb.ToString();
}
}
}