Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid calling GetInvocationList on hot paths #4736

Merged
merged 1 commit into from
Aug 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@
<Compile Include="System\Windows\Duration.cs" />
<Compile Include="System\Windows\DurationConverter.cs" />
<Compile Include="System\Windows\EventHandlersStore.cs" />
<Compile Include="System\Windows\EventHelper.cs" />
<Compile Include="System\Windows\EventManager.cs" />
<Compile Include="System\Windows\EventPrivateKey.cs" />
<Compile Include="System\Windows\EventRoute.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Threading;

namespace System.Windows
{
/// <summary>Provides helper methods for working with events and delegates.</summary>
internal static class EventHelper
{
/// <summary>Combines a delegate into an existing delegate.</summary>
/// <param name="field">A tuple of a delegate and its invocation list already in array form.</param>
/// <param name="value">The delegate to add.</param>
/// <remarks>
/// This routine enables code to store a tuple of a delegate and its invocation list. Adding
/// behaves exactly like combining with the delegate, but the invocation list is also updated.
/// Thread safety is maintained just as with the code generated by the C# compiler when adding
/// to an event. Storing such a tuple is helpful when the registered delegates are changed
/// relatively rarely while invocation via the invocation list happens much more frequently,
/// avoiding the need to call GetInvocationList every time invocation needs to happen, as the
/// array has already been precomputed.
/// </remarks>
public static void AddHandler<T>(ref Tuple<T, Delegate[]> field, T value) where T : Delegate
{
while (true)
{
Tuple<T, Delegate[]> oldTuple = field;
T combinedDelegate = (T)Delegate.Combine(oldTuple?.Item1, value);
Tuple<T, Delegate[]> newTuple = combinedDelegate != null ? Tuple.Create(combinedDelegate, combinedDelegate.GetInvocationList()) : null;
if (Interlocked.CompareExchange(ref field, newTuple, oldTuple) == oldTuple)
{
break;
}
}
}

/// <summary>Removes a delegate from an existing delegate.</summary>
/// <param name="field">A tuple of a delegate and its invocation list already in array form.</param>
/// <param name="value">The delegate to remove.</param>
public static void RemoveHandler<T>(ref Tuple<T, Delegate[]> field, T value) where T : Delegate
{
while (true)
{
Tuple<T, Delegate[]> oldTuple = field;
T delegateWithRemoval = (T)Delegate.Remove(oldTuple?.Item1, value);
Tuple<T, Delegate[]> newTuple = delegateWithRemoval != null ? Tuple.Create(delegateWithRemoval, delegateWithRemoval.GetInvocationList()) : null;
if (Interlocked.CompareExchange(ref field, newTuple, oldTuple) == oldTuple)
{
break;
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -179,49 +179,26 @@ private InputManager()

public event PreProcessInputEventHandler PreProcessInput
{
add
{
_preProcessInput += value;
}
remove
{
_preProcessInput -= value;
}
add => EventHelper.AddHandler(ref _preProcessInput, value);
remove => EventHelper.RemoveHandler(ref _preProcessInput, value);
}

public event NotifyInputEventHandler PreNotifyInput
{
add
{
_preNotifyInput += value;
}
remove
{
_preNotifyInput -= value;
}
}
add => EventHelper.AddHandler(ref _preNotifyInput, value);
remove => EventHelper.RemoveHandler(ref _preNotifyInput, value);
}

public event NotifyInputEventHandler PostNotifyInput
{
add
{
_postNotifyInput += value;
}
remove
{
_postNotifyInput -= value;
}
add => EventHelper.AddHandler(ref _postNotifyInput, value);
remove => EventHelper.RemoveHandler(ref _postNotifyInput, value);
}

public event ProcessInputEventHandler PostProcessInput
{
add
{
_postProcessInput += value;
}
remove
{
_postProcessInput -= value;
}
add => EventHelper.AddHandler(ref _postProcessInput, value);
remove => EventHelper.RemoveHandler(ref _postProcessInput, value);
}

/// <summary>
Expand Down Expand Up @@ -749,7 +726,7 @@ private bool ProcessStagingArea()

// Invoke the handlers in reverse order so that handlers that
// users add are invoked before handlers in the system.
Delegate[] handlers = _preProcessInput.GetInvocationList();
Delegate[] handlers = _preProcessInput.Item2;
for(int i = (handlers.Length - 1); i >= 0; i--)
{
PreProcessInputEventHandler handler = (PreProcessInputEventHandler) handlers[i];
Expand All @@ -772,7 +749,7 @@ private bool ProcessStagingArea()

// Invoke the handlers in reverse order so that handlers that
// users add are invoked before handlers in the system.
Delegate[] handlers = _preNotifyInput.GetInvocationList();
Delegate[] handlers = _preNotifyInput.Item2;
for(int i = (handlers.Length - 1); i >= 0; i--)
{
NotifyInputEventHandler handler = (NotifyInputEventHandler) handlers[i];
Expand Down Expand Up @@ -878,7 +855,7 @@ private bool ProcessStagingArea()

// Invoke the handlers in reverse order so that handlers that
// users add are invoked before handlers in the system.
Delegate[] handlers = _postNotifyInput.GetInvocationList();
Delegate[] handlers = _postNotifyInput.Item2;
for(int i = (handlers.Length - 1); i >= 0; i--)
{
NotifyInputEventHandler handler = (NotifyInputEventHandler) handlers[i];
Expand All @@ -898,7 +875,7 @@ private bool ProcessStagingArea()
{
processInputEventArgs.Reset(item, this);

RaiseProcessInputEventHandlers(_postProcessInput, processInputEventArgs);
RaiseProcessInputEventHandlers(_postProcessInput, processInputEventArgs);

// PreviewInputReport --> InputReport
if(item.Input.RoutedEvent == InputManager.PreviewInputReportEvent)
Expand Down Expand Up @@ -936,15 +913,15 @@ private bool ProcessStagingArea()
return handled;
}

private void RaiseProcessInputEventHandlers(ProcessInputEventHandler postProcessInput, ProcessInputEventArgs processInputEventArgs)
private void RaiseProcessInputEventHandlers(Tuple<ProcessInputEventHandler, Delegate[]> postProcessInput, ProcessInputEventArgs processInputEventArgs)
{
processInputEventArgs.StagingItem.Input.MarkAsUserInitiated();

try
{
// Invoke the handlers in reverse order so that handlers that
// users add are invoked before handlers in the system.
Delegate[] handlers = postProcessInput.GetInvocationList();
Delegate[] handlers = postProcessInput.Item2;
for(int i = (handlers.Length - 1); i >= 0; i--)
{
ProcessInputEventHandler handler = (ProcessInputEventHandler) handlers[i];
Expand Down Expand Up @@ -974,14 +951,14 @@ private void RequestContinueProcessingStagingArea()
private ProcessInputEventArgs _processInputEventArgs;
private PreProcessInputEventArgs _preProcessInputEventArgs;

//these four events introduced for secutiy purposes
private event PreProcessInputEventHandler _preProcessInput;

private event NotifyInputEventHandler _preNotifyInput;

private event NotifyInputEventHandler _postNotifyInput;

private event ProcessInputEventHandler _postProcessInput;
// These four events introduced for security purposes. Rather than just store the multicast delegate
// and have to do GetInvocationList on each invocation (in order to invoke delegates in reverse-registered
// order), we get the invocation list when the events are updated, and then use that cached list
// on every invocation.
private Tuple<PreProcessInputEventHandler, Delegate[]> _preProcessInput;
private Tuple<NotifyInputEventHandler, Delegate[]> _preNotifyInput;
private Tuple<NotifyInputEventHandler, Delegate[]> _postNotifyInput;
private Tuple<ProcessInputEventHandler, Delegate[]> _postProcessInput;

private event KeyEventHandler _translateAccelerator;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ private void Initialize(HwndSourceParameters parameters)
Delegate[] handlers = parameters.HwndSourceHook.GetInvocationList();
for (int i = handlers.Length -1; i >= 0; --i)
{
_hooks += (HwndSourceHook)handlers[i];
EventHelper.AddHandler(ref _hooks, (HwndSourceHook)handlers[i]);
}
wrapperHooks[3] = _publicHook;
}
Expand Down Expand Up @@ -376,7 +376,7 @@ public void AddHook(HwndSourceHook hook)
{
_hwndWrapper.AddHook(_publicHook);
}
_hooks += hook;
EventHelper.AddHandler(ref _hooks, hook);
}

/// <summary>
Expand All @@ -393,7 +393,7 @@ public void RemoveHook(HwndSourceHook hook)

//this.VerifyAccess();

_hooks -= hook;
EventHelper.RemoveHandler(ref _hooks, hook);
if(_hooks == null)
{
_hwndWrapper.RemoveHook(_publicHook);
Expand Down Expand Up @@ -1635,7 +1635,7 @@ private IntPtr PublicHooksFilterMessage(IntPtr hwnd, int msg, IntPtr wParam, Int
// would never see the WM_DESTROY etc. message.
if (_hooks != null)
{
Delegate[] handlers = _hooks.GetInvocationList();
Delegate[] handlers = _hooks.Item2;
for (int i = handlers.Length -1; i >= 0; --i)
{
var hook = (HwndSourceHook)handlers[i];
Expand Down Expand Up @@ -2820,7 +2820,7 @@ public void Dispose()

private SecurityCriticalDataForSet<Visual> _rootVisual;

private event HwndSourceHook _hooks;
private Tuple<HwndSourceHook, Delegate[]> _hooks;

private SecurityCriticalDataClass<HwndMouseInputProvider> _mouse;

Expand Down