From: Vibhav Pant vibhavp@gmail.com
--- dlls/vccorlib140/delegate.c | 372 ++++++++++++++++++++++++++++-- dlls/vccorlib140/private.h | 5 + dlls/vccorlib140/tests/vccorlib.c | 45 ++-- dlls/vccorlib140/vccorlib.c | 1 + 4 files changed, 378 insertions(+), 45 deletions(-)
diff --git a/dlls/vccorlib140/delegate.c b/dlls/vccorlib140/delegate.c index 73b289ddf3e..a9a33d94713 100644 --- a/dlls/vccorlib140/delegate.c +++ b/dlls/vccorlib140/delegate.c @@ -16,14 +16,24 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * These functions are used to implement WinRT event handling using delegates. + * A delegate is an ITypedEventHandler<T> object with an Invoke method. Components + * register delegates with an event source, and receive an EventRegistrationToken in return. + * The token can be later used to unregister the delegate in order to stop receiving events. */
#define COBJMACROS #include "eventtoken.h" #include "inspectable.h" +#include "weakreference.h"
+#include "wine/exception.h" #include "wine/debug.h"
+#include "cxx.h" +#include "private.h" + WINE_DEFAULT_DEBUG_CHANNEL(vccorlib);
void *__cdecl Delegate_ctor(void *this) @@ -34,40 +44,370 @@ void *__cdecl Delegate_ctor(void *this)
void WINAPI EventSourceInitialize(IInspectable **source) { - FIXME("(%p): stub!\n", source); + TRACE("(%p)\n", source); + *source = NULL; }
void WINAPI EventSourceUninitialize(IInspectable **source) { - FIXME("(%p): stub!\n", source); + TRACE("(%p)\n", source); + + if (*source) + IInspectable_Release(*source); + *source = NULL; +} + +/* Probably not binary compatible with native, but it shouldn't be needed as programs only modify the delegate list with + * EventSourceAdd/Remove, and read it though EventSourceGetTargetArrayEvent/Size. */ +struct EventTargetArray +{ + IInspectable IInspectable_iface; + IWeakReferenceSource IWeakReferenceSource_iface; + struct control_block *block; + IUnknown *marshal; + ULONG count; + /* Use agile references heres so that delegates can be invoked from any apartment. */ + IAgileReference *delegates[1]; +}; + +static inline struct EventTargetArray *impl_from_IInspectable(IInspectable *iface) +{ + return CONTAINING_RECORD(iface, struct EventTargetArray, IInspectable_iface); +} + +static HRESULT WINAPI EventTargetArray_QueryInterface(IInspectable *iface, const GUID *iid, void **out) +{ + struct EventTargetArray *impl = impl_from_IInspectable(iface); + + TRACE("(%p, %s, %p)\n", iface, debugstr_guid(iid), out); + + if (IsEqualGUID(iid, &IID_IUnknown) || + IsEqualGUID(iid, &IID_IInspectable) || + IsEqualGUID(iid, &IID_IAgileObject)) + { + IInspectable_AddRef((*out = &impl->IInspectable_iface)); + return S_OK; + } + if (IsEqualGUID(iid, &IID_IWeakReferenceSource)) + { + IWeakReferenceSource_AddRef((*out = &impl->IWeakReferenceSource_iface)); + return S_OK; + } + if (IsEqualGUID(iid, &IID_IMarshal)) + return IUnknown_QueryInterface(impl->marshal, iid, out); + + *out = NULL; + ERR("%s not implemented, returning E_NOINTERFACE.\n", debugstr_guid(iid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI EventTargetArray_AddRef(IInspectable *iface) +{ + struct EventTargetArray *impl = impl_from_IInspectable(iface); + TRACE("(%p)\n", iface); + return InterlockedIncrement(&impl->block->ref_strong); +} + +static ULONG WINAPI EventTargetArray_Release(IInspectable *iface) +{ + struct EventTargetArray *impl = impl_from_IInspectable(iface); + struct control_block *block; + ULONG ref; + + TRACE("(%p)\n", iface); + + block = impl->block; + if (!(ref = InterlockedDecrement(&block->ref_strong))) + { + ULONG i; + + for (i = 0; i < impl->count; i++) + IAgileReference_Release(impl->delegates[i]); + IUnknown_Release(impl->marshal); + /* ReleaseTarget will only free the object if ref_strong < 0. */ + WriteNoFence(&impl->block->ref_strong, -1); + control_block_ReleaseTarget(impl->block); + IWeakReference_Release(&block->IWeakReference_iface); + } + return ref; +} + +static HRESULT WINAPI EventTargetArray_GetIids(IInspectable *iface, ULONG *ret_count, IID **ret_iids) +{ + IID *iids; + + TRACE("(%p, %p, %p)\n", iface, ret_count, ret_iids); + + if (!(iids = CoTaskMemAlloc(2 * sizeof(*iids)))) + return E_OUTOFMEMORY; + + iids[0] = IID_IInspectable; + iids[1] = IID_IWeakReferenceSource; + *ret_iids = iids; + *ret_count = 2; + return S_OK; +} + +static HRESULT WINAPI EventTargetArray_GetRuntimeClassName(IInspectable *iface, HSTRING *name) +{ + TRACE("(%p, %p)\n", iface, name); + /* Native returns an empty string. */ + *name = NULL; + return S_OK; +} + +static HRESULT WINAPI EventTargetArray_GetTrustLevel(IInspectable *iface, TrustLevel *level) +{ + FIXME("(%p, %p): stub!\n", iface, level); + return E_NOTIMPL; +} + +DEFINE_RTTI_DATA(EventTargetArray, 0, ".?AVEventTargetArray@Details@Platform@@"); +COM_VTABLE_RTTI_START(IInspectable, EventTargetArray) +COM_VTABLE_ENTRY(EventTargetArray_QueryInterface) +COM_VTABLE_ENTRY(EventTargetArray_AddRef) +COM_VTABLE_ENTRY(EventTargetArray_Release) +COM_VTABLE_ENTRY(EventTargetArray_GetIids) +COM_VTABLE_ENTRY(EventTargetArray_GetRuntimeClassName) +COM_VTABLE_ENTRY(EventTargetArray_GetTrustLevel) +COM_VTABLE_RTTI_END; + +static inline struct EventTargetArray *impl_from_IWeakReferenceSource(IWeakReferenceSource *iface) +{ + return CONTAINING_RECORD(iface, struct EventTargetArray, IWeakReferenceSource_iface); +} + +static HRESULT WINAPI EventTargetArray_weakref_src_QueryInterface(IWeakReferenceSource *iface, const GUID *iid, void **out) +{ + struct EventTargetArray *impl = impl_from_IWeakReferenceSource(iface); + return IInspectable_QueryInterface(&impl->IInspectable_iface, iid, out); +} + +static ULONG WINAPI EventTargetArray_weakref_src_AddRef(IWeakReferenceSource *iface) +{ + struct EventTargetArray *impl = impl_from_IWeakReferenceSource(iface); + return IInspectable_AddRef(&impl->IInspectable_iface); +} + +static ULONG WINAPI EventTargetArray_weakref_src_Release(IWeakReferenceSource *iface) +{ + struct EventTargetArray *impl = impl_from_IWeakReferenceSource(iface); + return IInspectable_Release(&impl->IInspectable_iface); +} + +static HRESULT WINAPI EventTargetArray_weakref_src_GetWeakReference(IWeakReferenceSource *iface, IWeakReference **out) +{ + struct EventTargetArray *impl = impl_from_IWeakReferenceSource(iface); + + TRACE("(%p, %p)\n", iface, out); + IWeakReference_AddRef((*out = &impl->block->IWeakReference_iface)); + return S_OK; +} + +DEFINE_RTTI_DATA(EventTargetArray_weakref_src, + offsetof(struct EventTargetArray, IWeakReferenceSource_iface), + ".?AVEventTargetArray@Details@Platform@@"); +COM_VTABLE_RTTI_START(IWeakReferenceSource, EventTargetArray_weakref_src) +COM_VTABLE_ENTRY(EventTargetArray_weakref_src_QueryInterface) +COM_VTABLE_ENTRY(EventTargetArray_weakref_src_AddRef) +COM_VTABLE_ENTRY(EventTargetArray_weakref_src_Release) +COM_VTABLE_ENTRY(EventTargetArray_weakref_src_GetWeakReference) +COM_VTABLE_RTTI_END; + +void init_delegate(void *base) +{ + INIT_RTTI(EventTargetArray, base); + INIT_RTTI(EventTargetArray_weakref_src, base); }
-EventRegistrationToken WINAPI EventSourceAdd(IInspectable **source, void *lock, IUnknown *delegate) +static struct EventTargetArray *create_EventTargetArray(ULONG delegates) { - EventRegistrationToken token = {0}; - FIXME("(%p, %p, %p): stub!\n", source, lock, delegate); + struct EventTargetArray *impl; + HRESULT hr; + + impl = AllocateWithWeakRef(offsetof(struct EventTargetArray, block), + offsetof(struct EventTargetArray, delegates[delegates])); + impl->IInspectable_iface.lpVtbl = &EventTargetArray_vtable.vtable; + impl->IWeakReferenceSource_iface.lpVtbl = &EventTargetArray_weakref_src_vtable.vtable; + if (FAILED(hr = CoCreateFreeThreadedMarshaler((IUnknown *)&impl->IInspectable_iface, &impl->marshal))) + { + struct control_block *block = impl->block; + + WriteNoFence(&impl->block->ref_strong, -1); + control_block_ReleaseTarget(block); + IWeakReference_Release(&block->IWeakReference_iface); + __abi_WinRTraiseCOMException(hr); + } + impl->count = delegates; + return impl; +} + +/* According to MSDN's C++/CX documentation, this consists of two locks. + * The exact usage of these locks is still unclear. */ +struct EventLock +{ + /* Protects access to the EventTargetArray object. + * During event dispatch, programs hold a read-lock on this before iterating through the delegate list. + * We also hold this before replacing the old EventTargetArray object in EventSourceAdd/Remove, but I'm + * unsure if native does this as well. */ + SRWLOCK targets_ptr_lock; + /* Protects modification of the delegate list. + * EventSourceAdd/Remove will hold a write-lock on this. */ + SRWLOCK add_remove_lock; +}; +struct EventSourceAdd_cleanup_ctx +{ + IAgileReference *agile; + SRWLOCK *add_remove_lock; +}; + +static CALLBACK void EventSourceAdd_cleanup(BOOL normal, void *param) +{ + struct EventSourceAdd_cleanup_ctx *ctx = param; + + IAgileReference_Release(ctx->agile); + ReleaseSRWLockExclusive(ctx->add_remove_lock); +} + +/* Create a new EventTargetArray object with the newly added delegate and place it in *source, releasing the earlier + * value (if any). Returns the token associated with the added delegate. */ +EventRegistrationToken WINAPI EventSourceAdd(struct EventTargetArray **source, struct EventLock *lock, IUnknown *delegate) +{ + struct EventTargetArray *impl = NULL, *new_impl; + struct EventSourceAdd_cleanup_ctx finally_ctx; + EventRegistrationToken token; + IAgileReference *agile; + ULONG i, old_count = 0; + HRESULT hr; + + TRACE("(%p, %p, %p)\n", source, lock, delegate); + + /* Native throws InvalidArgumentException if delegate is NULL. */ + if (!delegate) + __abi_WinRTraiseInvalidArgumentException(); + + /* Get an agile reference outside the __TRY block to make cleanup simpler. + * FIXME: This uses eager marshaling, which marshals the interface right now. Native seems to use delayed/lazy marshaling, as + * that only increases the refcount on the delegate by 1 (eager marshaling increases it by 2). However, our current + * implementation of lazy marshaling is incomplete, as lazy marshaling needs to occur inside the apartment/context that + * the object originally belongs to, instead of the caller's apartment. */ + if (FAILED((hr = RoGetAgileReference(AGILEREFERENCE_DEFAULT, &IID_IUnknown, delegate, &agile)))) + __abi_WinRTraiseCOMException(hr); + finally_ctx.agile = agile; + + AcquireSRWLockExclusive((finally_ctx.add_remove_lock = &lock->add_remove_lock)); + if ((impl = *source)) + old_count = impl->count; + __TRY + { + new_impl = create_EventTargetArray(old_count + 1); + for (i = 0; i < old_count; i++) + IAgileReference_AddRef((new_impl->delegates[i] = impl->delegates[i])); + IAgileReference_AddRef((new_impl->delegates[old_count] = agile)); /* Additional AddRef for cleanup. */ + /* Using the IAgileReference address as the token saves us from having an additional stable token field for every + * delegate. */ + token.value = (INT64)agile; + /* Finally, update source and release the old list. + * Programs will acquire a read lock on targets_ptr_lock before iterating through the delegate list while + * dispatching events. */ + AcquireSRWLockExclusive(&lock->targets_ptr_lock); + *source = new_impl; + ReleaseSRWLockExclusive(&lock->targets_ptr_lock); + if (impl) + IInspectable_Release(&impl->IInspectable_iface); + } + __FINALLY_CTX(EventSourceAdd_cleanup, &finally_ctx) + return token; }
-void WINAPI EventSourceRemove(IInspectable **source, void *lock, EventRegistrationToken token) +static void CALLBACK EventSourceRemove_cleanup(BOOL normal, void *lock) { - FIXME("(%p, %p, {%I64u}): stub!\n", source, lock, token.value); + ReleaseSRWLockExclusive(lock); }
-IInspectable *WINAPI EventSourceGetTargetArray(IInspectable *source, void *lock) +/* Create a new EventTargetArray without the delegate associated with the provided token and place it in *source, + * releasing the earlier value (if any). Does nothing if *source is NULL or if the token is invalid. */ +void WINAPI EventSourceRemove(struct EventTargetArray **source, struct EventLock *lock, EventRegistrationToken token) { - FIXME("(%p, %p): stub!\n", source, lock); - return NULL; + IAgileReference *delegate = NULL; + struct EventTargetArray *impl; + ULONG i, j; + + TRACE("(%p, %p, {%I64u}): stub!\n", source, lock, token.value); + + if (!(impl = *source)) + return; + + AcquireSRWLockExclusive(&lock->add_remove_lock); + for (i = 0; i < impl->count; i++) + { + if (token.value == (INT64)impl->delegates[i]) + { + delegate = impl->delegates[i]; + break; + } + } + if (!delegate) + { + ReleaseSRWLockExclusive(&lock->add_remove_lock); + return; + } + __TRY + { + /* If the only delegate has been removed, native sets source back to NULL. */ + struct EventTargetArray *new_impl = NULL; + + if (impl->count > 1) + { + new_impl = create_EventTargetArray(impl->count - 1); + for (j = 0; j < i; j++) + IAgileReference_AddRef((new_impl->delegates[j] = impl->delegates[j])); + for (j = i; j < impl->count - 1; j++) + IAgileReference_AddRef((new_impl->delegates[j] = impl->delegates[j + 1])); + } + + AcquireSRWLockExclusive(&lock->targets_ptr_lock); + *source = new_impl; + ReleaseSRWLockExclusive(&lock->targets_ptr_lock); + IInspectable_Release(&impl->IInspectable_iface); + } + __FINALLY_CTX(EventSourceRemove_cleanup, &lock->add_remove_lock); }
-void *WINAPI EventSourceGetTargetArrayEvent(void *source, ULONG idx, const GUID *iid, EventRegistrationToken *token) +/* Used before dispatching an event. By incrementing the refcount on the EventTargetArray, the caller gets a "snapshot" + * of the currently registered delegates it can safetly invoke. */ +struct EventTargetArray *WINAPI EventSourceGetTargetArray(struct EventTargetArray *targets, struct EventLock *lock) { - FIXME("(%p, %lu, %s, %p): stub!\n", source, idx, debugstr_guid(iid), token); - return NULL; + TRACE("(%p, %p)\n", targets, lock); + + /* TODO: The lock pointer received by this function is different from what EventSourceAdd/Remove get, so it's + * probably guarding something else. Figure out what the lock is for. Holding targets_ptr_lock before calling this + * function shows that native holds a read-lock on targets_ptr_lock, so we'll do the same. + * The only thing we do here is increment the refcount, so do it inside the shared lock, even though it's + * unnecessary. */ + AcquireSRWLockShared(&lock->targets_ptr_lock); + if (targets) + IInspectable_AddRef((&targets->IInspectable_iface)); + ReleaseSRWLockShared(&lock->targets_ptr_lock); + return targets; +} + +void *WINAPI EventSourceGetTargetArrayEvent(struct EventTargetArray *targets, ULONG idx, const GUID *iid, EventRegistrationToken *token) +{ + HRESULT hr; + void *out; + + TRACE("(%p, %lu, %s, %p)\n", targets, idx, debugstr_guid(iid), token); + + if (FAILED((hr = IAgileReference_Resolve(targets->delegates[idx], iid, &out)))) + __abi_WinRTraiseCOMException(hr); + token->value = (INT64)targets->delegates[idx]; + return out; }
-ULONG WINAPI EventSourceGetTargetArraySize(void *source) +ULONG WINAPI EventSourceGetTargetArraySize(struct EventTargetArray *targets) { - FIXME("(%p): stub!\n", source); - return 0; + FIXME("(%p)\n", targets); + return targets->count; } diff --git a/dlls/vccorlib140/private.h b/dlls/vccorlib140/private.h index e71abcc56f3..97f6aae5e7e 100644 --- a/dlls/vccorlib140/private.h +++ b/dlls/vccorlib140/private.h @@ -43,12 +43,17 @@ struct control_block
void *__cdecl AllocateExceptionWithWeakRef(ptrdiff_t, size_t); void __cdecl FreeException(void *); +void *__cdecl AllocateWithWeakRef(ptrdiff_t, size_t); + +void __thiscall control_block_ReleaseTarget(struct control_block *);
void init_exception(void *); void WINAPI DECLSPEC_NORETURN __abi_WinRTraiseCOMException(HRESULT); void WINAPI DECLSPEC_NORETURN __abi_WinRTraiseInvalidArgumentException(void); void WINAPI DECLSPEC_NORETURN __abi_WinRTraiseOutOfMemoryException(void);
+void init_delegate(void *base); + #define COM_VTABLE_RTTI_START(iface, type) \ static const struct \ { \ diff --git a/dlls/vccorlib140/tests/vccorlib.c b/dlls/vccorlib140/tests/vccorlib.c index 58c4a9b8e69..77838accedb 100644 --- a/dlls/vccorlib140/tests/vccorlib.c +++ b/dlls/vccorlib140/tests/vccorlib.c @@ -2368,22 +2368,15 @@ static void test_EventSource(void) ok(hr == S_OK, "got hr %#lx.\n", hr);
p_EventSourceInitialize(&event_source); - todo_wine ok(event_source == NULL, "got event_source %p.\n", event_source); + ok(event_source == NULL, "got event_source %p.\n", event_source);
event_source = NULL; delegate1 = delegate_create(FALSE); token1 = p_EventSourceAdd(&event_source, &lock, (IUnknown *)delegate1); - todo_wine ok(token1.value != 0, "got token1.value {%#I64x}\n", token1.value); - todo_wine ok(event_source != NULL, "got event_source %p\n", event_source); + ok(token1.value != 0, "got token1.value {%#I64x}\n", token1.value); + ok(event_source != NULL, "got event_source %p\n", event_source); /* EventSourceAdd takes a reference to the delegate. */ todo_wine test_refcount(delegate1, 2); - if (!event_source) - { - skip("EventSourceAdd failed\n"); - IInspectable_Release(delegate1); - RoUninitialize(); - return; - } delegate2 = delegate_create(FALSE); old = event_source; IInspectable_AddRef(old); @@ -2394,9 +2387,9 @@ static void test_EventSource(void) ok(event_source && event_source != old, "got event_source %p\n", event_source); count = IInspectable_Release(old); ok(count == 0, "got count %lu\n", count); - test_refcount(delegate1, 2); + todo_wine test_refcount(delegate1, 2); count = IInspectable_Release(delegate2); - ok(count == 1, "got count %lu\n", count); + todo_wine ok(count == 1, "got count %lu\n", count);
test_rtti_names(event_source, "class Platform::Details::EventTargetArray", ".?AVEventTargetArray@Details@Platform@@"); @@ -2426,7 +2419,7 @@ static void test_EventSource(void) test_refcount(event_source, 1); test_refcount(old, 1); /* The older object should still have a reference to delegate1. */ - test_refcount(delegate1, 2); + todo_wine test_refcount(delegate1, 2); /* EventSourceUninitialize calls Release, and sets the argument to NULL. */ IInspectable_AddRef((obj = old)); p_EventSourceUninitialize(&obj); @@ -2461,7 +2454,7 @@ static void test_EventSource(void) obj = p_EventSourceGetTargetArray(event_source, &lock); ReleaseSRWLockExclusive(&lock.add_remove_lock); ReleaseSRWLockShared(&lock.targets_ptr_lock); - todo_wine ok(obj == event_source, "got obj %p != %p\n", obj, event_source); + ok(obj == event_source, "got obj %p != %p\n", obj, event_source); count = IInspectable_Release(obj); ok(count == 1, "got count == %lu\n", count); /* Passing NULL should not fail. */ @@ -2470,17 +2463,17 @@ static void test_EventSource(void)
/* Returns the number of delegates stored. */ count = p_EventSourceGetTargetArraySize(event_source); - todo_wine ok(count == 1, "got count %lu\n", count); + ok(count == 1, "got count %lu\n", count);
token1.value = 0; /* Returns a stored delegate and the assoicated token through its index. */ obj = p_EventSourceGetTargetArrayEvent(event_source, 0, &IID_IUnknown, &token1); /* We're in the same apartment/thread, so we should get back the same interface pointer. */ - todo_wine ok(obj == delegate2, "got obj %p != %p\n", obj, delegate2); - todo_wine ok(token1.value == token2.value, "got token1 {%#I64x} != {%#I64x}\n", token1.value, token2.value); + ok(obj == delegate2, "got obj %p != %p\n", obj, delegate2); + ok(token1.value == token2.value, "got token1 {%#I64x} != {%#I64x}\n", token1.value, token2.value); /* EventSourceGetTargetArrayEvent should increase the refcount on the returned delegate. */ count = IInspectable_Release(obj); - ok(count == 1, "got count %lu\n", count); + todo_wine ok(count == 1, "got count %lu\n", count);
IInspectable_AddRef(delegate2); p_EventSourceRemove(&event_source, &lock, token2); @@ -2558,7 +2551,7 @@ static CALLBACK DWORD test_EventSource_marshal_proc(void *params) { ok(delegate == data->non_agile_delegate, "got delegate %p\n", delegate); count = IInspectable_Release(delegate); - ok(count == 1, "got count %lu\n", count); + todo_wine ok(count == 1, "got count %lu\n", count); }
RoUninitialize(); @@ -2592,22 +2585,16 @@ static void test_EventSource_marshaling(void)
data.agile_delegate = delegate_create(TRUE); data.agile_token = p_EventSourceAdd(&data.source, &lock, (IUnknown *)data.agile_delegate); - todo_wine ok(data.source != NULL, "got source %p\n", data.source); - todo_wine ok(data.agile_token.value != 0, "got agile_token {%#I64x}\n", data.agile_token.value); + ok(data.source != NULL, "got source %p\n", data.source); + ok(data.agile_token.value != 0, "got agile_token {%#I64x}\n", data.agile_token.value); count = IInspectable_Release(data.agile_delegate); - todo_wine ok(count == 1, "got count %lu\n", count); - if (!data.source) - { - skip("EventSourceAdd failed\n"); - winetest_pop_context(); - continue; - } + ok(count == 1, "got count %lu\n", count);
data.non_agile_delegate = delegate_create(FALSE); data.non_agile_token = p_EventSourceAdd(&data.source, &lock, (IUnknown *)data.non_agile_delegate); ok(data.non_agile_token.value != 0, "got non_agile_token {%#I64x}\n", data.non_agile_token.value); count = IInspectable_Release(data.non_agile_delegate); - ok(count == 1, "got count %lu\n", count); + todo_wine ok(count == 1, "got count %lu\n", count);
count = p_EventSourceGetTargetArraySize(data.source); ok(count == 2, "got size %lu\n", count); diff --git a/dlls/vccorlib140/vccorlib.c b/dlls/vccorlib140/vccorlib.c index 0a256589fea..0071c1a4591 100644 --- a/dlls/vccorlib140/vccorlib.c +++ b/dlls/vccorlib140/vccorlib.c @@ -895,6 +895,7 @@ BOOL WINAPI DllMain(HINSTANCE inst, DWORD reason, void *reserved) { init_exception(inst); init_platform_type(inst); + init_delegate(inst); } return TRUE; }