Skip to content

Commit 3b80b6b

Browse files
committed
Minor code refactoring
1 parent bbbabfd commit 3b80b6b

File tree

3 files changed

+242
-218
lines changed

3 files changed

+242
-218
lines changed

CommunityToolkit.WinUI/Extensions/DispatcherQueueExtensions{T}.cs

Lines changed: 1 addition & 218 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,12 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System;
6-
using System.Runtime.CompilerServices;
7-
using System.Runtime.InteropServices;
8-
using System.Threading;
6+
using CommunityToolkit.WinUI.Interop;
97
using Microsoft.UI.Dispatching;
108
using WinRT;
119

1210
#nullable enable
1311

14-
#pragma warning disable SA1000, SA1023
15-
1612
namespace CommunityToolkit.WinUI
1713
{
1814
/// <summary>
@@ -102,218 +98,5 @@ public static unsafe bool TryEnqueue<TState>(this DispatcherQueue dispatcherQueu
10298

10399
return success;
104100
}
105-
106-
/// <summary>
107-
/// A struct mapping the native WinRT <c>IDispatcherQueue</c> interface.
108-
/// </summary>
109-
private unsafe struct IDispatcherQueue
110-
{
111-
private readonly void** lpVtbl;
112-
113-
/// <summary>
114-
/// Native API for <see cref="DispatcherQueue.TryEnqueue(DispatcherQueueHandler)"/>.
115-
/// </summary>
116-
/// <param name="callback">A pointer to an <c>IDispatcherQueueHandler</c> object.</param>
117-
/// <param name="result">The result of the operation (the <see cref="bool"/> WinRT retval).</param>
118-
/// <returns>The HRESULT for the operation.</returns>
119-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
120-
public int TryEnqueue(void* callback, byte* result)
121-
{
122-
return ((delegate* unmanaged<IDispatcherQueue*, void*, byte*, int>)lpVtbl[7])((IDispatcherQueue*)Unsafe.AsPointer(ref this), callback, result);
123-
}
124-
125-
/// <summary>
126-
/// Native API for <see cref="DispatcherQueue.TryEnqueue(DispatcherQueuePriority, DispatcherQueueHandler)"/>.
127-
/// </summary>
128-
/// <param name="priority">The priority for the input callback.</param>
129-
/// <param name="callback">A pointer to an <c>IDispatcherQueueHandler</c> object.</param>
130-
/// <param name="result">The result of the operation (the <see cref="bool"/> WinRT retval).</param>
131-
/// <returns>The HRESULT for the operation.</returns>
132-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
133-
public int TryEnqueueWithPriority(DispatcherQueuePriority priority, void* callback, byte* result)
134-
{
135-
return ((delegate* unmanaged<IDispatcherQueue*, DispatcherQueuePriority, void*, byte*, int>)lpVtbl[8])((IDispatcherQueue*)Unsafe.AsPointer(ref this), priority, callback, result);
136-
}
137-
}
138-
139-
/// <summary>
140-
/// A custom <c>IDispatcherQueueHandler</c> object, that internally stores a captured <see cref="DispatcherQueueHandler{TState}"/> instance
141-
/// and the input captured state. This allows consumers to enqueue a state and a cached stateless delegate without any managed allocations.
142-
/// </summary>
143-
private unsafe struct DispatcherQueueProxyHandler
144-
{
145-
private const int S_OK = 0;
146-
private const int E_NOINTERFACE = unchecked((int)0x80004002);
147-
148-
private static readonly Guid IUnknown = new(0x00000000, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46);
149-
private static readonly Guid IAgileObject = new(0x94EA2B94, 0xE9CC, 0x49E0, 0xC0, 0xFF, 0xEE, 0x64, 0xCA, 0x8F, 0x5B, 0x90);
150-
private static readonly Guid IDispatcherQueueHandler = new(0x2E0872A9, 0x4E29, 0x5F14, 0xB6, 0x88, 0xFB, 0x96, 0xD5, 0xF9, 0xD5, 0xF8);
151-
152-
/// <summary>
153-
/// The shared vtable pointer for <see cref="DispatcherQueueProxyHandler"/> instances.
154-
/// </summary>
155-
private static readonly void** Vtbl = InitVtbl();
156-
157-
/// <summary>
158-
/// Setups the vtable pointer for <see cref="DispatcherQueueProxyHandler"/>.
159-
/// </summary>
160-
/// <returns>The initialized vtable pointer for <see cref="DispatcherQueueProxyHandler"/>.</returns>
161-
/// <remarks>
162-
/// The vtable itself is allocated with <see cref="RuntimeHelpers.AllocateTypeAssociatedMemory(Type, int)"/>,
163-
/// which allocates memory in the high frequency heap associated with the input runtime type. This will be
164-
/// automatically cleaned up when the type is unloaded, so there is no need to ever manually free this memory.
165-
/// </remarks>
166-
private static void** InitVtbl()
167-
{
168-
void** lpVtbl = (void**)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(DispatcherQueueProxyHandler), sizeof(void*) * 4);
169-
170-
lpVtbl[0] = (delegate* unmanaged<DispatcherQueueProxyHandler*, Guid*, void**, int>)&Impl.QueryInterface;
171-
lpVtbl[1] = (delegate* unmanaged<DispatcherQueueProxyHandler*, uint>)&Impl.AddRef;
172-
lpVtbl[2] = (delegate* unmanaged<DispatcherQueueProxyHandler*, uint>)&Impl.Release;
173-
lpVtbl[3] = (delegate* unmanaged<DispatcherQueueProxyHandler*, int>)&Impl.Invoke;
174-
175-
return lpVtbl;
176-
}
177-
178-
/// <summary>
179-
/// The vtable pointer for the current instance.
180-
/// </summary>
181-
private void** lpVtbl;
182-
183-
/// <summary>
184-
/// The <see cref="GCHandle"/> to the captured <see cref="DispatcherQueueHandler{TState}"/> (for some unknown <c>TState</c> type).
185-
/// </summary>
186-
private GCHandle callbackHandle;
187-
188-
/// <summary>
189-
/// The <see cref="GCHandle"/> to the captured state (with an unknown <c>TState</c> type).
190-
/// </summary>
191-
private GCHandle stateHandle;
192-
193-
/// <summary>
194-
/// The current reference count for the object (from <c>IUnknown</c>).
195-
/// </summary>
196-
private volatile uint referenceCount;
197-
198-
/// <summary>
199-
/// Creates a new <see cref="DispatcherQueueProxyHandler"/> instance for the input callback and state.
200-
/// </summary>
201-
/// <typeparam name="TState">The type of state to capture.</typeparam>
202-
/// <param name="handler">The input <see cref="DispatcherQueueHandler{TState}"/> callback to enqueue.</param>
203-
/// <param name="state">The input state to capture and pass to the callback.</param>
204-
/// <returns>A pointer to the newly initialized <see cref="DispatcherQueueProxyHandler"/> instance.</returns>
205-
public static DispatcherQueueProxyHandler* Create<TState>(DispatcherQueueHandler<TState> handler, TState state)
206-
where TState : class
207-
{
208-
DispatcherQueueProxyHandler* @this = (DispatcherQueueProxyHandler*)Marshal.AllocHGlobal(sizeof(DispatcherQueueProxyHandler));
209-
210-
@this->lpVtbl = Vtbl;
211-
@this->callbackHandle = GCHandle.Alloc(handler);
212-
@this->stateHandle = GCHandle.Alloc(state);
213-
@this->referenceCount = 1;
214-
215-
return @this;
216-
}
217-
218-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
219-
public uint AddRef()
220-
{
221-
return Interlocked.Increment(ref referenceCount);
222-
}
223-
224-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
225-
public uint Release()
226-
{
227-
uint referenceCount = Interlocked.Decrement(ref this.referenceCount);
228-
229-
if (referenceCount == 0)
230-
{
231-
Marshal.FreeHGlobal((IntPtr)Unsafe.AsPointer(ref this));
232-
}
233-
234-
return referenceCount;
235-
}
236-
237-
/// <summary>
238-
/// A private type with the implementation of the unmanaged methods for <see cref="DispatcherQueueProxyHandler"/>.
239-
/// These methods will be set into the shared vtable and invoked by WinRT from the object passed to it as an interface.
240-
/// </summary>
241-
private static class Impl
242-
{
243-
/// <summary>
244-
/// Implements <c>IUnknown.QueryInterface(REFIID, void**)</c>.
245-
/// </summary>
246-
[UnmanagedCallersOnly]
247-
public static int QueryInterface(DispatcherQueueProxyHandler* @this, Guid* riid, void** ppvObject)
248-
{
249-
if (riid->Equals(IUnknown) ||
250-
riid->Equals(IAgileObject) ||
251-
riid->Equals(IDispatcherQueueHandler))
252-
{
253-
@this->AddRef();
254-
255-
*ppvObject = @this;
256-
257-
return S_OK;
258-
}
259-
260-
return E_NOINTERFACE;
261-
}
262-
263-
/// <summary>
264-
/// Implements <c>IUnknown.AddRef()</c>.
265-
/// </summary>
266-
[UnmanagedCallersOnly]
267-
public static uint AddRef(DispatcherQueueProxyHandler* @this)
268-
{
269-
return Interlocked.Increment(ref @this->referenceCount);
270-
}
271-
272-
/// <summary>
273-
/// Implements <c>IUnknown.Release()</c>.
274-
/// </summary>
275-
[UnmanagedCallersOnly]
276-
public static uint Release(DispatcherQueueProxyHandler* @this)
277-
{
278-
uint referenceCount = Interlocked.Decrement(ref @this->referenceCount);
279-
280-
if (referenceCount == 0)
281-
{
282-
@this->callbackHandle.Free();
283-
@this->stateHandle.Free();
284-
285-
Marshal.FreeHGlobal((IntPtr)@this);
286-
}
287-
288-
return referenceCount;
289-
}
290-
291-
/// <summary>
292-
/// Implements <c>IDispatcherQueueHandler.Invoke()</c>.
293-
/// </summary>
294-
[UnmanagedCallersOnly]
295-
public static int Invoke(DispatcherQueueProxyHandler* @this)
296-
{
297-
object callback = @this->callbackHandle.Target!;
298-
object state = @this->stateHandle.Target!;
299-
300-
try
301-
{
302-
// We do an unsafe cast here to treat the captured delegate as if the contravariant
303-
// input type was actually declared as covariant. This is valid because the type
304-
// parameter is constrained to be a reference type, and due to how the proxy handler
305-
// is constructed we know that the captured state will always match the actual type
306-
// of the captured handler at this point. This lets this whole method work without the
307-
// need to make the proxy type itself generic, so without knowing the actual type argument.
308-
Unsafe.As<DispatcherQueueHandler<object>>(callback)(state);
309-
}
310-
catch
311-
{
312-
}
313-
314-
return S_OK;
315-
}
316-
}
317-
}
318101
}
319102
}

0 commit comments

Comments
 (0)