Skip to content

Commit a2d61d9

Browse files
Issue 55685 processname optimisation (#59672)
Co-authored-by: Adam Sitnik <[email protected]>
1 parent 0ddc132 commit a2d61d9

File tree

7 files changed

+162
-15
lines changed

7 files changed

+162
-15
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Buffers;
6+
using System.Runtime.InteropServices;
7+
using Microsoft.Win32.SafeHandles;
8+
9+
internal static partial class Interop
10+
{
11+
internal static partial class Kernel32
12+
{
13+
[DllImport(Libraries.Kernel32, CharSet = CharSet.Unicode, EntryPoint = "QueryFullProcessImageNameW", ExactSpelling = true, SetLastError = true)]
14+
private static unsafe extern bool QueryFullProcessImageName(
15+
SafeHandle hProcess,
16+
uint dwFlags,
17+
char* lpBuffer,
18+
ref uint lpdwSize);
19+
20+
internal static unsafe string? GetProcessName(uint processId)
21+
{
22+
using (SafeProcessHandle handle = OpenProcess(Advapi32.ProcessOptions.PROCESS_QUERY_LIMITED_INFORMATION, false, (int)processId))
23+
{
24+
if (handle.IsInvalid) // OpenProcess can fail
25+
{
26+
return null;
27+
}
28+
29+
const int StartLength =
30+
#if DEBUG
31+
1; // in debug, validate ArrayPool growth
32+
#else
33+
Interop.Kernel32.MAX_PATH;
34+
#endif
35+
36+
Span<char> buffer = stackalloc char[StartLength + 1];
37+
char[]? rentedArray = null;
38+
39+
try
40+
{
41+
while (true)
42+
{
43+
uint length = (uint)buffer.Length;
44+
fixed (char* pinnedBuffer = &MemoryMarshal.GetReference(buffer))
45+
{
46+
if (QueryFullProcessImageName(handle, 0, pinnedBuffer, ref length))
47+
{
48+
return buffer.Slice(0, (int)length).ToString();
49+
}
50+
else if (Marshal.GetLastWin32Error() != Errors.ERROR_INSUFFICIENT_BUFFER)
51+
{
52+
return null;
53+
}
54+
}
55+
56+
char[]? toReturn = rentedArray;
57+
buffer = rentedArray = ArrayPool<char>.Shared.Rent(buffer.Length * 2);
58+
if (toReturn is not null)
59+
{
60+
ArrayPool<char>.Shared.Return(toReturn);
61+
}
62+
}
63+
}
64+
finally
65+
{
66+
if (rentedArray is not null)
67+
{
68+
ArrayPool<char>.Shared.Return(rentedArray);
69+
}
70+
}
71+
}
72+
}
73+
}
74+
}

src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@
159159
Link="Common\Interop\Windows\Advapi32\Interop.AdjustTokenPrivileges.cs" />
160160
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.GetComputerName.cs"
161161
Link="Common\Interop\Windows\Kernel32\Interop.GetComputerName.cs" />
162+
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.GetProcessName.cs"
163+
Link="Common\Interop\Windows\Kernel32\Interop.GetProcessName.cs" />
162164
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.GetConsoleCP.cs"
163165
Link="Common\Interop\Windows\Kernel32\Interop.GetConsoleCP.cs" />
164166
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.GetConsoleOutputCP.cs"

src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,5 +1113,15 @@ private static int OnSigChild(int reapAll, int configureConsole)
11131113
s_processStartLock.ExitWriteLock();
11141114
}
11151115
}
1116+
1117+
/// <summary>Gets the friendly name of the process.</summary>
1118+
public string ProcessName
1119+
{
1120+
get
1121+
{
1122+
EnsureState(State.HaveProcessInfo);
1123+
return _processInfo!.ProcessName;
1124+
}
1125+
}
11161126
}
11171127
}

src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.UnknownUnix.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,5 +83,15 @@ private string GetPathToOpenFile()
8383
}
8484

