Skip to content

Commit c3f807a

Browse files
committed
Fixed crash from TaskbarProgress when BuiltInComInteropSupport is disabled.
Fixed terminal sequence printed to console in legacy console.
1 parent e715d5b commit c3f807a

File tree

2 files changed

+153
-93
lines changed

2 files changed

+153
-93
lines changed

src/BenchmarkDotNet/Helpers/Taskbar/TaskbarProgress.cs

Lines changed: 152 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -9,40 +9,40 @@ internal class TaskbarProgress : IDisposable
99
// Must be windows 7 or greater
1010
&& Environment.OSVersion.Version >= new Version(6, 1);
1111

12-
private IntPtr consoleWindowHandle = IntPtr.Zero;
13-
private IntPtr consoleHandle = IntPtr.Zero;
12+
private TaskbarProgressCom? com;
13+
private TaskbarProgressTerminal? terminal;
1414

15-
[DllImport("kernel32.dll")]
16-
private static extern IntPtr GetConsoleWindow();
17-
[DllImport("kernel32.dll", SetLastError = true)]
18-
private static extern IntPtr GetStdHandle(int nStdHandle);
15+
private bool IsEnabled => com != null || terminal != null;
1916

20-
private const int STD_OUTPUT_HANDLE = -11;
21-
22-
internal TaskbarProgress()
17+
internal TaskbarProgress(TaskbarProgressState initialTaskbarState)
2318
{
2419
if (OsVersionIsSupported)
2520
{
26-
consoleWindowHandle = GetConsoleWindow();
27-
consoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
28-
Console.CancelKeyPress += OnConsoleCancelEvent;
21+
com = TaskbarProgressCom.MaybeCreateInstanceAndSetInitialState(initialTaskbarState);
22+
terminal = TaskbarProgressTerminal.MaybeCreateInstanceAndSetInitialState(initialTaskbarState);
23+
if (IsEnabled)
24+
{
25+
Console.CancelKeyPress += OnConsoleCancelEvent;
26+
}
2927
}
3028
}
3129

3230
internal void SetState(TaskbarProgressState state)
3331
{
34-
if (OsVersionIsSupported)
35-
{
36-
TaskbarProgressCom.SetState(consoleWindowHandle, consoleHandle, state);
37-
}
32+
com?.SetState(state);
33+
terminal?.SetState(state);
3834
}
3935

4036
internal void SetProgress(float progressValue)
4137
{
42-
if (OsVersionIsSupported)
38+
bool isValidRange = progressValue >= 0 & progressValue <= 1;
39+
if (!isValidRange)
4340
{
44-
TaskbarProgressCom.SetValue(consoleWindowHandle, consoleHandle, progressValue);
41+
throw new ArgumentOutOfRangeException(nameof(progressValue), "progressValue must be between 0 and 1 inclusive.");
4542
}
43+
uint value = (uint) (progressValue * 100);
44+
com?.SetValue(value);
45+
terminal?.SetValue(value);
4646
}
4747

4848
private void OnConsoleCancelEvent(object sender, ConsoleCancelEventArgs e)
@@ -52,11 +52,12 @@ private void OnConsoleCancelEvent(object sender, ConsoleCancelEventArgs e)
5252

5353
public void Dispose()
5454
{
55-
if (OsVersionIsSupported)
55+
if (IsEnabled)
5656
{
57-
TaskbarProgressCom.SetState(consoleWindowHandle, consoleHandle, TaskbarProgressState.NoProgress);
58-
consoleWindowHandle = IntPtr.Zero;
59-
consoleHandle = IntPtr.Zero;
57+
com?.SetState(TaskbarProgressState.NoProgress);
58+
terminal?.SetState(TaskbarProgressState.NoProgress);
59+
com = null;
60+
terminal = null;
6061
Console.CancelKeyPress -= OnConsoleCancelEvent;
6162
}
6263
}
@@ -72,33 +73,8 @@ internal enum TaskbarProgressState
7273
Warning = Paused
7374
}
7475

