diff --git a/Process-Explorer.BLL/HostedServices/ProcessMetricsHostedService.cs b/Process-Explorer.BLL/HostedServices/ProcessMetricsHostedService.cs index df99017..6cfbf9f 100644 --- a/Process-Explorer.BLL/HostedServices/ProcessMetricsHostedService.cs +++ b/Process-Explorer.BLL/HostedServices/ProcessMetricsHostedService.cs @@ -5,13 +5,13 @@ namespace Process_Explorer.BLL.HostedServices { - public class ProcessMetricsHostedService : IHostedService + public class ProcessMetricsHostedService : BackgroundService { - private readonly ILogger _logger = default!; - private readonly IProcessService _service = default!; + private readonly ILogger _logger; + private readonly IProcessService _service; private Timer _timer = default!; - public List Processes { get; private set; } = new(); + public event Action>? ProcessesUpdated; public ProcessMetricsHostedService(IProcessService service, ILogger logger) { @@ -23,7 +23,9 @@ private async void UpdateProcesses(object? state) { try { - Processes = (await _service.GetProcessesInformationAsync()).ToList(); + var processes = (await _service.GetProcessesInformationAsync())!.ToList()!; + + ProcessesUpdated?.Invoke(processes!); _logger.LogInformation("Process list updated successfully."); } @@ -33,16 +35,17 @@ private async void UpdateProcesses(object? state) } } - public Task StartAsync(CancellationToken cancellationToken) + protected override Task ExecuteAsync(CancellationToken stoppingToken) { _timer = new Timer(UpdateProcesses, null, TimeSpan.Zero, TimeSpan.FromSeconds(1)); return Task.CompletedTask; } - public Task StopAsync(CancellationToken cancellationToken) + public override Task StopAsync(CancellationToken cancellationToken) { - _timer?.DisposeAsync(); + _timer?.Dispose(); return Task.CompletedTask; } } + } diff --git a/Process-Explorer.BLL/Models/ProcessInformationDTO.cs b/Process-Explorer.BLL/Models/ProcessInformationDTO.cs index 7a95ca5..f7e1b3b 100644 --- a/Process-Explorer.BLL/Models/ProcessInformationDTO.cs +++ b/Process-Explorer.BLL/Models/ProcessInformationDTO.cs @@ -3,6 +3,9 @@ public class ProcessInformationDTO { public uint PID { get; set; } + public uint PPID { get; set; } + public uint HandlesCount { get; set; } + public uint ThreadsCount { get; set; } public string Name { get; set; } = string.Empty; public string Company { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; diff --git a/Process-Explorer.BLL/Profiles/ProcessInforamtionProfile.cs b/Process-Explorer.BLL/Profiles/ProcessInforamtionProfile.cs index 0b990d6..9887bf6 100644 --- a/Process-Explorer.BLL/Profiles/ProcessInforamtionProfile.cs +++ b/Process-Explorer.BLL/Profiles/ProcessInforamtionProfile.cs @@ -10,6 +10,9 @@ public ProcessInforamtionProfile() { CreateMap(); CreateMap(); + + CreateMap(); + CreateMap(); } } } diff --git a/Process-Explorer.BLL/Services/IProcessService.cs b/Process-Explorer.BLL/Services/IProcessService.cs index e595b34..2f69321 100644 --- a/Process-Explorer.BLL/Services/IProcessService.cs +++ b/Process-Explorer.BLL/Services/IProcessService.cs @@ -8,7 +8,8 @@ public interface IProcessService public Task> GetActiveProcessesAsync(); public Task GetProcessByIdAsync(int pid); public Task?> GetProcessesInformationAsync(); - public Task?> GetProcessesInformationAsync(IEnumerable processes); + public Task?> GetProcessesInformationAsync(IEnumerable processes); + public Task GetProcessInformationByIdAsync(int pid); public Task KillProcess(int pid); } diff --git a/Process-Explorer.BLL/Services/ProcessService.cs b/Process-Explorer.BLL/Services/ProcessService.cs index 506b201..5b89d90 100644 --- a/Process-Explorer.BLL/Services/ProcessService.cs +++ b/Process-Explorer.BLL/Services/ProcessService.cs @@ -55,11 +55,14 @@ public ProcessService(IMapper mapper, ILogger logger) public async Task?> GetProcessesInformationAsync() { _logger.LogDebug("GetProcessesInformation called"); - - return await GetProcessesInformationAsync(await ProcessManager.GetActiveProcessesAsync()); + if (false) + { + //return await GetProcessesInformationAsync(await Native.ProcessManager.GetActiveProcessesAsync()); + } + return await GetProcessesInformationAsync(await Native.ProcessManager.NtGetActiveProcessesAsync()); } - public Task?> GetProcessesInformationAsync(IEnumerable processes) + public Task?> GetProcessesInformationAsync(IEnumerable processes) { _logger.LogDebug("GetProcessesInformation(procesess) called"); try diff --git a/Process-Explorer.GUI/App.xaml.cs b/Process-Explorer.GUI/App.xaml.cs index a95d209..cba307c 100644 --- a/Process-Explorer.GUI/App.xaml.cs +++ b/Process-Explorer.GUI/App.xaml.cs @@ -1,25 +1,39 @@ -using System; -using System.Threading; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.UI.Xaml; -using Process_Explorer.BLL.HostedServices; using Process_Explorer.GUI.Extensions; +using Process_Explorer.GUI.Helpers; +using System; namespace Process_Explorer.GUI { public partial class App : Application { - private IServiceProvider _services = default!; + private IHost _host = default!; + private Window _window = default!; public App() { - InitializeComponent(); - Setup(); + SetupApplication(); + SetupHost(); } - private void Setup() + private void SetupHost() + { + _host = Host.CreateDefaultBuilder() + .ConfigureLogging(logging => + { + logging.ClearProviders(); + logging.AddConsole(); + logging.SetMinimumLevel(LogLevel.Debug); + }) + .ConfigureServices((ctx, services) => services.ConfigureServices()) + .Build(); + } + + private void SetupApplication() { try { @@ -28,32 +42,24 @@ private void Setup() } catch (Exception ex) { - Native.MessageBox.ShowWarning(ex.Message); + ToastNotificationHelper.ShowMessage("Process Explorer Message", ex.Message); } + } - var services = new ServiceCollection(); - - services.AddLogging(logging => - { - logging.ClearProviders(); - logging.AddConsole(); - logging.SetMinimumLevel(LogLevel.Debug); - }); + protected override async void OnLaunched(LaunchActivatedEventArgs args) + { + await _host.StartAsync(); - services.ConfigureServices(); + _window = _host.Services.GetRequiredService(); + _window.Activate(); - _services = services.BuildServiceProvider(); + _window.Closed += OnMainWindowClosed; } - protected override void OnLaunched(LaunchActivatedEventArgs args) + private async void OnMainWindowClosed(object? sender, WindowEventArgs e) { - m_window = _services.GetRequiredService(); - m_window.Activate(); - - var hostedService = _services.GetRequiredService(); - hostedService.StartAsync(CancellationToken.None); + await _host.StopAsync(TimeSpan.FromSeconds(5)); + _host.Dispose(); } - - private Window? m_window; } } diff --git a/Process-Explorer.GUI/Helpers/IconHelper.cs b/Process-Explorer.GUI/Helpers/IconHelper.cs index f3ec739..af2b2a4 100644 --- a/Process-Explorer.GUI/Helpers/IconHelper.cs +++ b/Process-Explorer.GUI/Helpers/IconHelper.cs @@ -31,5 +31,19 @@ public static async Task GetIconAsync(string exePath) return null!; } } + public static async Task GetDefaultIcon() + { + using (var bitmap = SystemIcons.Application.ToBitmap()) + { + var bitmapImage = new Microsoft.UI.Xaml.Media.Imaging.BitmapImage(); + using (var memoryStream = new System.IO.MemoryStream()) + { + bitmap.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Png); + memoryStream.Seek(0, System.IO.SeekOrigin.Begin); + await bitmapImage.SetSourceAsync(memoryStream.AsRandomAccessStream()); + } + return bitmapImage; + } + } } } diff --git a/Process-Explorer.GUI/Helpers/ToastNotificationHelper.cs b/Process-Explorer.GUI/Helpers/ToastNotificationHelper.cs new file mode 100644 index 0000000..2e15042 --- /dev/null +++ b/Process-Explorer.GUI/Helpers/ToastNotificationHelper.cs @@ -0,0 +1,18 @@ +using Microsoft.Windows.AppNotifications; +using Microsoft.Windows.AppNotifications.Builder; + +namespace Process_Explorer.GUI.Helpers +{ + public static class ToastNotificationHelper + { + public static void ShowMessage(string title, string message) + { + var toast = new AppNotificationBuilder() + .AddText(title) + .AddText(message) + .BuildNotification(); + + AppNotificationManager.Default.Show(toast); + } + } +} diff --git a/Process-Explorer.GUI/Process-Explorer.GUI.csproj b/Process-Explorer.GUI/Process-Explorer.GUI.csproj index 3ed1057..4e58f7f 100644 --- a/Process-Explorer.GUI/Process-Explorer.GUI.csproj +++ b/Process-Explorer.GUI/Process-Explorer.GUI.csproj @@ -53,6 +53,8 @@ + + diff --git a/Process-Explorer.GUI/ViewModels/ActionsViewModel.cs b/Process-Explorer.GUI/ViewModels/ActionsViewModel.cs index 0ae4298..97d1e54 100644 --- a/Process-Explorer.GUI/ViewModels/ActionsViewModel.cs +++ b/Process-Explorer.GUI/ViewModels/ActionsViewModel.cs @@ -7,22 +7,19 @@ using Process_Explorer.GUI.Helpers; using Process_Explorer.GUI.Models; using SkiaSharp; -using System; +using System.Collections.Generic; using System.Linq; -using System.Threading; namespace Process_Explorer.GUI.ViewModels { - public partial class ActionsViewModel : ObservableObject, IDisposable + public partial class ActionsViewModel : ObservableObject { private readonly ProcessMetricsHostedService _service = default!; - private readonly Timer _timer = default!; - private readonly DispatcherQueue _dispatcher = default!; - public MemoryUsageChart CpuChart; - public MemoryUsageChart PrivateChart; - public MemoryUsageChart WorkingChart; + public MemoryUsageChart CpuChart = default!; + public MemoryUsageChart PrivateChart = default!; + public MemoryUsageChart WorkingChart = default!; [ObservableProperty] private Visibility _progressBarVisiblity; @@ -33,7 +30,10 @@ public partial class ActionsViewModel : ObservableObject, IDisposable [ObservableProperty] private bool _isLoading = true; - private int _targetProcessId; + [ObservableProperty] + private string _text = "Enter Process Id [PID]"; + + private int _targetProcessId = -1; public int TargetProcessId { get => _targetProcessId; @@ -41,6 +41,8 @@ public int TargetProcessId { if (SetProperty(ref _targetProcessId, value)) { + Text = (value == 0) ? "Idle Process [Information Cannot Be Obtained]" : "Enter Process Id [PID]"; + IsLoading = true; ProcessAddress = "Loading..."; @@ -80,37 +82,35 @@ public ActionsViewModel(ProcessMetricsHostedService service) _service = service; _dispatcher = DispatcherQueue.GetForCurrentThread(); - _timer = new Timer(UpdateMetrics, null, TimeSpan.Zero, TimeSpan.FromSeconds(1)); - - + + KillCommand = new RelayCommand(OnKillButtonClicked); + + _service.ProcessesUpdated += OnProcessesUpdated; } - private void UpdateMetrics(object? state) + private void OnProcessesUpdated(IReadOnlyList processes) { - if (_dispatcher is not null) + _dispatcher.TryEnqueue(() => { - _dispatcher.TryEnqueue(() => - { - - if (_service.Processes.FirstOrDefault(p => p.PID == TargetProcessId) is not null) - { - _model = _service.Processes.FirstOrDefault(p => p.PID == TargetProcessId)!; - - CpuChart.AddValue(_model.CpuUsage); - PrivateChart.AddValue(_model.PrivateBytes); - WorkingChart.AddValue(_model.WorkingSet); - - ProcessAddress = _model.Name; - IsLoading = false; - } - else - { - IsLoading = true; - ProcessAddress = "NULL"; - } - }); - } + var process = processes.FirstOrDefault(p => p.PID == TargetProcessId); + + if (process is not null) + { + _model = process; + CpuChart.AddValue(_model.CpuUsage); + PrivateChart.AddValue(_model.PrivateBytes); + WorkingChart.AddValue(_model.WorkingSet); + + ProcessAddress = _model.Name; + IsLoading = false; + } + else + { + IsLoading = true; + ProcessAddress = "NULL"; + } + }); } public void OnKillButtonClicked() @@ -121,6 +121,9 @@ public void OnKillButtonClicked() } } - public void Dispose() => _timer?.Dispose(); + public void Dispose() + { + _service.ProcessesUpdated -= OnProcessesUpdated; + } } } diff --git a/Process-Explorer.GUI/ViewModels/MetricsViewModel.cs b/Process-Explorer.GUI/ViewModels/MetricsViewModel.cs index 33f4527..74703b6 100644 --- a/Process-Explorer.GUI/ViewModels/MetricsViewModel.cs +++ b/Process-Explorer.GUI/ViewModels/MetricsViewModel.cs @@ -1,19 +1,22 @@ using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.WinUI; +using Microsoft.UI.Dispatching; using Process_Explorer.BLL.HostedServices; +using Process_Explorer.BLL.Models; using Process_Explorer.GUI.Helpers; using Process_Explorer.GUI.Models; using SkiaSharp; using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using System.Threading; namespace Process_Explorer.GUI.ViewModels { public partial class MetricsViewModel : ObservableObject, IDisposable { private readonly ProcessMetricsHostedService _service; - private readonly Timer _timer; + private readonly DispatcherQueue _dispatcher; public ObservableCollection MemorySizes { get; } = MemoryUsageChart.InitializeSizes(); @@ -30,6 +33,7 @@ public partial class MetricsViewModel : ObservableObject, IDisposable public MetricsViewModel(ProcessMetricsHostedService service) { _service = service; + _dispatcher = DispatcherQueue.GetForCurrentThread(); _selectedMemVarPrivateBytes = MemorySizes.Last(); _selectedMemVarWorkingSet = MemorySizes.Last(); @@ -54,24 +58,26 @@ public MetricsViewModel(ProcessMetricsHostedService service) CpuChart.Limit = 100; - _timer = new Timer(UpdateMetrics, null, TimeSpan.Zero, TimeSpan.FromSeconds(1)); + _service.ProcessesUpdated += OnProcessesUpdated; } - private void UpdateMetrics(object? state) + private void OnProcessesUpdated(IReadOnlyList processes) { - var processes = _service.Processes; - if (!processes.Any()) return; var sumP = processes.Sum(p => p.PrivateBytes); var sumW = processes.Sum(p => p.WorkingSet); var sumC = processes.Sum(p => p.CpuUsage); - PrivateChart.AddValue(sumP); - WorkingChart.AddValue(sumW); - CpuChart.AddValue(sumC); + _dispatcher.EnqueueAsync(() => + { + PrivateChart.AddValue(sumP); + WorkingChart.AddValue(sumW); + CpuChart.AddValue(sumC); + }); } + partial void OnSelectedMemVarPrivateBytesChanged(MemorySize oldVal, MemorySize newVal) { PrivateChart.SelectedMemorySize = newVal; @@ -82,6 +88,9 @@ partial void OnSelectedMemVarWorkingSetChanged(MemorySize oldVal, MemorySize new WorkingChart.SelectedMemorySize = newVal; } - public void Dispose() => _timer?.Dispose(); + public void Dispose() + { + _service.ProcessesUpdated -= OnProcessesUpdated; + } } } \ No newline at end of file diff --git a/Process-Explorer.GUI/ViewModels/ProcessInformationViewModel.cs b/Process-Explorer.GUI/ViewModels/ProcessInformationViewModel.cs index 1a4f0f2..0b7dda8 100644 --- a/Process-Explorer.GUI/ViewModels/ProcessInformationViewModel.cs +++ b/Process-Explorer.GUI/ViewModels/ProcessInformationViewModel.cs @@ -1,6 +1,9 @@ using Process_Explorer.BLL.Models; using Process_Explorer.GUI.Helpers; +using System; using System.ComponentModel; +using System.Drawing; +using System.IO; using System.Runtime.CompilerServices; using System.Threading.Tasks; @@ -59,11 +62,30 @@ public uint PrivateBytes } } + public double CpuUsage + { + get => _dto.CpuUsage; + set + { + if (_dto.CpuUsage != value) + { + _dto.CpuUsage = value; + OnPropertyChanged(); + } + } + } + private async Task LoadIconAsync() { var icon = await IconHelper.GetIconAsync(_dto.FilePath); if (icon is not null) + { IconSource = icon; + } + else + { + IconSource = await IconHelper.GetDefaultIcon(); + } } public event PropertyChangedEventHandler? PropertyChanged; diff --git a/Process-Explorer.GUI/ViewModels/ProcessListViewModel.cs b/Process-Explorer.GUI/ViewModels/ProcessListViewModel.cs index b271d5e..500e507 100644 --- a/Process-Explorer.GUI/ViewModels/ProcessListViewModel.cs +++ b/Process-Explorer.GUI/ViewModels/ProcessListViewModel.cs @@ -1,61 +1,42 @@ -using CommunityToolkit.WinUI; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.WinUI; using Microsoft.UI.Dispatching; using Process_Explorer.BLL.HostedServices; +using Process_Explorer.BLL.Models; using System; +using System.Collections.Generic; using System.Collections.ObjectModel; -using System.ComponentModel; using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; namespace Process_Explorer.GUI.ViewModels { - public class ProcessListViewModel : INotifyPropertyChanged, IDisposable + + public class ProcessListViewModel : ObservableRecipient, IDisposable { private readonly ProcessMetricsHostedService _service; - private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread(); - private readonly Timer _timer; + private readonly DispatcherQueue _dispatcher = DispatcherQueue.GetForCurrentThread(); public ObservableCollection ProcessList { get; } = new(); public ProcessListViewModel(ProcessMetricsHostedService service) { _service = service; - _timer = new Timer(OnTimerTick, null, TimeSpan.Zero, TimeSpan.FromSeconds(1)); - LoadProcesses(); + _service.ProcessesUpdated += OnProcessesUpdated; } - private async void OnTimerTick(object? state) => await UpdateProcessesAsync(); - - public void LoadProcesses() - { - var processes = _service.Processes; - _dispatcherQueue.TryEnqueue(() => - { - ProcessList.Clear(); - foreach (var p in processes) - { - if (p.PID == 0) continue; - ProcessList.Add(new ProcessInformationViewModel(p)); - } - }); - } - - public async Task UpdateProcessesAsync() + private async void OnProcessesUpdated(IReadOnlyList processes) { - var newProcesses = _service.Processes; - await _dispatcherQueue.EnqueueAsync(() => + await _dispatcher.EnqueueAsync(() => { - var existingByPid = ProcessList.ToDictionary(vm => vm.PID); + var existing = ProcessList.ToDictionary(vm => vm.PID); - foreach (var dto in newProcesses) + foreach (var dto in processes.Where(p => p.PID != 0)) { - if (dto.PID == 0) continue; - if (existingByPid.TryGetValue(dto.PID, out var vm)) + if (existing.TryGetValue(dto.PID, out var vm)) { vm.WorkingSet = dto.WorkingSet; vm.PrivateBytes = dto.PrivateBytes; + vm.CpuUsage = Math.Truncate(dto.CpuUsage * 10) / 10; } else { @@ -63,22 +44,19 @@ await _dispatcherQueue.EnqueueAsync(() => } } - for (int i = ProcessList.Count - 1; i >= 0; i--) + for (int i = ProcessList.Count - 1; i >= 0; --i) { - if (!newProcesses.Any(p => p.PID == ProcessList[i].PID)) + if (!processes.Any(p => p.PID == ProcessList[i].PID)) + { ProcessList.RemoveAt(i); + } } }); } - public event PropertyChangedEventHandler? PropertyChanged; - - protected void OnPropertyChanged([CallerMemberName] string? propertyName = null) - => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - public void Dispose() { - _timer.Dispose(); + _service.ProcessesUpdated -= OnProcessesUpdated; } } } diff --git a/Process-Explorer.GUI/Views/Pages/ActionsPage.xaml b/Process-Explorer.GUI/Views/Pages/ActionsPage.xaml index e26c8bc..fc4a7fa 100644 --- a/Process-Explorer.GUI/Views/Pages/ActionsPage.xaml +++ b/Process-Explorer.GUI/Views/Pages/ActionsPage.xaml @@ -8,20 +8,20 @@ xmlns:charts="using:LiveChartsCore.SkiaSharpView.WinUI" xmlns:muxc="using:Microsoft.UI.Xaml.Controls" mc:Ignorable="d" - Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> + > - + - + - + diff --git a/Process-Explorer.GUI/Views/Pages/ActionsPage.xaml.cs b/Process-Explorer.GUI/Views/Pages/ActionsPage.xaml.cs index 7883eb7..1d341e1 100644 --- a/Process-Explorer.GUI/Views/Pages/ActionsPage.xaml.cs +++ b/Process-Explorer.GUI/Views/Pages/ActionsPage.xaml.cs @@ -1,7 +1,11 @@ using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Navigation; +using Microsoft.Windows.AppNotifications; +using Microsoft.Windows.AppNotifications.Builder; using Process_Explorer.GUI.ViewModels; using System; +using System.Drawing; namespace Process_Explorer.GUI.Views { @@ -38,7 +42,13 @@ protected override void OnNavigatedTo(NavigationEventArgs e) //so don't pay attention private void Button_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) { - _____(); + var notification = new AppNotificationBuilder() + .AddText("ops...") + .AddText("soon bro soon") + .BuildNotification(); + + AppNotificationManager.Default.Show(notification); + } private void Button_Click_1(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) diff --git a/Process-Explorer.GUI/Views/Pages/MetricsPage.xaml b/Process-Explorer.GUI/Views/Pages/MetricsPage.xaml index 2312745..c33fba2 100644 --- a/Process-Explorer.GUI/Views/Pages/MetricsPage.xaml +++ b/Process-Explorer.GUI/Views/Pages/MetricsPage.xaml @@ -9,7 +9,7 @@ xmlns:charts="using:LiveChartsCore.SkiaSharpView.WinUI" xmlns:vm="using:Process_Explorer.GUI.ViewModels" mc:Ignorable="d" - Background="{ThemeResource SystemChromeMediumColor}"> + > diff --git a/Process-Explorer.GUI/Views/Windows/MainWindow.xaml b/Process-Explorer.GUI/Views/Windows/MainWindow.xaml index 4a50e27..ff00592 100644 --- a/Process-Explorer.GUI/Views/Windows/MainWindow.xaml +++ b/Process-Explorer.GUI/Views/Windows/MainWindow.xaml @@ -12,9 +12,15 @@ mc:Ignorable="d" Title="Process Explorer 1.0.0"> + + + - + + + + @@ -28,7 +34,8 @@ IsSettingsVisible="False" IsBackEnabled="{x:Bind ContentFrame.CanGoBack, Mode=OneWay}" ItemInvoked="NavigationView_ItemInvoked" - BackRequested="NavigationView_BackRequested"> + BackRequested="NavigationView_BackRequested" + > @@ -38,7 +45,6 @@ - - + - + @@ -61,9 +67,11 @@ + - + + diff --git a/Process-Explorer.Native/Allocator.cpp b/Process-Explorer.Native/Allocator.cpp new file mode 100644 index 0000000..23018f2 --- /dev/null +++ b/Process-Explorer.Native/Allocator.cpp @@ -0,0 +1,11 @@ +#include "pch.h" +#include "Allocator.h" + +void Native::Allocator::Deallocate(void* buffer) +{ + if (buffer) + { + if (!VirtualFree(buffer, 0, MEM_RELEASE)) + throw gcnew System::NullReferenceException("mem error"); + } +} diff --git a/Process-Explorer.Native/Allocator.h b/Process-Explorer.Native/Allocator.h new file mode 100644 index 0000000..362cb89 --- /dev/null +++ b/Process-Explorer.Native/Allocator.h @@ -0,0 +1,23 @@ +#pragma once + +namespace Native +{ + ref class Allocator abstract sealed + { + public: + + template + static T* Allocate(size_t capacity); + + static void Deallocate(void* buffer); + + }; + + + template + inline T* Allocator::Allocate(size_t capacity) + { + return (T*)VirtualAlloc(nullptr, capacity, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + } +} + diff --git a/Process-Explorer.Native/Process-Explorer.Native.vcxproj b/Process-Explorer.Native/Process-Explorer.Native.vcxproj index f0fdac6..535f1eb 100644 --- a/Process-Explorer.Native/Process-Explorer.Native.vcxproj +++ b/Process-Explorer.Native/Process-Explorer.Native.vcxproj @@ -82,7 +82,7 @@ pch.h Level3 _DEBUG;%(PreprocessorDefinitions) - stdcpp20 + Default MultiThreadedDebugDLL @@ -123,18 +123,22 @@ + + + + @@ -147,8 +151,10 @@ Create + + diff --git a/Process-Explorer.Native/Process-Explorer.Native.vcxproj.filters b/Process-Explorer.Native/Process-Explorer.Native.vcxproj.filters index 351583e..5fd6d4e 100644 --- a/Process-Explorer.Native/Process-Explorer.Native.vcxproj.filters +++ b/Process-Explorer.Native/Process-Explorer.Native.vcxproj.filters @@ -45,6 +45,15 @@ Файлы заголовков + + Файлы заголовков + + + Файлы заголовков + + + Файлы заголовков + @@ -77,6 +86,15 @@ Исходные файлы + + Исходные файлы + + + Исходные файлы + + + Исходные файлы + diff --git a/Process-Explorer.Native/ProcessEx.cpp b/Process-Explorer.Native/ProcessEx.cpp new file mode 100644 index 0000000..8954df9 --- /dev/null +++ b/Process-Explorer.Native/ProcessEx.cpp @@ -0,0 +1,169 @@ +#include "pch.h" +#include "ProcessEx.h" +#include "ProcessExplorer-Definitions.h" +#include "Handle.h" +#include "CriticalSection.h" +#include "ProcessInformationEx.h" + +Native::ProcessEx::ProcessEx(PSYSTEM_PROCESS_INFORMATION_EX pInfo) : m_handle(gcnew Native::Handle(OpenProcess(PROCESS_ALL_ACCESS, FALSE, (ULONG_PTR)pInfo->UniqueProcessId))), m_cs(gcnew Native::CriticalSection()), m_info(gcnew Native::ProcessInformationEx()) +{ + SaveOrUpdateProcessInformation(pInfo); +} + +Native::ProcessEx::~ProcessEx() +{ + +} + +Native::ProcessEx::!ProcessEx() +{ + this->~ProcessEx(); +} + +Native::ProcessInformationEx^ Native::ProcessEx::GetProcessInformation() +{ + return m_info; +} + +System::String^ Native::ProcessEx::GetProcessDescription() +{ + if (!m_handle->IsValid()) return ""; + + WCHAR fullPath[MAX_PATH]{ 0 }; + if (GetModuleFileNameEx(m_handle, nullptr, fullPath, MAX_PATH)) + { + DWORD dummy; + DWORD size = GetFileVersionInfoSize(fullPath, &dummy); + if (size) + { + PLANGANDCODEPAGE lpTranslate; + BYTE* versionData = new BYTE[size]; + if (GetFileVersionInfo(fullPath, NULL, size, versionData)) + { + UINT cbTranslate{ 0 }; + if (VerQueryValue(versionData, L"\\VarFileInfo\\Translation", (LPVOID*)&lpTranslate, &cbTranslate)) + { + WCHAR subBlock[64]{}; + + swprintf_s(subBlock, L"\\StringFileInfo\\%04x%04x\\FileDescription", lpTranslate[0].wLanguage, lpTranslate[0].wCodePage); + LPVOID lpBuffer; + UINT dwBytes; + if (VerQueryValue(versionData, subBlock, &lpBuffer, &dwBytes)) + { + System::String^ company = gcnew System::String((wchar_t*)lpBuffer); + delete[] versionData; + return company; + } + } + } + delete[] versionData; + } + } +} + +System::String^ Native::ProcessEx::GetProcessCompany() +{ + if (!m_handle->IsValid()) return ""; + + WCHAR fullPath[MAX_PATH]{ 0 }; + if (GetModuleFileNameEx(m_handle, nullptr, fullPath, MAX_PATH)) + { + DWORD dummy; + DWORD size = GetFileVersionInfoSize(fullPath, &dummy); + if (size) + { + PLANGANDCODEPAGE lpTranslate; + BYTE* versionData = new BYTE[size]; + if (GetFileVersionInfo(fullPath, NULL, size, versionData)) + { + UINT cbTranslate{ 0 }; + if (VerQueryValue(versionData, L"\\VarFileInfo\\Translation", (LPVOID*)&lpTranslate, &cbTranslate)) + { + WCHAR subBlock[64]{}; + + swprintf_s(subBlock, L"StringFileInfo\\%04x%04x\\CompanyName", lpTranslate[0].wLanguage, lpTranslate[0].wCodePage); + LPVOID lpBuffer; + UINT dwBytes; + if (VerQueryValue(versionData, subBlock, &lpBuffer, &dwBytes)) + { + System::String^ description = gcnew System::String((wchar_t*)lpBuffer); + delete[] versionData; + return description; + } + } + } + delete[] versionData; + } + } +} + +System::String^ Native::ProcessEx::GetProcessFilePath() +{ + if (!m_handle->IsValid()) return ""; + + TCHAR path[MAX_PATH]{ 0 }; + DWORD size{ MAX_PATH }; + if (!QueryFullProcessImageName(m_handle, 0, path, &size)) + throw gcnew System::Exception(">:{ failed."); + + return gcnew System::String(path); +} + + +void Native::ProcessEx::UpdateProcessCpuUsage(PSYSTEM_PROCESS_INFORMATION_EX pspi) +{ + + auto processorTime = pspi->KernelTime.QuadPart + pspi->UserTime.QuadPart; + auto now = System::DateTime::UtcNow; + + if (!m_firstTimeMeasured) + { + m_info->ProcessorInfo->ProcessorTime = pspi->KernelTime.QuadPart + pspi->UserTime.QuadPart; + m_info->ProcessorInfo->LastCheckTime = now; + m_firstTimeMeasured = true; + return; + } + + System::TimeSpan deltaTime = now - m_info->ProcessorInfo->LastCheckTime; + + auto elapsedTime = deltaTime.TotalMilliseconds; + + auto deltaProcessorTime = processorTime - m_info->ProcessorInfo->ProcessorTime; + + auto cpuUsage = (deltaProcessorTime / (elapsedTime * 10'000.0 * System::Environment::ProcessorCount)) * 100; + + m_info->ProcessorInfo->ProcessorTime = processorTime; + m_info->ProcessorInfo->LastCheckTime = now; + m_info->CpuUsage = System::Math::Min(System::Math::Max(cpuUsage, 0.0), 100.0); +} + + +void Native::ProcessEx::SaveOrUpdateProcessInformation(PSYSTEM_PROCESS_INFORMATION_EX pspi) +{ + m_cs->Lock(); + + if (!m_dataReceived) + { + m_info->PID = (ULONG_PTR)pspi->UniqueProcessId; + m_info->PPID = (ULONG_PTR)pspi->InheritedFromUniqueProcessId; + m_info->Name = pspi->ImageName.Buffer ? gcnew System::String(pspi->ImageName.Buffer, 0, pspi->ImageName.Length / sizeof(WCHAR)) : "Unknown"; + + if (m_handle->IsValid()) + { + m_info->Description = GetProcessDescription(); + m_info->Company = GetProcessCompany(); + m_info->FilePath = GetProcessFilePath(); + } + m_dataReceived = true; + } + + m_info->WorkingSet = pspi->WorkingSetSize; + m_info->PrivateBytes = pspi->PrivatePageCount; + + m_info->HandlesCount = pspi->HandleCount; + m_info->ThreadsCount = pspi->NumberOfThreads; + + UpdateProcessCpuUsage(pspi); + + m_cs->Unlock(); +} diff --git a/Process-Explorer.Native/ProcessEx.h b/Process-Explorer.Native/ProcessEx.h new file mode 100644 index 0000000..e3e9485 --- /dev/null +++ b/Process-Explorer.Native/ProcessEx.h @@ -0,0 +1,42 @@ +#pragma once +typedef struct _SYSTEM_PROCESS_INFORMATION_EX* PSYSTEM_PROCESS_INFORMATION_EX; + +namespace Native +{ + ref class ProcessInformationEx; + ref class CriticalSection; + ref class Handle; + + public ref class ProcessEx + { + private: + + Handle^ m_handle{ nullptr }; + + CriticalSection^ m_cs; + ProcessInformationEx^ m_info; + + bool m_dataReceived{ false }; + bool m_firstTimeMeasured{ false }; + + private: + + + void UpdateProcessCpuUsage(PSYSTEM_PROCESS_INFORMATION_EX pspi); + + public: + + ProcessEx(PSYSTEM_PROCESS_INFORMATION_EX pInfo); + ~ProcessEx(); + !ProcessEx(); + + ProcessInformationEx^ GetProcessInformation(); + + void SaveOrUpdateProcessInformation(PSYSTEM_PROCESS_INFORMATION_EX pspi); + + System::String^ GetProcessDescription(); + System::String^ GetProcessCompany(); + System::String^ GetProcessFilePath(); + + }; +} diff --git a/Process-Explorer.Native/ProcessExplorer-Definitions.h b/Process-Explorer.Native/ProcessExplorer-Definitions.h index b670653..50e87c0 100644 --- a/Process-Explorer.Native/ProcessExplorer-Definitions.h +++ b/Process-Explorer.Native/ProcessExplorer-Definitions.h @@ -1,10 +1,51 @@ -#pragma once +#pragma once + +#define KB 1024 +#define MB 1024 * KB +#define GB 1024 * MB +#define TB 1024 * GB typedef struct LANGANDCODEPAGE { WORD wLanguage; WORD wCodePage; } *PLANGANDCODEPAGE, FAR* LPLANGANDCODEPAGE; +typedef struct _SYSTEM_PROCESS_INFORMATION_EX { + ULONG NextEntryOffset; + ULONG NumberOfThreads; + LARGE_INTEGER Reserved[3]; + LARGE_INTEGER CreateTime; + LARGE_INTEGER UserTime; + LARGE_INTEGER KernelTime; + UNICODE_STRING ImageName; + KPRIORITY BasePriority; + HANDLE UniqueProcessId; + HANDLE InheritedFromUniqueProcessId; + ULONG HandleCount; + ULONG SessionId; + ULONG_PTR PageDirectoryBase; + SIZE_T PeakVirtualSize; + SIZE_T VirtualSize; + ULONG PageFaultCount; + SIZE_T PeakWorkingSetSize; + SIZE_T WorkingSetSize; + SIZE_T QuotaPeaedPoolUsage; + SIZE_T QuotaPeakPagedPoolUsage; + SIZE_T QuotaPagkNonPagedPoolUsage; + SIZE_T QuotaNonPagedPoolUsage; + SIZE_T PagefileUsage; + SIZE_T PeakPagefileUsage; + SIZE_T PrivatePageCount; + LARGE_INTEGER ReadOperationCount; + LARGE_INTEGER WriteOperationCount; + LARGE_INTEGER OtherOperationCount; + LARGE_INTEGER ReadTransferCount; + LARGE_INTEGER WriteTransferCount; + LARGE_INTEGER OtherTransferCount; +} SYSTEM_PROCESS_INFORMATION_EX, * PSYSTEM_PROCESS_INFORMATION_EX, FAR* LPSYSTEM_PROCESS_INFORMATION_EX; + + + ULONGLONG FileTimeToULL(PFILETIME ft); //----------- diff --git a/Process-Explorer.Native/ProcessInformationEx.cpp b/Process-Explorer.Native/ProcessInformationEx.cpp new file mode 100644 index 0000000..b1bbd3b --- /dev/null +++ b/Process-Explorer.Native/ProcessInformationEx.cpp @@ -0,0 +1,2 @@ +#include "pch.h" +#include "ProcessInformationEx.h" diff --git a/Process-Explorer.Native/ProcessInformationEx.h b/Process-Explorer.Native/ProcessInformationEx.h new file mode 100644 index 0000000..0561b54 --- /dev/null +++ b/Process-Explorer.Native/ProcessInformationEx.h @@ -0,0 +1,37 @@ +#pragma once + +namespace Native +{ + + public ref class ProcessProcessorInformation + { + public: + property System::DateTime LastCheckTime; + property LONGLONG ProcessorTime; + }; + + public ref class ProcessInformationEx + { + public: + + ProcessInformationEx() + { + ProcessorInfo = gcnew ProcessProcessorInformation(); + } + + property DWORD PID; + property DWORD PPID; + property DWORD ThreadsCount; + property DWORD HandlesCount; + property System::String^ Name; + property DWORD WorkingSet; + property DWORD PrivateBytes; + property System::String^ Company; + property System::String^ Description; + property System::String^ FilePath; + property ProcessProcessorInformation^ ProcessorInfo; + property double CpuUsage; + }; + +} + diff --git a/Process-Explorer.Native/ProcessManager.cpp b/Process-Explorer.Native/ProcessManager.cpp index b165451..f46bfca 100644 --- a/Process-Explorer.Native/ProcessManager.cpp +++ b/Process-Explorer.Native/ProcessManager.cpp @@ -1,59 +1,143 @@ -#include "pch.h" +#include "pch.h" #include "ProcessManager.h" #include "Process.h" +#include "ProcessEx.h" #include "CriticalSection.h" #include "Handle.h" +#include "ProcessInformation.h" +#include "ProcessExplorer-Definitions.h" +#include "Allocator.h" using namespace System::Collections::Generic; Native::ProcessManager::ProcessManager() { - m_processes = gcnew Dictionary(); + m_processes = gcnew Dictionary(350); + m_processesEx = gcnew Dictionary(350); } System::Threading::Tasks::Task^ Native::ProcessManager::InitializeProcessesListAsync() { - DWORD PIDs[1024], needed; - if (EnumProcesses(PIDs, sizeof(PIDs), &needed)) { - size_t count = needed / sizeof(DWORD); - for (size_t i = 0; i < count; ++i) { - - if (PIDs[i] == 0) continue; - auto newPIDs = gcnew HashSet(); - for (size_t i = 0; i < count; ++i) + DWORD PIDs[1024]{}; + DWORD needed{ 0 }; + + if (!EnumProcesses(PIDs, sizeof(PIDs), &needed)) + throw gcnew System::Exception("Failed to get process pids."); + + const size_t count = needed / sizeof(DWORD); + + auto newPIDs = gcnew HashSet(count); + for (size_t i = 0; i < count; ++i) + { + if (PIDs[i] != 0) + newPIDs->Add(PIDs[i]); + } + + auto oldPIDs = gcnew List(m_processes->Keys); + for each (auto oldPid in oldPIDs) + { + if (!newPIDs->Contains(oldPid)) + { + m_processes->Remove(oldPid); + } + } + + for each (auto pid in newPIDs) + { + if (!m_processes->ContainsKey(pid)) + { + Native::Handle^ hProcess = gcnew Native::Handle(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid)); + if (hProcess->IsValid()) { - newPIDs->Add(PIDs[i]); + m_processes->Add(pid, gcnew Native::Process(hProcess)); } + } + } + + return System::Threading::Tasks::Task::CompletedTask; +} + + +System::Threading::Tasks::Task^ Native::ProcessManager::NtInitializeProcessesListAsync() +{ + ULONG BUFFER_SIZE = 0; + PBYTE buffer = nullptr; + + + auto status = NtQuerySystemInformation(SystemProcessInformation, nullptr, 0, &BUFFER_SIZE); + /*if (status != STATUS_INFO_LENGTH_MISMATCH) + throw gcnew System::Exception("NtQuerySystemInformation.");*/ + + buffer = Allocator::Allocate(BUFFER_SIZE); + if (!buffer) + throw gcnew System::OutOfMemoryException("Failed to allocate memory for process buffer."); + + try + { + status = NtQuerySystemInformation(SystemProcessInformation, buffer, BUFFER_SIZE, nullptr); + if (!NT_SUCCESS(status)) + throw gcnew System::Exception("NtQuerySystemInformation returned an error."); - auto oldPIDs = gcnew HashSet(m_processes->Keys); - for each(auto pid in oldPIDs) + PSYSTEM_PROCESS_INFORMATION_EX pspi_ex = reinterpret_cast(buffer); + + auto newPids = gcnew HashSet(); + auto oldPids = gcnew List(m_processesEx->Keys); + + while (true) + { + DWORD pid = static_cast(reinterpret_cast(pspi_ex->UniqueProcessId)); + newPids->Add(pid); + + if (!m_processesEx->ContainsKey(pid)) + { + m_processesEx->Add(pid, gcnew ProcessEx(pspi_ex)); + } + else { - if (!newPIDs->Contains(pid)) - { - m_processes->Remove(pid); - } - } + m_processesEx[pid]->SaveOrUpdateProcessInformation(pspi_ex); + } + + if (pspi_ex->NextEntryOffset == 0) + break; + + pspi_ex = reinterpret_cast( + reinterpret_cast(pspi_ex) + pspi_ex->NextEntryOffset); + } - if (!m_processes->ContainsKey(PIDs[i])) + for each (auto oldPid in oldPids) + { + if (!newPids->Contains(oldPid)) { - Native::Handle^ hProcess = gcnew Native::Handle(OpenProcess(PROCESS_ALL_ACCESS, FALSE, PIDs[i])); - if (!hProcess->IsValid()) continue; - m_processes->Add(PIDs[i], gcnew Native::Process(hProcess)); + m_processesEx->Remove(oldPid); } } } + finally + { + Allocator::Deallocate(buffer); + } - return System::Threading::Tasks::Task::CompletedTask; + return System::Threading::Tasks::Task::CompletedTask; } + + System::Threading::Tasks::Task^>^ Native::ProcessManager::GetActiveProcessesAsync() { - InitializeProcessesListAsync()->Wait(); - + InitializeProcessesListAsync(); IEnumerable^ result = m_processes->Values; return System::Threading::Tasks::Task::FromResult(result); } +System::Threading::Tasks::Task^>^ Native::ProcessManager::NtGetActiveProcessesAsync() +{ + NtInitializeProcessesListAsync(); + + IEnumerable^ result = m_processesEx->Values; + + return System::Threading::Tasks::Task::FromResult(result); +} + diff --git a/Process-Explorer.Native/ProcessManager.h b/Process-Explorer.Native/ProcessManager.h index a42fa1c..c806cd9 100644 --- a/Process-Explorer.Native/ProcessManager.h +++ b/Process-Explorer.Native/ProcessManager.h @@ -6,19 +6,25 @@ using namespace System::Collections::Generic; namespace Native { ref class Process; - + ref class ProcessEx; + ref class ProcessInformation; + public ref class ProcessManager abstract sealed { private: static ProcessManager(); static System::Threading::Tasks::Task^ InitializeProcessesListAsync(); + static System::Threading::Tasks::Task^ NtInitializeProcessesListAsync(); + + static bool m_first{ true }; static System::Collections::Generic::Dictionary^ m_processes; + static System::Collections::Generic::Dictionary^ m_processesEx; public: static System::Threading::Tasks::Task^>^ GetActiveProcessesAsync(); - + static System::Threading::Tasks::Task^>^ NtGetActiveProcessesAsync(); }; } diff --git a/Process-Explorer.Native/pch.h b/Process-Explorer.Native/pch.h index fdbd6b9..d1294d6 100644 --- a/Process-Explorer.Native/pch.h +++ b/Process-Explorer.Native/pch.h @@ -16,6 +16,8 @@ #pragma comment(lib, "Shlwapi.lib") #pragma comment(lib, "Advapi32.lib") #pragma comment(lib, "User32.lib") +#pragma comment(lib, "ntdll.lib") + #endif //PCH_H diff --git a/README.md b/README.md index f126ae3..4e329b5 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,3 @@ ``status - in development`` -``version - alpha`` +``version - experimental`` -# old - -![Screenshot 2025-05-25 145932](https://github.com/user-attachments/assets/c3cfbb20-c2c4-4232-bc70-db341476d6a2) - -# new - -![Screenshot 2025-06-01 032041](https://github.com/user-attachments/assets/51dcfd6a-64a4-4e6a-94d1-6cf1e33afa91) - -# old - -![Screenshot 2025-05-24 214413](https://github.com/user-attachments/assets/caad59ab-7fba-4eb1-ae8f-bbc335b19c59) - -# new - -![Screenshot 2025-06-01 032622](https://github.com/user-attachments/assets/a783347b-fc93-4dfe-b230-5c6ca6a89098)