8585
private int ParentProcessId => throw new PlatformNotSupportedException();
86+
87+
/// <summary>Gets the friendly name of the process.</summary>
88+
public string ProcessName
89+
{
90+
get
91+
{
92+
EnsureState(State.HaveProcessInfo);
93+
return _processInfo!.ProcessName;
94+
}
95+
}
8696
}
8797
}

src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Windows.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ public partial class Process : IDisposable
1717
{
1818
private static readonly object s_createProcessLock = new object();
1919

20+
private string? _processName;
21+
2022
/// <summary>
2123
/// Creates an array of <see cref="Process"/> components that are associated with process resources on a
2224
/// remote computer. These process resources share the specified process name.
@@ -125,6 +127,7 @@ private void RefreshCore()
125127
_haveMainWindow = false;
126128
_mainWindowTitle = null;
127129
_haveResponding = false;
130+
_processName = null;
128131
}
129132

130133
/// <summary>Additional logic invoked when the Process is closed.</summary>
@@ -887,5 +890,35 @@ private static string GetEnvironmentVariablesBlock(IDictionary<string, string> s
887890
}
888891

889892
private static string GetErrorMessage(int error) => Interop.Kernel32.GetMessage(error);
893+
894+
/// <summary>Gets the friendly name of the process.</summary>
895+
public string ProcessName
896+
{
897+
get
898+
{
899+
if (_processName == null)
900+
{
901+
EnsureState(State.HaveNonExitedId);
902+
// If we already have the name via a populated ProcessInfo
903+
// then use that one.
904+
if (_processInfo?.ProcessName != null)
905+
{
906+
_processName = _processInfo!.ProcessName;
907+
}
908+
else
909+
{
910+
// If we don't have a populated ProcessInfo, then get and cache the process name.
911+
_processName = ProcessManager.GetProcessName(_processId, _machineName);
912+
913+
if (_processName == null)
914+
{
915+
throw new InvalidOperationException(SR.NoProcessInfo);
916+
}
917+
}
918+
}
919+
920+
return _processName;
921+
}
922+
}
890923
}
891924
}

src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -505,21 +505,6 @@ public int PrivateMemorySize
505505
}
506506
}
507507

508-
/// <devdoc>
509-
/// <para>
510-
/// Gets
511-
/// the friendly name of the process.
512-
/// </para>
513-
/// </devdoc>
514-
public string ProcessName
515-
{
516-
get
517-
{
518-
EnsureState(State.HaveProcessInfo);
519-
return _processInfo!.ProcessName;
520-
}
521-
}
522-
523508
/// <devdoc>
524509
/// <para>
525510
/// Gets

src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Windows.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,39 @@ public static ProcessInfo[] GetProcessInfos(string machineName)
8585
return null;
8686
}
8787

88+
/// <summary>Gets the process name for the specified process ID on the specified machine.</summary>
89+
/// <param name="processId">The process ID.</param>
90+
/// <param name="machineName">The machine name.</param>
91+
/// <returns>The process name for the process if it could be found; otherwise, null.</returns>
92+
public static string? GetProcessName(int processId, string machineName)
93+
{
94+
if (IsRemoteMachine(machineName))
95+
{
96+
// remote case: we take the hit of looping through all results
97+
ProcessInfo[] processInfos = NtProcessManager.GetProcessInfos(machineName, isRemoteMachine: true);
98+
foreach (ProcessInfo processInfo in processInfos)
99+
{
100+
if (processInfo.ProcessId == processId)
101+
{
102+
return processInfo.ProcessName;
103+
}
104+
}
105+
}
106+
else
107+
{
108+
// local case: do not use performance counter and also attempt to get the matching (by pid) process only
109+
110+
string? processName = Interop.Kernel32.GetProcessName((uint)processId);
111+
if (processName is not null)
112+
{
113+
return NtProcessInfoHelper.GetProcessShortName(processName);
114+
}
115+
}
116+
117+
return null;
118+
}
119+
120+
88121
/// <summary>Gets the IDs of all processes on the specified machine.</summary>
89122
/// <param name="machineName">The machine to examine.</param>
90123
/// <returns>An array of process IDs from the specified machine.</returns>

0 commit comments

Comments
 (0)