75-
internal static class TaskbarProgressCom
76+
internal sealed class TaskbarProgressCom
7677
{
77-
[DllImport("kernel32.dll", SetLastError = true)]
78-
private static extern bool GetConsoleMode(IntPtr hConsoleHandle, out ConsoleModes lpMode);
79-
[DllImport("kernel32.dll", SetLastError = true)]
80-
private static extern bool SetConsoleMode(IntPtr hConsoleHandle, ConsoleModes dwMode);
81-
82-
[Flags]
83-
private enum ConsoleModes : uint
84-
{
85-
ENABLE_PROCESSED_INPUT = 0x0001,
86-
ENABLE_LINE_INPUT = 0x0002,
87-
ENABLE_ECHO_INPUT = 0x0004,
88-
ENABLE_WINDOW_INPUT = 0x0008,
89-
ENABLE_MOUSE_INPUT = 0x0010,
90-
ENABLE_INSERT_MODE = 0x0020,
91-
ENABLE_QUICK_EDIT_MODE = 0x0040,
92-
ENABLE_EXTENDED_FLAGS = 0x0080,
93-
ENABLE_AUTO_POSITION = 0x0100,
94-
95-
ENABLE_PROCESSED_OUTPUT = 0x0001,
96-
ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002,
97-
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004,
98-
DISABLE_NEWLINE_AUTO_RETURN = 0x0008,
99-
ENABLE_LVB_GRID_WORLDWIDE = 0x0010
100-
}
101-
10278
[ComImport]
10379
[Guid("ea1afb91-9e28-4b86-90e9-9e9f8a5eefaf")]
10480
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
@@ -134,64 +110,149 @@ private class TaskbarInstance
134110
{
135111
}
136112

137-
private static readonly ITaskbarList3 s_taskbarInstance = (ITaskbarList3) new TaskbarInstance();
113+
[DllImport("kernel32.dll")]
114+
private static extern IntPtr GetConsoleWindow();
138115

139-
internal static void SetState(IntPtr consoleWindowHandle, IntPtr consoleHandle, TaskbarProgressState taskbarState)
116+
private readonly ITaskbarList3 taskbarInstance;
117+
private readonly IntPtr consoleWindowHandle;
118+
119+
private TaskbarProgressCom(IntPtr handle)
140120
{
141-
if (consoleWindowHandle != IntPtr.Zero)
142-
{
143-
s_taskbarInstance.SetProgressState(consoleWindowHandle, taskbarState);
144-
}
121+
taskbarInstance = (ITaskbarList3) new TaskbarInstance();
122+
consoleWindowHandle = handle;
123+
}
145124

146-
if (consoleHandle != IntPtr.Zero)
125+
internal static TaskbarProgressCom? MaybeCreateInstanceAndSetInitialState(TaskbarProgressState initialTaskbarState)
126+
{
127+
try
147128
{
148-
// Write progress state to console for Windows Terminal (https://github.com/microsoft/terminal/issues/6700).
149-
GetConsoleMode(consoleHandle, out ConsoleModes previousConsoleMode);
150-
SetConsoleMode(consoleHandle, ConsoleModes.ENABLE_VIRTUAL_TERMINAL_PROCESSING | ConsoleModes.ENABLE_PROCESSED_OUTPUT);
151-
switch (taskbarState)
129+
IntPtr handle = GetConsoleWindow();
130+
if (handle == IntPtr.Zero)
152131
{
153-
case TaskbarProgressState.NoProgress:
154-
Console.Write("\x1b]9;4;0;0\x1b\\");
155-
break;
156-
case TaskbarProgressState.Indeterminate:
157-
Console.Write("\x1b]9;4;3;0\x1b\\");
158-
break;
159-
case TaskbarProgressState.Normal:
160-
// Do nothing, this is set automatically when SetValue is called (and WT has no documented way to set this).
161-
break;
162-
case TaskbarProgressState.Error:
163-
Console.Write("\x1b]9;4;2;0\x1b\\");
164-
break;
165-
case TaskbarProgressState.Warning:
166-
Console.Write("\x1b]9;4;4;0\x1b\\");
167-
break;
168-
};
169-
SetConsoleMode(consoleHandle, previousConsoleMode);
132+
return null;
133+
}
134+
var com = new TaskbarProgressCom(handle);
135+
com.SetState(initialTaskbarState);
136+
return com;
137+
}
138+
// COM may be disabled, in which case this will throw (#2253).
139+
catch (NotSupportedException)
140+
{
141+
return null;
170142
}
171143
}
172144

