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
+ // See the LICENSE file in the project root for more information.
4
+
5
+ using System ;
6
+ using System . Runtime . CompilerServices ;
7
+ using System . Runtime . InteropServices ;
8
+ using System . Threading ;
9
+ using Microsoft . UI . Dispatching ;
10
+ using WinRT ;
11
+
12
+ #nullable enable
13
+
14
+ #pragma warning disable SA1000 , SA1023
15
+
16
+ namespace CommunityToolkit . WinUI
17
+ {
18
+ /// <summary>
19
+ /// A callback that will be executed on the <see cref="DispatcherQueue"/> thread.
20
+ /// </summary>
21
+ /// <typeparam name="TState">The type of state to receive as input.</typeparam>
22
+ /// <param name="state">The input state for the callback.</param>
23
+ public delegate void DispatcherQueueHandler < in TState > ( TState state )
24
+ where TState : class ;
25
+
26
+ /// <summary>
27
+ /// Helpers for executing code in a <see cref="DispatcherQueue"/>.
28
+ /// </summary>
29
+ public static partial class DispatcherQueueExtensions
30
+ {
31
+ /// <summary>
32
+ /// Adds a task to the <see cref="DispatcherQueue"/> which will be executed on the thread associated with it.
33
+ /// </summary>
34
+ /// <typeparam name="TState">The type of state to capture.</typeparam>
35
+ /// <param name="dispatcherQueue">The target <see cref="DispatcherQueue"/> to invoke the code on.</param>
36
+ /// <param name="callback">The input <see cref="DispatcherQueueHandler{TState}"/> callback to enqueue.</param>
37
+ /// <param name="state">The input state to capture and pass to the callback.</param>
38
+ /// <returns>Whether or not the task was added to the queue.</returns>
39
+ public static unsafe bool TryEnqueue < TState > ( this DispatcherQueue dispatcherQueue , DispatcherQueueHandler < TState > callback , TState state )
40
+ where TState : class
41
+ {
42
+ IDispatcherQueue * dispatcherQueuePtr = ( IDispatcherQueue * ) ( ( IWinRTObject ) dispatcherQueue ) . NativeObject . ThisPtr ;
43
+ DispatcherQueueProxyHandler * dispatcherQueueHandlerPtr = DispatcherQueueProxyHandler . Create ( callback , state ) ;
44
+
45
+ byte result ;
46
+
47
+ try
48
+ {
49
+ _ = dispatcherQueuePtr ->TryEnqueue ( dispatcherQueueHandlerPtr , & result ) ;
50
+
51
+ GC . KeepAlive ( dispatcherQueue ) ;
52
+ }
53
+ finally
54
+ {
55
+ dispatcherQueueHandlerPtr ->Release ( ) ;
56
+ }
57
+
58
+ return result == 0 ;
59
+ }
60
+
61
+ /// <summary>
62
+ /// Adds a task to the <see cref="DispatcherQueue"/> which will be executed on the thread associated with it.
63
+ /// </summary>
64
+ /// <typeparam name="TState">The type of state to capture.</typeparam>
65
+ /// <param name="dispatcherQueue">The target <see cref="DispatcherQueue"/> to invoke the code on.</param>
66
+ /// <param name="priority"> The desired priority for the callback to schedule.</param>
67
+ /// <param name="callback">The input <see cref="DispatcherQueueHandler{TState}"/> callback to enqueue.</param>
68
+ /// <param name="state">The input state to capture and pass to the callback.</param>
69
+ /// <returns>Whether or not the task was added to the queue.</returns>
70
+ public static unsafe bool TryEnqueue < TState > ( this DispatcherQueue dispatcherQueue , DispatcherQueuePriority priority , DispatcherQueueHandler < TState > callback , TState state )
71
+ where TState : class
72
+ {
73
+ IDispatcherQueue * dispatcherQueuePtr = ( IDispatcherQueue * ) ( ( IWinRTObject ) dispatcherQueue ) . NativeObject . ThisPtr ;
74
+ DispatcherQueueProxyHandler * dispatcherQueueHandlerPtr = DispatcherQueueProxyHandler . Create ( callback , state ) ;
75
+
76
+ byte result ;
77
+
78
+ try
79
+ {
80
+ _ = dispatcherQueuePtr ->TryEnqueueWithPriority ( priority , dispatcherQueueHandlerPtr , & result ) ;
81
+
82
+ GC . KeepAlive ( dispatcherQueue ) ;
83
+ }
84
+ finally
85
+ {
86
+ dispatcherQueueHandlerPtr ->Release ( ) ;
87
+ }
88
+
89
+ return result == 0 ;
90
+ }
91
+
92
+ /// <summary>
93
+ /// A struct mapping the native WinRT <c>IDispatcherQueue</c> interface.
94
+ /// </summary>
95
+ private unsafe struct IDispatcherQueue
96
+ {
97
+ private readonly void * * lpVtbl ;
98
+
99
+ /// <summary>
100
+ /// Native API for <see cref="DispatcherQueue.TryEnqueue(DispatcherQueueHandler)"/>.
101
+ /// </summary>
102
+ /// <param name="callback">A pointer to an <c>IDispatcherQueueHandler</c> object.</param>
103
+ /// <param name="result">The result of the operation (the <see cref="bool"/> WinRT retval).</param>
104
+ /// <returns>The HRESULT for the operation.</returns>
105
+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
106
+ public int TryEnqueue ( void * callback , byte * result )
107
+ {
108
+ return ( ( delegate * unmanaged< IDispatcherQueue * , void * , byte * , int > ) lpVtbl [ 7 ] ) ( ( IDispatcherQueue * ) Unsafe . AsPointer ( ref this ) , callback , result ) ;
109
+ }
110
+
111
+ /// <summary>
112
+ /// Native API for <see cref="DispatcherQueue.TryEnqueue(DispatcherQueuePriority, DispatcherQueueHandler)"/>.
113
+ /// </summary>
114
+ /// <param name="priority">The priority for the input callback.</param>
115
+ /// <param name="callback">A pointer to an <c>IDispatcherQueueHandler</c> object.</param>
116
+ /// <param name="result">The result of the operation (the <see cref="bool"/> WinRT retval).</param>
117
+ /// <returns>The HRESULT for the operation.</returns>
118
+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
119
+ public int TryEnqueueWithPriority ( DispatcherQueuePriority priority , void * callback , byte * result )
120
+ {
121
+ return ( ( delegate * unmanaged< IDispatcherQueue * , DispatcherQueuePriority , void * , byte * , int > ) lpVtbl [ 8 ] ) ( ( IDispatcherQueue * ) Unsafe . AsPointer ( ref this ) , priority , callback , result ) ;
122
+ }
123
+ }
124
+
125
+ /// <summary>
126
+ /// A custom <c>IDispatcherQueueHandler</c> object, that internally stores a captured <see cref="DispatcherQueueHandler{TState}"/> instance
127
+ /// and the input captured state. This allows consumers to enqueue a state and a cached stateless delegate without any managed allocations.
128
+ /// </summary>
129
+ private unsafe struct DispatcherQueueProxyHandler
130
+ {
131
+ private const int S_OK = 0 ;
132
+ private const int E_NOINTERFACE = unchecked ( ( int ) 0x80004002 ) ;
133
+
134
+ private static readonly Guid IUnknown = new ( 0x00000000 , 0x0000 , 0x0000 , 0xC0 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x46 ) ;
135
+ private static readonly Guid IAgileObject = new ( 0x94EA2B94 , 0xE9CC , 0x49E0 , 0xC0 , 0xFF , 0xEE , 0x64 , 0xCA , 0x8F , 0x5B , 0x90 ) ;
136
+ private static readonly Guid IDispatcherQueueHandler = new ( 0x2E0872A9 , 0x4E29 , 0x5F14 , 0xB6 , 0x88 , 0xFB , 0x96 , 0xD5 , 0xF9 , 0xD5 , 0xF8 ) ;
137
+
138
+ /// <summary>
139
+ /// The shared vtable pointer for <see cref="DispatcherQueueProxyHandler"/> instances.
140
+ /// </summary>
141
+ private static readonly void * * Vtbl = InitVtbl ( ) ;
142
+
143
+ /// <summary>
144
+ /// Setups the vtable pointer for <see cref="DispatcherQueueProxyHandler"/>.
145
+ /// </summary>
146
+ /// <returns>The initialized vtable pointer for <see cref="DispatcherQueueProxyHandler"/>.</returns>
147
+ /// <remarks>
148
+ /// The vtable itself is allocated with <see cref="RuntimeHelpers.AllocateTypeAssociatedMemory(Type, int)"/>,
149
+ /// which allocates memory in the high frequency heap associated with the input runtime type. This will be
150
+ /// automatically cleaned up when the type is unloaded, so there is no need to ever manually free this memory.
151
+ /// </remarks>
152
+ private static void * * InitVtbl ( )
153
+ {
154
+ void * * lpVtbl = ( void * * ) RuntimeHelpers . AllocateTypeAssociatedMemory ( typeof ( DispatcherQueueProxyHandler ) , sizeof ( void * ) * 4 ) ;
155
+
156
+ lpVtbl [ 0 ] = ( delegate * unmanaged< DispatcherQueueProxyHandler * , Guid * , void * * , int > ) & Impl . QueryInterface ;
157
+ lpVtbl [ 1 ] = ( delegate * unmanaged< DispatcherQueueProxyHandler * , uint > ) & Impl . AddRef ;
158
+ lpVtbl [ 2 ] = ( delegate * unmanaged< DispatcherQueueProxyHandler * , uint > ) & Impl . Release ;
159
+ lpVtbl [ 3 ] = ( delegate * unmanaged< DispatcherQueueProxyHandler * , int > ) & Impl . Invoke ;
160
+
161
+ return lpVtbl ;
162
+ }
163
+
164
+ /// <summary>
165
+ /// The vtable pointer for the current instance.
166
+ /// </summary>
167
+ private void * * lpVtbl ;
168
+
169
+ /// <summary>
170
+ /// The <see cref="GCHandle"/> to the captured <see cref="DispatcherQueueHandler{TState}"/> (for some unknown <c>TState</c> type).
171
+ /// </summary>
172
+ private GCHandle callbackHandle ;
173
+
174
+ /// <summary>
175
+ /// The <see cref="GCHandle"/> to the captured state (with an unknown <c>TState</c> type).
176
+ /// </summary>
177
+ private GCHandle stateHandle ;
178
+
179
+ /// <summary>
180
+ /// The current reference count for the object (from <c>IUnknown</c>).
181
+ /// </summary>
182
+ private volatile uint referenceCount ;
183
+
184
+ /// <summary>
185
+ /// Creates a new <see cref="DispatcherQueueProxyHandler"/> instance for the input callback and state.
186
+ /// </summary>
187
+ /// <typeparam name="TState">The type of state to capture.</typeparam>
188
+ /// <param name="handler">The input <see cref="DispatcherQueueHandler{TState}"/> callback to enqueue.</param>
189
+ /// <param name="state">The input state to capture and pass to the callback.</param>
190
+ /// <returns>A pointer to the newly initialized <see cref="DispatcherQueueProxyHandler"/> instance.</returns>
191
+ public static DispatcherQueueProxyHandler * Create < TState > ( DispatcherQueueHandler < TState > handler , TState state )
192
+ where TState : class
193
+ {
194
+ DispatcherQueueProxyHandler * @this = ( DispatcherQueueProxyHandler * ) Marshal . AllocHGlobal ( sizeof ( DispatcherQueueProxyHandler ) ) ;
195
+
196
+ @this ->lpVtbl = Vtbl ;
197
+ @this ->callbackHandle = GCHandle . Alloc ( handler ) ;
198
+ @this ->stateHandle = GCHandle . Alloc ( state ) ;
199
+ @this ->referenceCount = 1 ;
200
+
201
+ return @this ;
202
+ }
203
+
204
+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
205
+ public uint AddRef ( )
206
+ {
207
+ return Interlocked . Increment ( ref referenceCount ) ;
208
+ }
209
+
210
+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
211
+ public uint Release ( )
212
+ {
213
+ uint referenceCount = Interlocked . Decrement ( ref this . referenceCount ) ;
214
+
215
+ if ( referenceCount == 0 )
216
+ {
217
+ Marshal . FreeHGlobal ( ( IntPtr ) Unsafe . AsPointer ( ref this ) ) ;
218
+ }
219
+
220
+ return referenceCount ;
221
+ }
222
+
223
+ /// <summary>
224
+ /// A private type with the implementation of the unmanaged methods for <see cref="DispatcherQueueProxyHandler"/>.
225
+ /// These methods will be set into the shared vtable and invoked by WinRT from the object passed to it as an interface.
226
+ /// </summary>
227
+ private static class Impl
228
+ {
229
+ /// <summary>
230
+ /// Implements <c>IUnknown.QueryInterface(REFIID, void**)</c>.
231
+ /// </summary>
232
+ [ UnmanagedCallersOnly ]
233
+ public static int QueryInterface ( DispatcherQueueProxyHandler * @this , Guid * riid , void * * ppvObject )
234
+ {
235
+ if ( riid ->Equals ( IUnknown ) ||
236
+ riid ->Equals ( IAgileObject ) ||
237
+ riid ->Equals ( IDispatcherQueueHandler ) )
238
+ {
239
+ @this ->AddRef ( ) ;
240
+
241
+ * ppvObject = @this ;
242
+
243
+ return S_OK ;
244
+ }
245
+
246
+ return E_NOINTERFACE ;
247
+ }
248
+
249
+ /// <summary>
250
+ /// Implements <c>IUnknown.AddRef()</c>.
251
+ /// </summary>
252
+ [ UnmanagedCallersOnly ]
253
+ public static uint AddRef ( DispatcherQueueProxyHandler * @this )
254
+ {
255
+ return Interlocked . Increment ( ref @this ->referenceCount ) ;
256
+ }
257
+
258
+ /// <summary>
259
+ /// Implements <c>IUnknown.Release()</c>.
260
+ /// </summary>
261
+ [ UnmanagedCallersOnly ]
262
+ public static uint Release ( DispatcherQueueProxyHandler * @this )
263
+ {
264
+ uint referenceCount = Interlocked . Decrement ( ref @this ->referenceCount ) ;
265
+
266
+ if ( referenceCount == 0 )
267
+ {
268
+ @this ->callbackHandle . Free ( ) ;
269
+ @this ->stateHandle . Free ( ) ;
270
+
271
+ Marshal . FreeHGlobal ( ( IntPtr ) @this ) ;
272
+ }
273
+
274
+ return referenceCount ;
275
+ }
276
+
277
+ /// <summary>
278
+ /// Implements <c>IDispatcherQueueHandler.Invoke()</c>.
279
+ /// </summary>
280
+ [ UnmanagedCallersOnly ]
281
+ public static int Invoke ( DispatcherQueueProxyHandler * @this )
282
+ {
283
+ object callback = @this ->callbackHandle . Target ! ;
284
+ object state = @this ->stateHandle . Target ! ;
285
+
286
+ try
287
+ {
288
+ // We do an unsafe cast here to treat the captured delegate as if the contravariant
289
+ // input type was actually declared as covariant. This is valid because the type
290
+ // parameter is constrained to be a reference type, and due to how the proxy handler
291
+ // is constructed we know that the captured state will always match the actual type
292
+ // of the captured handler at this point. This lets this whole method work without the
293
+ // need to make the proxy type itself generic, so without knowing the actual type argument.
294
+ Unsafe . As < DispatcherQueueHandler < object > > ( callback ) ( state ) ;
295
+ }
296
+ catch
297
+ {
298
+ }
299
+
300
+ return S_OK ;
301
+ }
302
+ }
303
+ }
304
+ }
305
+ }
0 commit comments