2 * Copyright 2019-2020 Nikolay Sivov for CodeWeavers
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22 #define NONAMELESSUNION
26 #include "wine/debug.h"
27 #include "wine/list.h"
29 WINE_DEFAULT_DEBUG_CHANNEL(mfplat
);
31 #define FIRST_USER_QUEUE_HANDLE 5
32 #define MAX_USER_QUEUE_HANDLES 124
34 #define WAIT_ITEM_KEY_MASK (0x82000000)
35 #define SCHEDULED_ITEM_KEY_MASK (0x80000000)
37 static LONG next_item_key
;
39 static RTWQWORKITEM_KEY
get_item_key(DWORD mask
, DWORD key
)
41 return ((RTWQWORKITEM_KEY
)mask
<< 32) | key
;
44 static RTWQWORKITEM_KEY
generate_item_key(DWORD mask
)
46 return get_item_key(mask
, InterlockedIncrement(&next_item_key
));
56 static struct queue_handle user_queues
[MAX_USER_QUEUE_HANDLES
];
57 static struct queue_handle
*next_free_user_queue
;
58 static struct queue_handle
*next_unused_user_queue
= user_queues
;
59 static WORD queue_generation
;
60 static DWORD shared_mt_queue
;
62 static CRITICAL_SECTION queues_section
;
63 static CRITICAL_SECTION_DEBUG queues_critsect_debug
=
65 0, 0, &queues_section
,
66 { &queues_critsect_debug
.ProcessLocksList
, &queues_critsect_debug
.ProcessLocksList
},
67 0, 0, { (DWORD_PTR
)(__FILE__
": queues_section") }
69 static CRITICAL_SECTION queues_section
= { &queues_critsect_debug
, -1, 0, 0, 0, 0 };
71 static LONG platform_lock
;
72 static CO_MTA_USAGE_COOKIE mta_cookie
;
74 static struct queue_handle
*get_queue_obj(DWORD handle
)
76 unsigned int idx
= HIWORD(handle
) - FIRST_USER_QUEUE_HANDLE
;
78 if (idx
< MAX_USER_QUEUE_HANDLES
&& user_queues
[idx
].refcount
)
80 if (LOWORD(handle
) == user_queues
[idx
].generation
)
81 return &user_queues
[idx
];
87 /* Should be kept in sync with corresponding MFASYNC_CALLBACK_ constants. */
88 enum rtwq_callback_queue_id
90 RTWQ_CALLBACK_QUEUE_UNDEFINED
= 0x00000000,
91 RTWQ_CALLBACK_QUEUE_STANDARD
= 0x00000001,
92 RTWQ_CALLBACK_QUEUE_RT
= 0x00000002,
93 RTWQ_CALLBACK_QUEUE_IO
= 0x00000003,
94 RTWQ_CALLBACK_QUEUE_TIMER
= 0x00000004,
95 RTWQ_CALLBACK_QUEUE_MULTITHREADED
= 0x00000005,
96 RTWQ_CALLBACK_QUEUE_LONG_FUNCTION
= 0x00000007,
97 RTWQ_CALLBACK_QUEUE_PRIVATE_MASK
= 0xffff0000,
98 RTWQ_CALLBACK_QUEUE_ALL
= 0xffffffff,
101 /* Should be kept in sync with corresponding MFASYNC_ constants. */
102 enum rtwq_callback_flags
104 RTWQ_FAST_IO_PROCESSING_CALLBACK
= 0x00000001,
105 RTWQ_SIGNAL_CALLBACK
= 0x00000002,
106 RTWQ_BLOCKING_CALLBACK
= 0x00000004,
107 RTWQ_REPLY_CALLBACK
= 0x00000008,
108 RTWQ_LOCALIZE_REMOTE_CALLBACK
= 0x00000010,
111 enum system_queue_index
113 SYS_QUEUE_STANDARD
= 0,
117 SYS_QUEUE_MULTITHREADED
,
118 SYS_QUEUE_DO_NOT_USE
,
119 SYS_QUEUE_LONG_FUNCTION
,
125 IUnknown IUnknown_iface
;
128 IRtwqAsyncResult
*result
;
129 IRtwqAsyncResult
*reply_result
;
131 RTWQWORKITEM_KEY key
;
134 PTP_SIMPLE_CALLBACK finalization_callback
;
137 TP_WAIT
*wait_object
;
138 TP_TIMER
*timer_object
;
142 static struct work_item
*work_item_impl_from_IUnknown(IUnknown
*iface
)
144 return CONTAINING_RECORD(iface
, struct work_item
, IUnknown_iface
);
147 static const TP_CALLBACK_PRIORITY priorities
[] =
149 TP_CALLBACK_PRIORITY_HIGH
,
150 TP_CALLBACK_PRIORITY_NORMAL
,
151 TP_CALLBACK_PRIORITY_LOW
,
159 HRESULT (*init
)(const struct queue_desc
*desc
, struct queue
*queue
);
160 BOOL (*shutdown
)(struct queue
*queue
);
161 void (*submit
)(struct queue
*queue
, struct work_item
*item
);
166 RTWQ_WORKQUEUE_TYPE queue_type
;
167 const struct queue_ops
*ops
;
173 IRtwqAsyncCallback IRtwqAsyncCallback_iface
;
174 const struct queue_ops
*ops
;
176 TP_CALLBACK_ENVIRON_V3 envs
[ARRAY_SIZE(priorities
)];
178 struct list pending_items
;
180 /* Data used for serial queues only. */
181 PTP_SIMPLE_CALLBACK finalization_callback
;
185 static void shutdown_queue(struct queue
*queue
);
187 static HRESULT
lock_user_queue(DWORD queue
)
189 HRESULT hr
= RTWQ_E_INVALID_WORKQUEUE
;
190 struct queue_handle
*entry
;
192 if (!(queue
& RTWQ_CALLBACK_QUEUE_PRIVATE_MASK
))
195 EnterCriticalSection(&queues_section
);
196 entry
= get_queue_obj(queue
);
197 if (entry
&& entry
->refcount
)
202 LeaveCriticalSection(&queues_section
);
206 static HRESULT
unlock_user_queue(DWORD queue
)
208 HRESULT hr
= RTWQ_E_INVALID_WORKQUEUE
;
209 struct queue_handle
*entry
;
211 if (!(queue
& RTWQ_CALLBACK_QUEUE_PRIVATE_MASK
))
214 EnterCriticalSection(&queues_section
);
215 entry
= get_queue_obj(queue
);
216 if (entry
&& entry
->refcount
)
218 if (--entry
->refcount
== 0)
220 if (shared_mt_queue
== queue
) shared_mt_queue
= 0;
221 shutdown_queue((struct queue
*)entry
->obj
);
223 entry
->obj
= next_free_user_queue
;
224 next_free_user_queue
= entry
;
228 LeaveCriticalSection(&queues_section
);
232 static struct queue
*queue_impl_from_IRtwqAsyncCallback(IRtwqAsyncCallback
*iface
)
234 return CONTAINING_RECORD(iface
, struct queue
, IRtwqAsyncCallback_iface
);
237 static HRESULT WINAPI
queue_serial_callback_QueryInterface(IRtwqAsyncCallback
*iface
, REFIID riid
, void **obj
)
239 if (IsEqualIID(riid
, &IID_IRtwqAsyncCallback
) ||
240 IsEqualIID(riid
, &IID_IUnknown
))
243 IRtwqAsyncCallback_AddRef(iface
);
248 return E_NOINTERFACE
;
251 static ULONG WINAPI
queue_serial_callback_AddRef(IRtwqAsyncCallback
*iface
)
256 static ULONG WINAPI
queue_serial_callback_Release(IRtwqAsyncCallback
*iface
)
261 static HRESULT WINAPI
queue_serial_callback_GetParameters(IRtwqAsyncCallback
*iface
, DWORD
*flags
, DWORD
*queue_id
)
263 struct queue
*queue
= queue_impl_from_IRtwqAsyncCallback(iface
);
266 *queue_id
= queue
->id
;
271 static HRESULT WINAPI
queue_serial_callback_Invoke(IRtwqAsyncCallback
*iface
, IRtwqAsyncResult
*result
)
273 /* Reply callback won't be called in a regular way, pending items and chained queues will make it
274 unnecessary complicated to reach actual work queue that's able to execute this item. Instead
275 serial queues are cleaned up right away on submit(). */
279 static const IRtwqAsyncCallbackVtbl queue_serial_callback_vtbl
=
281 queue_serial_callback_QueryInterface
,
282 queue_serial_callback_AddRef
,
283 queue_serial_callback_Release
,
284 queue_serial_callback_GetParameters
,
285 queue_serial_callback_Invoke
,
288 static struct queue system_queues
[SYS_QUEUE_COUNT
];
290 static struct queue
*get_system_queue(DWORD queue_id
)
294 case RTWQ_CALLBACK_QUEUE_STANDARD
:
295 case RTWQ_CALLBACK_QUEUE_RT
:
296 case RTWQ_CALLBACK_QUEUE_IO
:
297 case RTWQ_CALLBACK_QUEUE_TIMER
:
298 case RTWQ_CALLBACK_QUEUE_MULTITHREADED
:
299 case RTWQ_CALLBACK_QUEUE_LONG_FUNCTION
:
300 return &system_queues
[queue_id
- 1];
306 static HRESULT
grab_queue(DWORD queue_id
, struct queue
**ret
);
308 static void CALLBACK
standard_queue_cleanup_callback(void *object_data
, void *group_data
)
312 static HRESULT
pool_queue_init(const struct queue_desc
*desc
, struct queue
*queue
)
314 TP_CALLBACK_ENVIRON_V3 env
;
315 unsigned int max_thread
, i
;
317 queue
->pool
= CreateThreadpool(NULL
);
319 memset(&env
, 0, sizeof(env
));
321 env
.Size
= sizeof(env
);
322 env
.Pool
= queue
->pool
;
323 env
.CleanupGroup
= CreateThreadpoolCleanupGroup();
324 env
.CleanupGroupCancelCallback
= standard_queue_cleanup_callback
;
325 env
.CallbackPriority
= TP_CALLBACK_PRIORITY_NORMAL
;
326 for (i
= 0; i
< ARRAY_SIZE(queue
->envs
); ++i
)
328 queue
->envs
[i
] = env
;
329 queue
->envs
[i
].CallbackPriority
= priorities
[i
];
331 list_init(&queue
->pending_items
);
332 InitializeCriticalSection(&queue
->cs
);
334 max_thread
= (desc
->queue_type
== RTWQ_STANDARD_WORKQUEUE
|| desc
->queue_type
== RTWQ_WINDOW_WORKQUEUE
) ? 1 : 4;
336 SetThreadpoolThreadMinimum(queue
->pool
, 1);
337 SetThreadpoolThreadMaximum(queue
->pool
, max_thread
);
339 if (desc
->queue_type
== RTWQ_WINDOW_WORKQUEUE
)
340 FIXME("RTWQ_WINDOW_WORKQUEUE is not supported.\n");
345 static BOOL
pool_queue_shutdown(struct queue
*queue
)
350 CloseThreadpoolCleanupGroupMembers(queue
->envs
[0].CleanupGroup
, TRUE
, NULL
);
351 CloseThreadpool(queue
->pool
);
357 static void CALLBACK
standard_queue_worker(TP_CALLBACK_INSTANCE
*instance
, void *context
, TP_WORK
*work
)
359 struct work_item
*item
= context
;
360 RTWQASYNCRESULT
*result
= (RTWQASYNCRESULT
*)item
->result
;
362 TRACE("result object %p.\n", result
);
364 /* Submitting from serial queue in reply mode, use different result object acting as receipt token.
365 It's submitted to user callback still, but when invoked, special serial queue callback will be used
366 to ensure correct destination queue. */
368 IRtwqAsyncCallback_Invoke(result
->pCallback
, item
->reply_result
? item
->reply_result
: item
->result
);
370 IUnknown_Release(&item
->IUnknown_iface
);
373 static void pool_queue_submit(struct queue
*queue
, struct work_item
*item
)
375 TP_CALLBACK_PRIORITY callback_priority
;
376 TP_CALLBACK_ENVIRON_V3 env
;
377 TP_WORK
*work_object
;
379 if (item
->priority
== 0)
380 callback_priority
= TP_CALLBACK_PRIORITY_NORMAL
;
381 else if (item
->priority
< 0)
382 callback_priority
= TP_CALLBACK_PRIORITY_LOW
;
384 callback_priority
= TP_CALLBACK_PRIORITY_HIGH
;
386 env
= queue
->envs
[callback_priority
];
387 env
.FinalizationCallback
= item
->finalization_callback
;
388 /* Worker pool callback will release one reference. Grab one more to keep object alive when
389 we need finalization callback. */
390 if (item
->finalization_callback
)
391 IUnknown_AddRef(&item
->IUnknown_iface
);
392 work_object
= CreateThreadpoolWork(standard_queue_worker
, item
, (TP_CALLBACK_ENVIRON
*)&env
);
393 SubmitThreadpoolWork(work_object
);
395 TRACE("dispatched %p.\n", item
->result
);
398 static const struct queue_ops pool_queue_ops
=
405 static struct work_item
* serial_queue_get_next(struct queue
*queue
, struct work_item
*item
)
407 struct work_item
*next_item
= NULL
;
409 list_remove(&item
->entry
);
410 if (!list_empty(&item
->queue
->pending_items
))
411 next_item
= LIST_ENTRY(list_head(&item
->queue
->pending_items
), struct work_item
, entry
);
416 static void CALLBACK
serial_queue_finalization_callback(PTP_CALLBACK_INSTANCE instance
, void *user_data
)
418 struct work_item
*item
= (struct work_item
*)user_data
, *next_item
;
419 struct queue
*target_queue
, *queue
= item
->queue
;
422 EnterCriticalSection(&queue
->cs
);
424 if ((next_item
= serial_queue_get_next(queue
, item
)))
426 if (SUCCEEDED(hr
= grab_queue(queue
->target_queue
, &target_queue
)))
427 target_queue
->ops
->submit(target_queue
, next_item
);
429 WARN("Failed to grab queue for id %#x, hr %#x.\n", queue
->target_queue
, hr
);
432 LeaveCriticalSection(&queue
->cs
);
434 IUnknown_Release(&item
->IUnknown_iface
);
437 static HRESULT
serial_queue_init(const struct queue_desc
*desc
, struct queue
*queue
)
439 queue
->IRtwqAsyncCallback_iface
.lpVtbl
= &queue_serial_callback_vtbl
;
440 queue
->target_queue
= desc
->target_queue
;
441 lock_user_queue(queue
->target_queue
);
442 queue
->finalization_callback
= serial_queue_finalization_callback
;
447 static BOOL
serial_queue_shutdown(struct queue
*queue
)
449 unlock_user_queue(queue
->target_queue
);
454 static struct work_item
* serial_queue_is_ack_token(struct queue
*queue
, struct work_item
*item
)
456 RTWQASYNCRESULT
*async_result
= (RTWQASYNCRESULT
*)item
->result
;
457 struct work_item
*head
;
459 if (list_empty(&queue
->pending_items
))
462 head
= LIST_ENTRY(list_head(&queue
->pending_items
), struct work_item
, entry
);
463 if (head
->reply_result
== item
->result
&& async_result
->pCallback
== &queue
->IRtwqAsyncCallback_iface
)
469 static void serial_queue_submit(struct queue
*queue
, struct work_item
*item
)
471 struct work_item
*head
, *next_item
= NULL
;
472 struct queue
*target_queue
;
475 /* In reply mode queue will advance when 'reply_result' is invoked, in regular mode it will advance automatically,
476 via finalization callback. */
478 if (item
->flags
& RTWQ_REPLY_CALLBACK
)
480 if (FAILED(hr
= RtwqCreateAsyncResult(NULL
, &queue
->IRtwqAsyncCallback_iface
, NULL
, &item
->reply_result
)))
481 WARN("Failed to create reply object, hr %#x.\n", hr
);
484 item
->finalization_callback
= queue
->finalization_callback
;
486 /* Serial queues could be chained together, detach from current queue before transitioning item to this one.
487 Items are not detached when submitted to pool queues, because pool queues won't forward them further. */
488 EnterCriticalSection(&item
->queue
->cs
);
489 list_remove(&item
->entry
);
490 LeaveCriticalSection(&item
->queue
->cs
);
492 EnterCriticalSection(&queue
->cs
);
496 if ((head
= serial_queue_is_ack_token(queue
, item
)))
498 /* Ack receipt token - pop waiting item, advance. */
499 next_item
= serial_queue_get_next(queue
, head
);
500 IUnknown_Release(&head
->IUnknown_iface
);
504 if (list_empty(&queue
->pending_items
))
506 list_add_tail(&queue
->pending_items
, &item
->entry
);
507 IUnknown_AddRef(&item
->IUnknown_iface
);
512 if (SUCCEEDED(hr
= grab_queue(queue
->target_queue
, &target_queue
)))
513 target_queue
->ops
->submit(target_queue
, next_item
);
515 WARN("Failed to grab queue for id %#x, hr %#x.\n", queue
->target_queue
, hr
);
518 LeaveCriticalSection(&queue
->cs
);
521 static const struct queue_ops serial_queue_ops
=
524 serial_queue_shutdown
,
528 static HRESULT WINAPI
work_item_QueryInterface(IUnknown
*iface
, REFIID riid
, void **obj
)
530 if (IsEqualIID(riid
, &IID_IUnknown
))
533 IUnknown_AddRef(iface
);
538 return E_NOINTERFACE
;
541 static ULONG WINAPI
work_item_AddRef(IUnknown
*iface
)
543 struct work_item
*item
= work_item_impl_from_IUnknown(iface
);
544 return InterlockedIncrement(&item
->refcount
);
547 static ULONG WINAPI
work_item_Release(IUnknown
*iface
)
549 struct work_item
*item
= work_item_impl_from_IUnknown(iface
);
550 ULONG refcount
= InterlockedDecrement(&item
->refcount
);
554 if (item
->reply_result
)
555 IRtwqAsyncResult_Release(item
->reply_result
);
556 IRtwqAsyncResult_Release(item
->result
);
563 static const IUnknownVtbl work_item_vtbl
=
565 work_item_QueryInterface
,
570 static struct work_item
* alloc_work_item(struct queue
*queue
, LONG priority
, IRtwqAsyncResult
*result
)
572 RTWQASYNCRESULT
*async_result
= (RTWQASYNCRESULT
*)result
;
573 DWORD flags
= 0, queue_id
= 0;
574 struct work_item
*item
;
576 item
= calloc(1, sizeof(*item
));
578 item
->IUnknown_iface
.lpVtbl
= &work_item_vtbl
;
579 item
->result
= result
;
580 IRtwqAsyncResult_AddRef(item
->result
);
583 list_init(&item
->entry
);
584 item
->priority
= priority
;
586 if (SUCCEEDED(IRtwqAsyncCallback_GetParameters(async_result
->pCallback
, &flags
, &queue_id
)))
592 static void init_work_queue(const struct queue_desc
*desc
, struct queue
*queue
)
594 assert(desc
->ops
!= NULL
);
596 queue
->ops
= desc
->ops
;
597 if (SUCCEEDED(queue
->ops
->init(desc
, queue
)))
599 list_init(&queue
->pending_items
);
600 InitializeCriticalSection(&queue
->cs
);
604 static HRESULT
grab_queue(DWORD queue_id
, struct queue
**ret
)
606 struct queue
*queue
= get_system_queue(queue_id
);
607 RTWQ_WORKQUEUE_TYPE queue_type
;
608 struct queue_handle
*entry
;
612 if (!system_queues
[SYS_QUEUE_STANDARD
].pool
)
613 return RTWQ_E_SHUTDOWN
;
615 if (queue
&& queue
->pool
)
622 struct queue_desc desc
;
624 EnterCriticalSection(&queues_section
);
627 case RTWQ_CALLBACK_QUEUE_IO
:
628 case RTWQ_CALLBACK_QUEUE_MULTITHREADED
:
629 case RTWQ_CALLBACK_QUEUE_LONG_FUNCTION
:
630 queue_type
= RTWQ_MULTITHREADED_WORKQUEUE
;
633 queue_type
= RTWQ_STANDARD_WORKQUEUE
;
636 desc
.queue_type
= queue_type
;
637 desc
.ops
= &pool_queue_ops
;
638 desc
.target_queue
= 0;
639 init_work_queue(&desc
, queue
);
640 LeaveCriticalSection(&queues_section
);
645 /* Handles user queues. */
646 if ((entry
= get_queue_obj(queue_id
)))
649 return *ret
? S_OK
: RTWQ_E_INVALID_WORKQUEUE
;
652 static void shutdown_queue(struct queue
*queue
)
654 struct work_item
*item
, *item2
;
656 if (!queue
->ops
|| !queue
->ops
->shutdown(queue
))
659 EnterCriticalSection(&queue
->cs
);
660 LIST_FOR_EACH_ENTRY_SAFE(item
, item2
, &queue
->pending_items
, struct work_item
, entry
)
662 list_remove(&item
->entry
);
663 IUnknown_Release(&item
->IUnknown_iface
);
665 LeaveCriticalSection(&queue
->cs
);
667 DeleteCriticalSection(&queue
->cs
);
669 memset(queue
, 0, sizeof(*queue
));
672 static HRESULT
queue_submit_item(struct queue
*queue
, LONG priority
, IRtwqAsyncResult
*result
)
674 struct work_item
*item
;
676 if (!(item
= alloc_work_item(queue
, priority
, result
)))
677 return E_OUTOFMEMORY
;
679 queue
->ops
->submit(queue
, item
);
684 static HRESULT
queue_put_work_item(DWORD queue_id
, LONG priority
, IRtwqAsyncResult
*result
)
689 if (FAILED(hr
= grab_queue(queue_id
, &queue
)))
692 return queue_submit_item(queue
, priority
, result
);
695 static HRESULT
invoke_async_callback(IRtwqAsyncResult
*result
)
697 RTWQASYNCRESULT
*result_data
= (RTWQASYNCRESULT
*)result
;
698 DWORD queue
= RTWQ_CALLBACK_QUEUE_STANDARD
, flags
;
701 if (FAILED(IRtwqAsyncCallback_GetParameters(result_data
->pCallback
, &flags
, &queue
)))
702 queue
= RTWQ_CALLBACK_QUEUE_STANDARD
;
704 if (FAILED(lock_user_queue(queue
)))
705 queue
= RTWQ_CALLBACK_QUEUE_STANDARD
;
707 hr
= queue_put_work_item(queue
, 0, result
);
709 unlock_user_queue(queue
);
714 static void queue_release_pending_item(struct work_item
*item
)
716 EnterCriticalSection(&item
->queue
->cs
);
719 list_remove(&item
->entry
);
721 IUnknown_Release(&item
->IUnknown_iface
);
723 LeaveCriticalSection(&item
->queue
->cs
);
726 static void CALLBACK
waiting_item_callback(TP_CALLBACK_INSTANCE
*instance
, void *context
, TP_WAIT
*wait
,
727 TP_WAIT_RESULT wait_result
)
729 struct work_item
*item
= context
;
731 TRACE("result object %p.\n", item
->result
);
733 invoke_async_callback(item
->result
);
735 IUnknown_Release(&item
->IUnknown_iface
);
738 static void CALLBACK
waiting_item_cancelable_callback(TP_CALLBACK_INSTANCE
*instance
, void *context
, TP_WAIT
*wait
,
739 TP_WAIT_RESULT wait_result
)
741 struct work_item
*item
= context
;
743 TRACE("result object %p.\n", item
->result
);
745 queue_release_pending_item(item
);
747 invoke_async_callback(item
->result
);
749 IUnknown_Release(&item
->IUnknown_iface
);
752 static void CALLBACK
scheduled_item_callback(TP_CALLBACK_INSTANCE
*instance
, void *context
, TP_TIMER
*timer
)
754 struct work_item
*item
= context
;
756 TRACE("result object %p.\n", item
->result
);
758 invoke_async_callback(item
->result
);
760 IUnknown_Release(&item
->IUnknown_iface
);
763 static void CALLBACK
scheduled_item_cancelable_callback(TP_CALLBACK_INSTANCE
*instance
, void *context
, TP_TIMER
*timer
)
765 struct work_item
*item
= context
;
767 TRACE("result object %p.\n", item
->result
);
769 queue_release_pending_item(item
);
771 invoke_async_callback(item
->result
);
773 IUnknown_Release(&item
->IUnknown_iface
);
776 static void CALLBACK
periodic_item_callback(TP_CALLBACK_INSTANCE
*instance
, void *context
, TP_TIMER
*timer
)
778 struct work_item
*item
= context
;
780 IUnknown_AddRef(&item
->IUnknown_iface
);
782 invoke_async_callback(item
->result
);
784 IUnknown_Release(&item
->IUnknown_iface
);
787 static void queue_mark_item_pending(DWORD mask
, struct work_item
*item
, RTWQWORKITEM_KEY
*key
)
789 *key
= generate_item_key(mask
);
792 EnterCriticalSection(&item
->queue
->cs
);
793 list_add_tail(&item
->queue
->pending_items
, &item
->entry
);
794 IUnknown_AddRef(&item
->IUnknown_iface
);
795 LeaveCriticalSection(&item
->queue
->cs
);
798 static HRESULT
queue_submit_wait(struct queue
*queue
, HANDLE event
, LONG priority
, IRtwqAsyncResult
*result
,
799 RTWQWORKITEM_KEY
*key
)
801 PTP_WAIT_CALLBACK callback
;
802 struct work_item
*item
;
804 if (!(item
= alloc_work_item(queue
, priority
, result
)))
805 return E_OUTOFMEMORY
;
809 queue_mark_item_pending(WAIT_ITEM_KEY_MASK
, item
, key
);
810 callback
= waiting_item_cancelable_callback
;
813 callback
= waiting_item_callback
;
815 item
->u
.wait_object
= CreateThreadpoolWait(callback
, item
,
816 (TP_CALLBACK_ENVIRON
*)&queue
->envs
[TP_CALLBACK_PRIORITY_NORMAL
]);
817 SetThreadpoolWait(item
->u
.wait_object
, event
, NULL
);
819 TRACE("dispatched %p.\n", result
);
824 static HRESULT
queue_submit_timer(struct queue
*queue
, IRtwqAsyncResult
*result
, INT64 timeout
, DWORD period
,
825 RTWQWORKITEM_KEY
*key
)
827 PTP_TIMER_CALLBACK callback
;
828 struct work_item
*item
;
832 if (!(item
= alloc_work_item(queue
, 0, result
)))
833 return E_OUTOFMEMORY
;
837 queue_mark_item_pending(SCHEDULED_ITEM_KEY_MASK
, item
, key
);
841 callback
= periodic_item_callback
;
843 callback
= key
? scheduled_item_cancelable_callback
: scheduled_item_callback
;
845 t
.QuadPart
= timeout
* 1000 * 10;
846 filetime
.dwLowDateTime
= t
.u
.LowPart
;
847 filetime
.dwHighDateTime
= t
.u
.HighPart
;
849 item
->u
.timer_object
= CreateThreadpoolTimer(callback
, item
,
850 (TP_CALLBACK_ENVIRON
*)&queue
->envs
[TP_CALLBACK_PRIORITY_NORMAL
]);
851 SetThreadpoolTimer(item
->u
.timer_object
, &filetime
, period
, 0);
853 TRACE("dispatched %p.\n", result
);
858 static HRESULT
queue_cancel_item(struct queue
*queue
, RTWQWORKITEM_KEY key
)
860 HRESULT hr
= RTWQ_E_NOT_FOUND
;
861 struct work_item
*item
;
863 EnterCriticalSection(&queue
->cs
);
864 LIST_FOR_EACH_ENTRY(item
, &queue
->pending_items
, struct work_item
, entry
)
866 if (item
->key
== key
)
869 if ((key
& WAIT_ITEM_KEY_MASK
) == WAIT_ITEM_KEY_MASK
)
871 IRtwqAsyncResult_SetStatus(item
->result
, RTWQ_E_OPERATION_CANCELLED
);
872 invoke_async_callback(item
->result
);
873 CloseThreadpoolWait(item
->u
.wait_object
);
875 else if ((key
& SCHEDULED_ITEM_KEY_MASK
) == SCHEDULED_ITEM_KEY_MASK
)
876 CloseThreadpoolTimer(item
->u
.timer_object
);
878 WARN("Unknown item key mask %#x.\n", (DWORD
)key
);
879 queue_release_pending_item(item
);
884 LeaveCriticalSection(&queue
->cs
);
889 static HRESULT
alloc_user_queue(const struct queue_desc
*desc
, DWORD
*queue_id
)
891 struct queue_handle
*entry
;
895 *queue_id
= RTWQ_CALLBACK_QUEUE_UNDEFINED
;
897 if (platform_lock
<= 0)
898 return RTWQ_E_SHUTDOWN
;
900 if (!(queue
= calloc(1, sizeof(*queue
))))
901 return E_OUTOFMEMORY
;
903 init_work_queue(desc
, queue
);
905 EnterCriticalSection(&queues_section
);
907 entry
= next_free_user_queue
;
909 next_free_user_queue
= entry
->obj
;
910 else if (next_unused_user_queue
< user_queues
+ MAX_USER_QUEUE_HANDLES
)
911 entry
= next_unused_user_queue
++;
914 LeaveCriticalSection(&queues_section
);
916 WARN("Out of user queue handles.\n");
917 return E_OUTOFMEMORY
;
922 if (++queue_generation
== 0xffff) queue_generation
= 1;
923 entry
->generation
= queue_generation
;
924 idx
= entry
- user_queues
+ FIRST_USER_QUEUE_HANDLE
;
925 *queue_id
= (idx
<< 16) | entry
->generation
;
927 LeaveCriticalSection(&queues_section
);
934 RTWQASYNCRESULT result
;
940 static struct async_result
*impl_from_IRtwqAsyncResult(IRtwqAsyncResult
*iface
)
942 return CONTAINING_RECORD(iface
, struct async_result
, result
.AsyncResult
);
945 static HRESULT WINAPI
async_result_QueryInterface(IRtwqAsyncResult
*iface
, REFIID riid
, void **obj
)
947 TRACE("%p, %s, %p.\n", iface
, debugstr_guid(riid
), obj
);
949 if (IsEqualIID(riid
, &IID_IRtwqAsyncResult
) ||
950 IsEqualIID(riid
, &IID_IUnknown
))
953 IRtwqAsyncResult_AddRef(iface
);
958 WARN("Unsupported interface %s.\n", debugstr_guid(riid
));
959 return E_NOINTERFACE
;
962 static ULONG WINAPI
async_result_AddRef(IRtwqAsyncResult
*iface
)
964 struct async_result
*result
= impl_from_IRtwqAsyncResult(iface
);
965 ULONG refcount
= InterlockedIncrement(&result
->refcount
);
967 TRACE("%p, %u.\n", iface
, refcount
);
972 static ULONG WINAPI
async_result_Release(IRtwqAsyncResult
*iface
)
974 struct async_result
*result
= impl_from_IRtwqAsyncResult(iface
);
975 ULONG refcount
= InterlockedDecrement(&result
->refcount
);
977 TRACE("%p, %u.\n", iface
, refcount
);
981 if (result
->result
.pCallback
)
982 IRtwqAsyncCallback_Release(result
->result
.pCallback
);
984 IUnknown_Release(result
->object
);
986 IUnknown_Release(result
->state
);
987 if (result
->result
.hEvent
)
988 CloseHandle(result
->result
.hEvent
);
991 RtwqUnlockPlatform();
997 static HRESULT WINAPI
async_result_GetState(IRtwqAsyncResult
*iface
, IUnknown
**state
)
999 struct async_result
*result
= impl_from_IRtwqAsyncResult(iface
);
1001 TRACE("%p, %p.\n", iface
, state
);
1006 *state
= result
->state
;
1007 IUnknown_AddRef(*state
);
1012 static HRESULT WINAPI
async_result_GetStatus(IRtwqAsyncResult
*iface
)
1014 struct async_result
*result
= impl_from_IRtwqAsyncResult(iface
);
1016 TRACE("%p.\n", iface
);
1018 return result
->result
.hrStatusResult
;
1021 static HRESULT WINAPI
async_result_SetStatus(IRtwqAsyncResult
*iface
, HRESULT status
)
1023 struct async_result
*result
= impl_from_IRtwqAsyncResult(iface
);
1025 TRACE("%p, %#x.\n", iface
, status
);
1027 result
->result
.hrStatusResult
= status
;
1032 static HRESULT WINAPI
async_result_GetObject(IRtwqAsyncResult
*iface
, IUnknown
**object
)
1034 struct async_result
*result
= impl_from_IRtwqAsyncResult(iface
);
1036 TRACE("%p, %p.\n", iface
, object
);
1038 if (!result
->object
)
1041 *object
= result
->object
;
1042 IUnknown_AddRef(*object
);
1047 static IUnknown
* WINAPI
async_result_GetStateNoAddRef(IRtwqAsyncResult
*iface
)
1049 struct async_result
*result
= impl_from_IRtwqAsyncResult(iface
);
1051 TRACE("%p.\n", iface
);
1053 return result
->state
;
1056 static const IRtwqAsyncResultVtbl async_result_vtbl
=
1058 async_result_QueryInterface
,
1059 async_result_AddRef
,
1060 async_result_Release
,
1061 async_result_GetState
,
1062 async_result_GetStatus
,
1063 async_result_SetStatus
,
1064 async_result_GetObject
,
1065 async_result_GetStateNoAddRef
,
1068 static HRESULT
create_async_result(IUnknown
*object
, IRtwqAsyncCallback
*callback
, IUnknown
*state
, IRtwqAsyncResult
**out
)
1070 struct async_result
*result
;
1073 return E_INVALIDARG
;
1075 if (!(result
= calloc(1, sizeof(*result
))))
1076 return E_OUTOFMEMORY
;
1080 result
->result
.AsyncResult
.lpVtbl
= &async_result_vtbl
;
1081 result
->refcount
= 1;
1082 result
->object
= object
;
1084 IUnknown_AddRef(result
->object
);
1085 result
->result
.pCallback
= callback
;
1086 if (result
->result
.pCallback
)
1087 IRtwqAsyncCallback_AddRef(result
->result
.pCallback
);
1088 result
->state
= state
;
1090 IUnknown_AddRef(result
->state
);
1092 *out
= &result
->result
.AsyncResult
;
1094 TRACE("Created async result object %p.\n", *out
);
1099 HRESULT WINAPI
RtwqCreateAsyncResult(IUnknown
*object
, IRtwqAsyncCallback
*callback
, IUnknown
*state
,
1100 IRtwqAsyncResult
**out
)
1102 TRACE("%p, %p, %p, %p.\n", object
, callback
, state
, out
);
1104 return create_async_result(object
, callback
, state
, out
);
1107 HRESULT WINAPI
RtwqLockPlatform(void)
1109 InterlockedIncrement(&platform_lock
);
1114 HRESULT WINAPI
RtwqUnlockPlatform(void)
1116 InterlockedDecrement(&platform_lock
);
1121 static void init_system_queues(void)
1123 struct queue_desc desc
;
1126 /* Always initialize standard queue, keep the rest lazy. */
1128 EnterCriticalSection(&queues_section
);
1130 if (system_queues
[SYS_QUEUE_STANDARD
].pool
)
1132 LeaveCriticalSection(&queues_section
);
1136 if (FAILED(hr
= CoIncrementMTAUsage(&mta_cookie
)))
1137 WARN("Failed to initialize MTA, hr %#x.\n", hr
);
1139 desc
.queue_type
= RTWQ_STANDARD_WORKQUEUE
;
1140 desc
.ops
= &pool_queue_ops
;
1141 desc
.target_queue
= 0;
1142 init_work_queue(&desc
, &system_queues
[SYS_QUEUE_STANDARD
]);
1144 LeaveCriticalSection(&queues_section
);
1147 HRESULT WINAPI
RtwqStartup(void)
1149 if (InterlockedIncrement(&platform_lock
) == 1)
1151 init_system_queues();
1157 static void shutdown_system_queues(void)
1162 EnterCriticalSection(&queues_section
);
1164 for (i
= 0; i
< ARRAY_SIZE(system_queues
); ++i
)
1166 shutdown_queue(&system_queues
[i
]);
1169 if (FAILED(hr
= CoDecrementMTAUsage(mta_cookie
)))
1170 WARN("Failed to uninitialize MTA, hr %#x.\n", hr
);
1172 LeaveCriticalSection(&queues_section
);
1175 HRESULT WINAPI
RtwqShutdown(void)
1177 if (platform_lock
<= 0)
1180 if (InterlockedExchangeAdd(&platform_lock
, -1) == 1)
1182 shutdown_system_queues();
1188 HRESULT WINAPI
RtwqPutWaitingWorkItem(HANDLE event
, LONG priority
, IRtwqAsyncResult
*result
, RTWQWORKITEM_KEY
*key
)
1190 struct queue
*queue
;
1193 TRACE("%p, %d, %p, %p.\n", event
, priority
, result
, key
);
1195 if (FAILED(hr
= grab_queue(RTWQ_CALLBACK_QUEUE_TIMER
, &queue
)))
1198 hr
= queue_submit_wait(queue
, event
, priority
, result
, key
);
1203 static HRESULT
schedule_work_item(IRtwqAsyncResult
*result
, INT64 timeout
, RTWQWORKITEM_KEY
*key
)
1205 struct queue
*queue
;
1208 if (FAILED(hr
= grab_queue(RTWQ_CALLBACK_QUEUE_TIMER
, &queue
)))
1211 TRACE("%p, %s, %p.\n", result
, wine_dbgstr_longlong(timeout
), key
);
1213 return queue_submit_timer(queue
, result
, timeout
, 0, key
);
1216 HRESULT WINAPI
RtwqScheduleWorkItem(IRtwqAsyncResult
*result
, INT64 timeout
, RTWQWORKITEM_KEY
*key
)
1218 TRACE("%p, %s, %p.\n", result
, wine_dbgstr_longlong(timeout
), key
);
1220 return schedule_work_item(result
, timeout
, key
);
1223 struct periodic_callback
1225 IRtwqAsyncCallback IRtwqAsyncCallback_iface
;
1227 RTWQPERIODICCALLBACK callback
;
1230 static struct periodic_callback
*impl_from_IRtwqAsyncCallback(IRtwqAsyncCallback
*iface
)
1232 return CONTAINING_RECORD(iface
, struct periodic_callback
, IRtwqAsyncCallback_iface
);
1235 static HRESULT WINAPI
periodic_callback_QueryInterface(IRtwqAsyncCallback
*iface
, REFIID riid
, void **obj
)
1237 if (IsEqualIID(riid
, &IID_IRtwqAsyncCallback
) ||
1238 IsEqualIID(riid
, &IID_IUnknown
))
1241 IRtwqAsyncCallback_AddRef(iface
);
1246 return E_NOINTERFACE
;
1249 static ULONG WINAPI
periodic_callback_AddRef(IRtwqAsyncCallback
*iface
)
1251 struct periodic_callback
*callback
= impl_from_IRtwqAsyncCallback(iface
);
1252 ULONG refcount
= InterlockedIncrement(&callback
->refcount
);
1254 TRACE("%p, %u.\n", iface
, refcount
);
1259 static ULONG WINAPI
periodic_callback_Release(IRtwqAsyncCallback
*iface
)
1261 struct periodic_callback
*callback
= impl_from_IRtwqAsyncCallback(iface
);
1262 ULONG refcount
= InterlockedDecrement(&callback
->refcount
);
1264 TRACE("%p, %u.\n", iface
, refcount
);
1272 static HRESULT WINAPI
periodic_callback_GetParameters(IRtwqAsyncCallback
*iface
, DWORD
*flags
, DWORD
*queue
)
1277 static HRESULT WINAPI
periodic_callback_Invoke(IRtwqAsyncCallback
*iface
, IRtwqAsyncResult
*result
)
1279 struct periodic_callback
*callback
= impl_from_IRtwqAsyncCallback(iface
);
1280 IUnknown
*context
= NULL
;
1282 if (FAILED(IRtwqAsyncResult_GetObject(result
, &context
)))
1283 WARN("Expected object to be set for result object.\n");
1285 callback
->callback(context
);
1288 IUnknown_Release(context
);
1293 static const IRtwqAsyncCallbackVtbl periodic_callback_vtbl
=
1295 periodic_callback_QueryInterface
,
1296 periodic_callback_AddRef
,
1297 periodic_callback_Release
,
1298 periodic_callback_GetParameters
,
1299 periodic_callback_Invoke
,
1302 static HRESULT
create_periodic_callback_obj(RTWQPERIODICCALLBACK callback
, IRtwqAsyncCallback
**out
)
1304 struct periodic_callback
*object
;
1306 if (!(object
= calloc(1, sizeof(*object
))))
1307 return E_OUTOFMEMORY
;
1309 object
->IRtwqAsyncCallback_iface
.lpVtbl
= &periodic_callback_vtbl
;
1310 object
->refcount
= 1;
1311 object
->callback
= callback
;
1313 *out
= &object
->IRtwqAsyncCallback_iface
;
1318 HRESULT WINAPI
RtwqAddPeriodicCallback(RTWQPERIODICCALLBACK callback
, IUnknown
*context
, DWORD
*key
)
1320 IRtwqAsyncCallback
*periodic_callback
;
1321 RTWQWORKITEM_KEY workitem_key
;
1322 IRtwqAsyncResult
*result
;
1323 struct queue
*queue
;
1326 TRACE("%p, %p, %p.\n", callback
, context
, key
);
1328 if (FAILED(hr
= grab_queue(RTWQ_CALLBACK_QUEUE_TIMER
, &queue
)))
1331 if (FAILED(hr
= create_periodic_callback_obj(callback
, &periodic_callback
)))
1334 hr
= create_async_result(context
, periodic_callback
, NULL
, &result
);
1335 IRtwqAsyncCallback_Release(periodic_callback
);
1339 /* Same period MFGetTimerPeriodicity() returns. */
1340 hr
= queue_submit_timer(queue
, result
, 0, 10, key
? &workitem_key
: NULL
);
1342 IRtwqAsyncResult_Release(result
);
1345 *key
= workitem_key
;
1350 HRESULT WINAPI
RtwqRemovePeriodicCallback(DWORD key
)
1352 struct queue
*queue
;
1355 TRACE("%#x.\n", key
);
1357 if (FAILED(hr
= grab_queue(RTWQ_CALLBACK_QUEUE_TIMER
, &queue
)))
1360 return queue_cancel_item(queue
, get_item_key(SCHEDULED_ITEM_KEY_MASK
, key
));
1363 HRESULT WINAPI
RtwqCancelWorkItem(RTWQWORKITEM_KEY key
)
1365 struct queue
*queue
;
1368 TRACE("%s.\n", wine_dbgstr_longlong(key
));
1370 if (FAILED(hr
= grab_queue(RTWQ_CALLBACK_QUEUE_TIMER
, &queue
)))
1373 return queue_cancel_item(queue
, key
);
1376 HRESULT WINAPI
RtwqInvokeCallback(IRtwqAsyncResult
*result
)
1378 TRACE("%p.\n", result
);
1380 return invoke_async_callback(result
);
1383 HRESULT WINAPI
RtwqPutWorkItem(DWORD queue
, LONG priority
, IRtwqAsyncResult
*result
)
1385 TRACE("%#x, %d, %p.\n", queue
, priority
, result
);
1387 return queue_put_work_item(queue
, priority
, result
);
1390 HRESULT WINAPI
RtwqAllocateWorkQueue(RTWQ_WORKQUEUE_TYPE queue_type
, DWORD
*queue
)
1392 struct queue_desc desc
;
1394 TRACE("%d, %p.\n", queue_type
, queue
);
1396 desc
.queue_type
= queue_type
;
1397 desc
.ops
= &pool_queue_ops
;
1398 desc
.target_queue
= 0;
1399 return alloc_user_queue(&desc
, queue
);
1402 HRESULT WINAPI
RtwqLockWorkQueue(DWORD queue
)
1404 TRACE("%#x.\n", queue
);
1406 return lock_user_queue(queue
);
1409 HRESULT WINAPI
RtwqUnlockWorkQueue(DWORD queue
)
1411 TRACE("%#x.\n", queue
);
1413 return unlock_user_queue(queue
);
1416 HRESULT WINAPI
RtwqSetLongRunning(DWORD queue_id
, BOOL enable
)
1418 struct queue
*queue
;
1422 TRACE("%#x, %d.\n", queue_id
, enable
);
1424 lock_user_queue(queue_id
);
1426 if (SUCCEEDED(hr
= grab_queue(queue_id
, &queue
)))
1428 for (i
= 0; i
< ARRAY_SIZE(queue
->envs
); ++i
)
1429 queue
->envs
[i
].u
.s
.LongFunction
= !!enable
;
1432 unlock_user_queue(queue_id
);
1437 HRESULT WINAPI
RtwqLockSharedWorkQueue(const WCHAR
*usageclass
, LONG priority
, DWORD
*taskid
, DWORD
*queue
)
1439 struct queue_desc desc
;
1442 TRACE("%s, %d, %p, %p.\n", debugstr_w(usageclass
), priority
, taskid
, queue
);
1447 if (!*usageclass
&& taskid
)
1448 return E_INVALIDARG
;
1451 FIXME("Class name is ignored.\n");
1453 EnterCriticalSection(&queues_section
);
1455 if (shared_mt_queue
)
1456 hr
= lock_user_queue(shared_mt_queue
);
1459 desc
.queue_type
= RTWQ_MULTITHREADED_WORKQUEUE
;
1460 desc
.ops
= &pool_queue_ops
;
1461 desc
.target_queue
= 0;
1462 hr
= alloc_user_queue(&desc
, &shared_mt_queue
);
1465 *queue
= shared_mt_queue
;
1467 LeaveCriticalSection(&queues_section
);
1472 HRESULT WINAPI
RtwqSetDeadline(DWORD queue_id
, LONGLONG deadline
, HANDLE
*request
)
1474 FIXME("%#x, %s, %p.\n", queue_id
, wine_dbgstr_longlong(deadline
), request
);
1479 HRESULT WINAPI
RtwqSetDeadline2(DWORD queue_id
, LONGLONG deadline
, LONGLONG predeadline
, HANDLE
*request
)
1481 FIXME("%#x, %s, %s, %p.\n", queue_id
, wine_dbgstr_longlong(deadline
), wine_dbgstr_longlong(predeadline
), request
);
1486 HRESULT WINAPI
RtwqCancelDeadline(HANDLE request
)
1488 FIXME("%p.\n", request
);
1493 HRESULT WINAPI
RtwqAllocateSerialWorkQueue(DWORD target_queue
, DWORD
*queue
)
1495 struct queue_desc desc
;
1497 TRACE("%#x, %p.\n", target_queue
, queue
);
1499 desc
.queue_type
= RTWQ_STANDARD_WORKQUEUE
;
1500 desc
.ops
= &serial_queue_ops
;
1501 desc
.target_queue
= target_queue
;
1502 return alloc_user_queue(&desc
, queue
);
1505 HRESULT WINAPI
RtwqJoinWorkQueue(DWORD queue
, HANDLE hFile
, HANDLE
*cookie
)
1507 FIXME("%#x, %p, %p.\n", queue
, hFile
, cookie
);
1512 HRESULT WINAPI
RtwqUnjoinWorkQueue(DWORD queue
, HANDLE cookie
)
1514 FIXME("%#x, %p.\n", queue
, cookie
);
1519 HRESULT WINAPI
RtwqGetWorkQueueMMCSSClass(DWORD queue
, WCHAR
*class, DWORD
*length
)
1521 FIXME("%#x, %p, %p.\n", queue
, class, length
);
1526 HRESULT WINAPI
RtwqGetWorkQueueMMCSSTaskId(DWORD queue
, DWORD
*taskid
)
1528 FIXME("%#x, %p.\n", queue
, taskid
);
1533 HRESULT WINAPI
RtwqGetWorkQueueMMCSSPriority(DWORD queue
, LONG
*priority
)
1535 FIXME("%#x, %p.\n", queue
, priority
);
1540 HRESULT WINAPI
RtwqRegisterPlatformWithMMCSS(const WCHAR
*class, DWORD
*taskid
, LONG priority
)
1542 FIXME("%s, %p, %d.\n", debugstr_w(class), taskid
, priority
);
1547 HRESULT WINAPI
RtwqUnregisterPlatformFromMMCSS(void)
1554 HRESULT WINAPI
RtwqBeginRegisterWorkQueueWithMMCSS(DWORD queue
, const WCHAR
*class, DWORD taskid
, LONG priority
,
1555 IRtwqAsyncCallback
*callback
, IUnknown
*state
)
1557 FIXME("%#x, %s, %u, %d, %p, %p.\n", queue
, debugstr_w(class), taskid
, priority
, callback
, state
);
1562 HRESULT WINAPI
RtwqEndRegisterWorkQueueWithMMCSS(IRtwqAsyncResult
*result
, DWORD
*taskid
)
1564 FIXME("%p, %p.\n", result
, taskid
);
1569 HRESULT WINAPI
RtwqBeginUnregisterWorkQueueWithMMCSS(DWORD queue
, IRtwqAsyncCallback
*callback
, IUnknown
*state
)
1571 FIXME("%#x, %p, %p.\n", queue
, callback
, state
);
1576 HRESULT WINAPI
RtwqEndUnregisterWorkQueueWithMMCSS(IRtwqAsyncResult
*result
)
1578 FIXME("%p.\n", result
);
1583 HRESULT WINAPI
RtwqRegisterPlatformEvents(IRtwqPlatformEvents
*events
)
1585 FIXME("%p.\n", events
);
1590 HRESULT WINAPI
RtwqUnregisterPlatformEvents(IRtwqPlatformEvents
*events
)
1592 FIXME("%p.\n", events
);