173-
internal static void SetValue(IntPtr consoleWindowHandle, IntPtr consoleHandle, float progressValue)
145+
internal void SetState(TaskbarProgressState taskbarState)
174146
{
175-
bool isValidRange = progressValue >= 0 & progressValue <= 1;
176-
if (!isValidRange)
147+
taskbarInstance.SetProgressState(consoleWindowHandle, taskbarState);
148+
}
149+
150+
/// <summary>
151+
/// Sets the progress value out of 100.
152+
/// </summary>
153+
internal void SetValue(uint progressValue)
154+
{
155+
taskbarInstance.SetProgressValue(consoleWindowHandle, progressValue, 100);
156+
}
157+
}
158+
159+
internal sealed class TaskbarProgressTerminal
160+
{
161+
[Flags]
162+
private enum ConsoleModes : uint
163+
{
164+
ENABLE_PROCESSED_INPUT = 0x0001,
165+
ENABLE_LINE_INPUT = 0x0002,
166+
ENABLE_ECHO_INPUT = 0x0004,
167+
ENABLE_WINDOW_INPUT = 0x0008,
168+
ENABLE_MOUSE_INPUT = 0x0010,
169+
ENABLE_INSERT_MODE = 0x0020,
170+
ENABLE_QUICK_EDIT_MODE = 0x0040,
171+
ENABLE_EXTENDED_FLAGS = 0x0080,
172+
ENABLE_AUTO_POSITION = 0x0100,
173+
174+
ENABLE_PROCESSED_OUTPUT = 0x0001,
175+
ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002,
176+
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004,
177+
DISABLE_NEWLINE_AUTO_RETURN = 0x0008,
178+
ENABLE_LVB_GRID_WORLDWIDE = 0x0010
179+
}
180+
181+
[DllImport("kernel32.dll", SetLastError = true)]
182+
private static extern bool GetConsoleMode(IntPtr hConsoleHandle, out ConsoleModes lpMode);
183+
[DllImport("kernel32.dll", SetLastError = true)]
184+
private static extern bool SetConsoleMode(IntPtr hConsoleHandle, ConsoleModes dwMode);
185+
[DllImport("kernel32.dll", SetLastError = true)]
186+
private static extern IntPtr GetStdHandle(int nStdHandle);
187+
private const int STD_OUTPUT_HANDLE = -11;
188+
189+
private readonly IntPtr consoleHandle = IntPtr.Zero;
190+
191+
private TaskbarProgressTerminal(IntPtr handle)
192+
{
193+
consoleHandle = handle;
194+
}
195+
196+
internal static TaskbarProgressTerminal? MaybeCreateInstanceAndSetInitialState(TaskbarProgressState initialTaskbarState)
197+
{
198+
IntPtr handle = GetStdHandle(STD_OUTPUT_HANDLE);
199+
if (handle == IntPtr.Zero)
177200
{
178-
throw new ArgumentOutOfRangeException(nameof(progressValue), "progressValue must be between 0 and 1 inclusive.");
201+
return null;
179202
}
180-
uint value = (uint) (progressValue * 100);
181-
182-
if (consoleWindowHandle != IntPtr.Zero)
203+
if (!GetConsoleMode(handle, out ConsoleModes previousConsoleMode)
204+
|| !SetConsoleMode(handle, ConsoleModes.ENABLE_VIRTUAL_TERMINAL_PROCESSING | ConsoleModes.ENABLE_PROCESSED_OUTPUT))
183205
{
184-
s_taskbarInstance.SetProgressValue(consoleWindowHandle, value, 100);
206+
// If we failed to set virtual terminal processing mode, it is likely due to an older Windows version that does not support it,
207+
// or legacy console. In either case the TaskbarProgressCom will take care of the progress. See https://stackoverflow.com/a/44574463/5703407.
208+
// If we try to write without VT mode, the sequence will be printed for the user to see, which clutters the output.
209+
return null;
185210
}
211+
SetStateThenRevertConsoleMode(handle, initialTaskbarState, previousConsoleMode);
212+
return new TaskbarProgressTerminal(handle);
213+
}
186214

187-
if (consoleHandle != IntPtr.Zero)
215+
internal void SetState(TaskbarProgressState taskbarState)
216+
{
217+
GetConsoleMode(consoleHandle, out ConsoleModes previousConsoleMode);
218+
SetConsoleMode(consoleHandle, ConsoleModes.ENABLE_VIRTUAL_TERMINAL_PROCESSING | ConsoleModes.ENABLE_PROCESSED_OUTPUT);
219+
SetStateThenRevertConsoleMode(consoleHandle, taskbarState, previousConsoleMode);
220+
}
221+
222+
private static void SetStateThenRevertConsoleMode(IntPtr handle, TaskbarProgressState taskbarState, ConsoleModes previousConsoleMode)
223+
{
224+
// Write progress state to console for Windows Terminal (https://github.com/microsoft/terminal/issues/6700).
225+
switch (taskbarState)
188226
{
189-
// Write progress sequence to console for Windows Terminal (https://github.com/microsoft/terminal/discussions/14268).
190-
GetConsoleMode(consoleHandle, out ConsoleModes previousConsoleMode);
191-
SetConsoleMode(consoleHandle, ConsoleModes.ENABLE_VIRTUAL_TERMINAL_PROCESSING | ConsoleModes.ENABLE_PROCESSED_OUTPUT);
192-
Console.Write($"\x1b]9;4;1;{value}\x1b\\");
193-
SetConsoleMode(consoleHandle, previousConsoleMode);
194-
}
227+
case TaskbarProgressState.NoProgress:
228+
Console.Write("\x1b]9;4;0;0\x1b\\");
229+
break;
230+
case TaskbarProgressState.Indeterminate:
231+
Console.Write("\x1b]9;4;3;0\x1b\\");
232+
break;
233+
case TaskbarProgressState.Normal:
234+
// Do nothing, this is set automatically when SetValue is called (and WT has no documented way to set this).
235+
break;
236+
case TaskbarProgressState.Error:
237+
Console.Write("\x1b]9;4;2;0\x1b\\");
238+
break;
239+
case TaskbarProgressState.Warning:
240+
Console.Write("\x1b]9;4;4;0\x1b\\");
241+
break;
242+
};
243+
SetConsoleMode(handle, previousConsoleMode);
244+
}
245+
246+
/// <summary>
247+
/// Sets the progress value out of 100.
248+
/// </summary>
249+
internal void SetValue(uint progressValue)
250+
{
251+
// Write progress sequence to console for Windows Terminal (https://github.com/microsoft/terminal/discussions/14268).
252+
GetConsoleMode(consoleHandle, out ConsoleModes previousConsoleMode);
253+
SetConsoleMode(consoleHandle, ConsoleModes.ENABLE_VIRTUAL_TERMINAL_PROCESSING | ConsoleModes.ENABLE_PROCESSED_OUTPUT);
254+
Console.Write($"\x1b]9;4;1;{progressValue}\x1b\\");
255+
SetConsoleMode(consoleHandle, previousConsoleMode);
195256
}
196257
}
197258
}

src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@ internal static class BenchmarkRunnerClean
3535

3636
internal static Summary[] Run(BenchmarkRunInfo[] benchmarkRunInfos)
3737
{
38-
using var taskbarProgress = new TaskbarProgress();
39-
taskbarProgress.SetState(TaskbarProgressState.Indeterminate);
38+
using var taskbarProgress = new TaskbarProgress(TaskbarProgressState.Indeterminate);
4039

4140
var resolver = DefaultResolver;
4241
var artifactsToCleanup = new List<string>();

0 commit comments

Comments
 (0)