-- v2: vccorlib140: Implement delegate helper functions. vccorlib140: Add stubs for delegate/EventSource helper functions.
From: Vibhav Pant vibhavp@gmail.com
--- dlls/vccorlib140/Makefile.in | 1 + dlls/vccorlib140/delegate.c | 73 +++++ dlls/vccorlib140/tests/Makefile.in | 2 +- dlls/vccorlib140/tests/vccorlib.c | 469 ++++++++++++++++++++++++++++- dlls/vccorlib140/vccorlib140.spec | 46 +-- 5 files changed, 566 insertions(+), 25 deletions(-) create mode 100644 dlls/vccorlib140/delegate.c
diff --git a/dlls/vccorlib140/Makefile.in b/dlls/vccorlib140/Makefile.in index bad509dbdf5..a35758ede88 100644 --- a/dlls/vccorlib140/Makefile.in +++ b/dlls/vccorlib140/Makefile.in @@ -2,5 +2,6 @@ MODULE = vccorlib140.dll IMPORTS = combase oleaut32
SOURCES = \ + delegate.c \ except.c \ vccorlib.c diff --git a/dlls/vccorlib140/delegate.c b/dlls/vccorlib140/delegate.c new file mode 100644 index 00000000000..73b289ddf3e --- /dev/null +++ b/dlls/vccorlib140/delegate.c @@ -0,0 +1,73 @@ +/* + * Helpers used for WinRT event dispatch. + * + * Copyright 2025 Vibhav Pant + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * 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 + */ + +#define COBJMACROS +#include "eventtoken.h" +#include "inspectable.h" + +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(vccorlib); + +void *__cdecl Delegate_ctor(void *this) +{ + FIXME("(%p): stub!\n", this); + return NULL; +} + +void WINAPI EventSourceInitialize(IInspectable **source) +{ + FIXME("(%p): stub!\n", source); +} + +void WINAPI EventSourceUninitialize(IInspectable **source) +{ + FIXME("(%p): stub!\n", source); +} + +EventRegistrationToken WINAPI EventSourceAdd(IInspectable **source, void *lock, IUnknown *delegate) +{ + EventRegistrationToken token = {0}; + FIXME("(%p, %p, %p): stub!\n", source, lock, delegate); + return token; +} + +void WINAPI EventSourceRemove(IInspectable **source, void *lock, EventRegistrationToken token) +{ + FIXME("(%p, %p, {%I64u}): stub!\n", source, lock, token.value); +} + +IInspectable *WINAPI EventSourceGetTargetArray(IInspectable *source, void *lock) +{ + FIXME("(%p, %p): stub!\n", source, lock); + return NULL; +} + +void *WINAPI EventSourceGetTargetArrayEvent(void *source, ULONG idx, const GUID *iid, EventRegistrationToken *token) +{ + FIXME("(%p, %lu, %s, %p): stub!\n", source, idx, debugstr_guid(iid), token); + return NULL; +} + +ULONG WINAPI EventSourceGetTargetArraySize(void *source) +{ + FIXME("(%p): stub!\n", source); + return 0; +} diff --git a/dlls/vccorlib140/tests/Makefile.in b/dlls/vccorlib140/tests/Makefile.in index 7d19fceb468..79a76cb6927 100644 --- a/dlls/vccorlib140/tests/Makefile.in +++ b/dlls/vccorlib140/tests/Makefile.in @@ -1,5 +1,5 @@ TESTDLL = vccorlib140.dll -IMPORTS = combase ole32 oleaut32 uuid +IMPORTS = combase ole32 oleaut32 user32 uuid
SOURCES = \ vccorlib.c diff --git a/dlls/vccorlib140/tests/vccorlib.c b/dlls/vccorlib140/tests/vccorlib.c index 7d1ac57a3ac..58c4a9b8e69 100644 --- a/dlls/vccorlib140/tests/vccorlib.c +++ b/dlls/vccorlib140/tests/vccorlib.c @@ -29,6 +29,7 @@ #include "objbase.h" #include "weakreference.h" #include "restrictederrorinfo.h" +#include "roapi.h" #define WIDL_using_Windows_Foundation #include "windows.foundation.h" #include "winstring.h" @@ -134,6 +135,12 @@ struct __abi_type_descriptor int type_id; };
+struct EventLock +{ + SRWLOCK targets_ptr_lock; + SRWLOCK add_remove_lock; +}; + static HRESULT (__cdecl *pInitializeData)(int); static void (__cdecl *pUninitializeData)(int); static HRESULT (WINAPI *pGetActivationFactoryByPCWSTR)(const WCHAR *, const GUID *, void **); @@ -180,6 +187,14 @@ static HSTRING (__cdecl *p_uint16_ToString)(const UINT16 *); static HSTRING (__cdecl *p_uint32_ToString)(const UINT32 *); static HSTRING (__cdecl *p_uint64_ToString)(const UINT64 *); static HSTRING (__cdecl *p_uint8_ToString)(const UINT8 *); +static void *(__cdecl *p_Delegate_ctor)(IInspectable *); +static void (WINAPI *p_EventSourceInitialize)(IInspectable **); +static void (WINAPI *p_EventSourceUninitialize)(IInspectable **); +static EventRegistrationToken (WINAPI *p_EventSourceAdd)(IInspectable **, struct EventLock *, IUnknown *); +static void (WINAPI *p_EventSourceRemove)(IInspectable **, struct EventLock *, EventRegistrationToken); +static IInspectable *(WINAPI *p_EventSourceGetTargetArray)(IInspectable *, struct EventLock *); +static IInspectable *(WINAPI *p_EventSourceGetTargetArrayEvent)(IInspectable *, ULONG, const GUID *, EventRegistrationToken *); +static ULONG (WINAPI *p_EventSourceGetTargetArraySize)(IInspectable *);
static void *(__cdecl *p__RTtypeid)(const void *); static const char *(__thiscall *p_type_info_name)(void *); @@ -268,6 +283,19 @@ static BOOL init(void) p_uint32_ToString = (void *)GetProcAddress(hmod, "?ToString@uint32@default@@QAAP$AAVString@Platform@@XZ"); p_uint64_ToString = (void *)GetProcAddress(hmod, "?ToString@uint64@default@@QAAP$AAVString@Platform@@XZ"); p_uint8_ToString = (void *)GetProcAddress(hmod, "?ToString@uint8@default@@QAAP$AAVString@Platform@@XZ"); + p_Delegate_ctor = (void *)GetProcAddress(hmod, "??0Delegate@Platform@@Q$AAA@XZ"); + p_EventSourceInitialize = (void *)GetProcAddress(hmod, "?EventSourceInitialize@Details@Platform@@YAXPAPAX@Z"); + p_EventSourceUninitialize = (void *)GetProcAddress(hmod, "?EventSourceUninitialize@Details@Platform@@YAXPAPAX@Z"); + p_EventSourceAdd = (void *)GetProcAddress(hmod, + "?EventSourceAdd@Details@Platform@@YA?AVEventRegistrationToken@Foundation@Windows@@PAPAXPAUEventLock@12@P$AAVDelegate@2@@Z"); + p_EventSourceRemove = (void *)GetProcAddress(hmod, + "?EventSourceRemove@Details@Platform@@YAXPAPAXPAUEventLock@12@VEventRegistrationToken@Foundation@Windows@@@Z"); + p_EventSourceGetTargetArray = (void *)GetProcAddress(hmod, + "?EventSourceGetTargetArray@Details@Platform@@YAPAXPAXPAUEventLock@12@@Z"); + p_EventSourceGetTargetArrayEvent = (void *)GetProcAddress(hmod, + "?EventSourceGetTargetArrayEvent@Details@Platform@@YAPAXPAXIPBXPA_J@Z"); + p_EventSourceGetTargetArraySize = (void *)GetProcAddress(hmod, + "?EventSourceGetTargetArraySize@Details@Platform@@YAIPAX@Z");
p_type_info_name = (void *)GetProcAddress(msvcrt, "?name@type_info@@QBAPBDXZ"); p_type_info_raw_name = (void *)GetProcAddress(msvcrt, "?raw_name@type_info@@QBAPBDXZ"); @@ -339,6 +367,21 @@ static BOOL init(void) p_uint32_ToString = (void *)GetProcAddress(hmod, "?ToString@uint32@default@@QEAAPE$AAVString@Platform@@XZ"); p_uint64_ToString = (void *)GetProcAddress(hmod, "?ToString@uint64@default@@QEAAPE$AAVString@Platform@@XZ"); p_uint8_ToString = (void *)GetProcAddress(hmod, "?ToString@uint8@default@@QEAAPE$AAVString@Platform@@XZ"); + p_Delegate_ctor = (void *)GetProcAddress(hmod, "??0Delegate@Platform@@QE$AAA@XZ"); + p_EventSourceInitialize = (void *)GetProcAddress(hmod, + "?EventSourceInitialize@Details@Platform@@YAXPEAPEAX@Z"); + p_EventSourceUninitialize = (void *)GetProcAddress(hmod, + "?EventSourceUninitialize@Details@Platform@@YAXPEAPEAX@Z"); + p_EventSourceAdd = (void *)GetProcAddress(hmod, + "?EventSourceAdd@Details@Platform@@YA?AVEventRegistrationToken@Foundation@Windows@@PEAPEAXPEAUEventLock@12@PE$AAVDelegate@2@@Z"); + p_EventSourceRemove = (void *)GetProcAddress(hmod, + "?EventSourceRemove@Details@Platform@@YAXPEAPEAXPEAUEventLock@12@VEventRegistrationToken@Foundation@Windows@@@Z"); + p_EventSourceGetTargetArray = (void *)GetProcAddress(hmod, + "?EventSourceGetTargetArray@Details@Platform@@YAPEAXPEAXPEAUEventLock@12@@Z"); + p_EventSourceGetTargetArrayEvent = (void *)GetProcAddress(hmod, + "?EventSourceGetTargetArrayEvent@Details@Platform@@YAPEAXPEAXIPEBXPEA_J@Z"); + p_EventSourceGetTargetArraySize = (void *)GetProcAddress(hmod, + "?EventSourceGetTargetArraySize@Details@Platform@@YAIPEAX@Z");
p_type_info_name = (void *)GetProcAddress(msvcrt, "?name@type_info@@QEBAPEBDXZ"); p_type_info_raw_name = (void *)GetProcAddress(msvcrt, "?raw_name@type_info@@QEBAPEBDXZ"); @@ -409,6 +452,20 @@ static BOOL init(void) p_uint32_ToString = (void *)GetProcAddress(hmod, "?ToString@uint32@default@@QAAP$AAVString@Platform@@XZ"); p_uint64_ToString = (void *)GetProcAddress(hmod, "?ToString@uint64@default@@QAAP$AAVString@Platform@@XZ"); p_uint8_ToString = (void *)GetProcAddress(hmod, "?ToString@uint8@default@@QAAP$AAVString@Platform@@XZ"); + p_Delegate_ctor = (void *)GetProcAddress(hmod, "??0Delegate@Platform@@Q$AAA@XZ"); + p_EventSourceInitialize = (void *)GetProcAddress(hmod, "?EventSourceInitialize@Details@Platform@@YGXPAPAX@Z"); + p_EventSourceUninitialize = (void *)GetProcAddress(hmod, + "?EventSourceUninitialize@Details@Platform@@YGXPAPAX@Z"); + p_EventSourceAdd = (void *)GetProcAddress(hmod, + "?EventSourceAdd@Details@Platform@@YG?AVEventRegistrationToken@Foundation@Windows@@PAPAXPAUEventLock@12@P$AAVDelegate@2@@Z"); + p_EventSourceRemove = (void *)GetProcAddress(hmod, + "?EventSourceRemove@Details@Platform@@YGXPAPAXPAUEventLock@12@VEventRegistrationToken@Foundation@Windows@@@Z"); + p_EventSourceGetTargetArray = (void *)GetProcAddress(hmod, + "?EventSourceGetTargetArray@Details@Platform@@YGPAXPAXPAUEventLock@12@@Z"); + p_EventSourceGetTargetArrayEvent = (void *)GetProcAddress(hmod, + "?EventSourceGetTargetArrayEvent@Details@Platform@@YGPAXPAXIPBXPA_J@Z"); + p_EventSourceGetTargetArraySize = (void *)GetProcAddress(hmod, + "?EventSourceGetTargetArraySize@Details@Platform@@YGIPAX@Z");
p_type_info_name = (void *)GetProcAddress(msvcrt, "?name@type_info@@QBEPBDXZ"); p_type_info_raw_name = (void *)GetProcAddress(msvcrt, "?raw_name@type_info@@QBEPBDXZ"); @@ -461,6 +518,13 @@ static BOOL init(void) ok(p_uint32_ToString != NULL, "default::uint32::ToString not available.\n"); ok(p_uint64_ToString != NULL, "default::uint64::ToString not available.\n"); ok(p_uint8_ToString != NULL, "default::uint8::ToString not available.\n"); + ok(p_Delegate_ctor != NULL, "Platform::Delegate not available.\n"); + ok(p_EventSourceInitialize != NULL, "Platform::EventSourceInitialize not available.\n"); + ok(p_EventSourceAdd != NULL, "Platform::Details::EventSourceAdd not available.\n"); + ok(p_EventSourceRemove != NULL, "Platform::Details::EventSourceRemove not available.\n"); + ok(p_EventSourceGetTargetArray != NULL, "Platform::Details::EventSourceGetTargetArray not available.\n"); + ok(p_EventSourceGetTargetArrayEvent != NULL, "Platform::Details::EventSourceGetTargetArrayEvent not available.\n"); + ok(p_EventSourceGetTargetArraySize != NULL, "Platform::Details::EventSourceGetTargetArrayEvent not available.\n");
ok(p_type_info_name != NULL, "type_info::name not available\n"); ok(p_type_info_raw_name != NULL, "type_info::raw_name not available\n"); @@ -1469,7 +1533,7 @@ static void test_CreateValue(void) ok(obj == NULL, "got obj %p\n", obj); }
- pUninitializeData(0); + pUninitializeData(1); }
#define CLASS_IS_SIMPLE_TYPE 1 @@ -2172,6 +2236,406 @@ static void test_ToString(void) test_hstring(str, L"{af86e2e0-b12d-4c6a-9c5a-d7aa65101e90}"); }
+#define test_rtti_names(obj, pretty, mangled) test_rtti_names_(__LINE__, obj, pretty, mangled) +static void test_rtti_names_(int line, void *obj, const char *exp_pretty_name, const char *exp_mangled_name) +{ + const char *pretty_name, *mangled_name; + void *type_info; + + type_info = p__RTtypeid(obj); + ok_(__FILE__, line)(type_info != NULL, "got type_info %p\n", type_info); + + pretty_name = (char *)call_func1(p_type_info_name, type_info); + ok(!strcmp(pretty_name, exp_pretty_name), "got pretty_name %s != %s.\n", debugstr_a(pretty_name), + debugstr_a(exp_pretty_name)); + + mangled_name = (char *)call_func1(p_type_info_raw_name, type_info); + ok(!strcmp(mangled_name, exp_mangled_name), "got mangled_name %s != %s.\n", debugstr_a(mangled_name), + debugstr_a(exp_mangled_name)); +} + +static void test_Delegate(void) +{ + IInspectable delegate = {.lpVtbl = NULL}; + + p_Delegate_ctor(&delegate); + todo_wine ok(delegate.lpVtbl != NULL, "got lpVtbl %p.\n", delegate.lpVtbl); + if (!delegate.lpVtbl) + { + skip("Platform::Delegate failed.\n"); + return; + } + test_rtti_names(&delegate, "class Platform::Delegate", ".?AVDelegate@Platform@@"); +} + +struct delegate +{ + IInspectable IInspectable_iface; + bool agile; + LONG ref; +}; + +static inline struct delegate *impl_delegate_from_IInspectable(IInspectable *iface) +{ + return CONTAINING_RECORD(iface, struct delegate, IInspectable_iface); +} + +static HRESULT WINAPI delegate_QueryInterface(IInspectable *iface, const GUID *iid, void **out) +{ + struct delegate *impl = impl_delegate_from_IInspectable(iface); + + if (IsEqualGUID(iid, &IID_IUnknown) || + IsEqualGUID(iid, &IID_IInspectable) || + (impl->agile && IsEqualGUID(iid, &IID_IAgileObject))) + { + IInspectable_AddRef((*out = &impl->IInspectable_iface)); + return S_OK; + } + + *out = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI delegate_AddRef(IInspectable *iface) +{ + struct delegate *impl = impl_delegate_from_IInspectable(iface); + return InterlockedIncrement(&impl->ref); +} + +static ULONG WINAPI delegate_Release(IInspectable *iface) +{ + struct delegate *impl = impl_delegate_from_IInspectable(iface); + ULONG ref = InterlockedDecrement(&impl->ref); + + if (!ref) pFree(impl); + return ref; +} + +static HRESULT WINAPI delegate_GetIids(IInspectable *iface, ULONG *count, GUID **iids) +{ + ok(0, "Unexpected call\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI delegate_GetRuntimeClassName(IInspectable *iface, HSTRING *str) +{ + ok(0, "Unexpected call\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI delegate_GetTrustLevel(IInspectable *iface, TrustLevel *level) +{ + ok(0, "Unexpected call\n"); + return E_NOTIMPL; +} + +static const IInspectableVtbl delegate_vtbl = +{ + /* IUnknown */ + delegate_QueryInterface, + delegate_AddRef, + delegate_Release, + /* IInspectable */ + delegate_GetIids, + delegate_GetRuntimeClassName, + delegate_GetTrustLevel +}; + +static IInspectable *delegate_create(bool agile) +{ + struct delegate *impl; + + impl = pAllocate(sizeof(*impl)); + /* Using the Platform::Delegate constructor doesn't seem to be necessary. */ + impl->IInspectable_iface.lpVtbl = &delegate_vtbl; + impl->agile = agile; + impl->ref = 1; + return &impl->IInspectable_iface; +} + +static void test_EventSource(void) +{ + IInspectable *event_source = (IInspectable *)0xdeadbeef, *obj, *old, *delegate1, *delegate2; + struct EventLock lock = {SRWLOCK_INIT, SRWLOCK_INIT}; + EventRegistrationToken token1 = {0}, token2 = {0}; + HSTRING str; + ULONG count; + HRESULT hr; + IID *iids; + + /* EventSourceTargetAdd will marshal stored delegate, so COM needs to be initialized. */ + hr = RoInitialize(RO_INIT_MULTITHREADED); + 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); + + 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); + /* 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); + /* EventSourceAdd/Remove have CoW semantics and create a new object on modification, releasing the old one. */ + token2 = p_EventSourceAdd(&event_source, &lock, (IUnknown *)delegate2); + ok(event_source != old, "got event_source %p\n", event_source); + ok(token1.value != token2.value, "got token.value {%#I64x}\n", token1.value); + 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); + count = IInspectable_Release(delegate2); + ok(count == 1, "got count %lu\n", count); + + test_rtti_names(event_source, "class Platform::Details::EventTargetArray", + ".?AVEventTargetArray@Details@Platform@@"); + test_refcount(event_source, 1); + check_interface(event_source, &IID_IUnknown); + check_interface(event_source, &IID_IInspectable); + check_interface(event_source, &IID_IAgileObject); + check_interface(event_source, &IID_IWeakReferenceSource); + check_interface(event_source, &IID_IMarshal); + + hr = IInspectable_GetIids(event_source, &count, &iids); + ok(hr == S_OK, "got hr %#lx\n", hr); + ok(count == 2, "got count %lu\n", count); + ok(iids != NULL, "got iids %p\n", iids); + ok(IsEqualGUID(&iids[0], &IID_IInspectable), "got iids[0] %s\n", debugstr_guid(&iids[0])); + ok(IsEqualGUID(&iids[1], &IID_IWeakReferenceSource), "got iids[1] %s\n", debugstr_guid(&iids[1])); + + hr = IInspectable_GetRuntimeClassName(event_source, &str); + ok(hr == S_OK, "got hr %#lx\n", hr); + ok(WindowsIsStringEmpty(str), "got str %s\n", debugstr_hstring(str)); + WindowsDeleteString(str); + + IInspectable_AddRef((old = event_source)); + /* Similarly, EventSourceRemove should create a new object, releasing the old one. */ + p_EventSourceRemove(&event_source, &lock, token1); + ok(event_source != old, "got event_source %p\n", event_source); + test_refcount(event_source, 1); + test_refcount(old, 1); + /* The older object should still have a reference to delegate1. */ + test_refcount(delegate1, 2); + /* EventSourceUninitialize calls Release, and sets the argument to NULL. */ + IInspectable_AddRef((obj = old)); + p_EventSourceUninitialize(&obj); + ok(obj == NULL, "got obj %p\n", obj); + count = IInspectable_Release(old); + ok(count == 0, "got count %lu\n", count); + /* Releasing an EventSource also releases the delegates it holds references to. */ + count = IInspectable_Release(delegate1); + ok(count == 0, "got count %lu\n", count); + /* Unintializing an empty EventSource should not fail. */ + obj = NULL; + p_EventSourceUninitialize(&obj); + + /* Passing invalid token values should do nothing. */ + old = event_source; + p_EventSourceRemove(&event_source, &lock, token1); + ok(event_source == old, "got event_source %p != %p\n", event_source, old); + test_refcount(event_source, 1); + token1.value = 0; + p_EventSourceRemove(&event_source, &lock, token1); + ok(event_source == old, "got event_source %p != %p\n", event_source, old); + /* Calling EventSourceRemove on an empty EventSource should not fail. */ + obj = NULL; + p_EventSourceRemove(&obj, &lock, token1); + ok(obj == NULL, "got obj %p\n", obj); + + /* Verifies targets_ptr_lock is not acquired exclusively. */ + AcquireSRWLockShared(&lock.targets_ptr_lock); + /* Verifies add_remove_lock is not acquired. */ + AcquireSRWLockExclusive(&lock.add_remove_lock); + /* All this seems to do is increase the reference count. */ + 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); + count = IInspectable_Release(obj); + ok(count == 1, "got count == %lu\n", count); + /* Passing NULL should not fail. */ + obj = p_EventSourceGetTargetArray(NULL, &lock); + ok(obj == NULL, "got obj %p\n", obj); + + /* Returns the number of delegates stored. */ + count = p_EventSourceGetTargetArraySize(event_source); + todo_wine 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); + /* EventSourceGetTargetArrayEvent should increase the refcount on the returned delegate. */ + count = IInspectable_Release(obj); + ok(count == 1, "got count %lu\n", count); + + IInspectable_AddRef(delegate2); + p_EventSourceRemove(&event_source, &lock, token2); + ok(event_source == NULL, "got event_source %p\n", event_source); + /* Ensure no delegate references have leaked. */ + count = IInspectable_Release(delegate2); + ok(count == 0, "got count %lu\n", count); + + RoUninitialize(); +} + +static void flush_events(void) +{ + int diff = 200; + DWORD time; + MSG msg; + + time = GetTickCount() + diff; + while (diff > 0) + { + if (MsgWaitForMultipleObjects(0, NULL, FALSE, 100, QS_ALLINPUT) == WAIT_TIMEOUT) + break; + while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) + DispatchMessageA(&msg); + diff = time - GetTickCount(); + } +} + +struct test_EventSource_marshal_data +{ + RO_INIT_TYPE from_apt; + RO_INIT_TYPE to_apt; + IInspectable *source; + EventRegistrationToken agile_token; + EventRegistrationToken non_agile_token; + void *agile_delegate; + void *non_agile_delegate; +}; + +static CALLBACK DWORD test_EventSource_marshal_proc(void *params) +{ + struct test_EventSource_marshal_data *data = params; + EventRegistrationToken token = {0}; + ULONG count; + IInspectable *delegate; + HRESULT hr; + + winetest_push_context("from_apt=%d: to_apt=%d", data->from_apt, data->to_apt); + hr = RoInitialize(data->to_apt); + ok(hr == S_OK, "got hr %#lx.\n", hr); + + /* Get the agile delegate. */ + delegate = p_EventSourceGetTargetArrayEvent(data->source, 0, &IID_IUnknown, &token); + ok(token.value == data->agile_token.value, "got token {%#I64x} != {%#I64x}\n", token.value, + data->agile_token.value); + /* Because the delegate implemented IAgileObject, we should get the same pointer, regardless of the apartment it + * belongs to. */ + ok(delegate == data->agile_delegate, "got delegate %p != %p\n", delegate, data->agile_delegate); + count = IInspectable_Release(delegate); + ok(count == 1, "got count %lu\n", count); + + /* Get the non-agile delegate. */ + token.value = 0; + delegate = p_EventSourceGetTargetArrayEvent(data->source, 1, &IID_IUnknown, &token); + ok(token.value == data->non_agile_token.value, "got token {%#I64x} != {%#I64x}\n", token.value, + data->agile_token.value); + /* If the object belongs to a STA or a different apartment type, we should get back a marshaled pointer. */ + if (data->from_apt == RO_INIT_SINGLETHREADED || data->from_apt != data->to_apt) + { + ok(delegate != data->non_agile_delegate, "got delegate %p\n", delegate); + count = IInspectable_Release(delegate); + ok(count == 0, "got count %lu\n", count); + } + else /* Otherwise, we get back the same pointer. */ + { + ok(delegate == data->non_agile_delegate, "got delegate %p\n", delegate); + count = IInspectable_Release(delegate); + ok(count == 1, "got count %lu\n", count); + } + + RoUninitialize(); + winetest_pop_context(); + return 0; +} + +static void test_EventSource_marshaling(void) +{ + RO_INIT_TYPE from_apt, to_apt; + + for (from_apt = RO_INIT_SINGLETHREADED; from_apt <= RO_INIT_MULTITHREADED; from_apt++) + { + HRESULT hr; + + winetest_push_context("from_apt=%d", from_apt); + hr = RoInitialize(from_apt); + ok(hr == S_OK, "got hr %#lx\n", hr); + + for (to_apt = RO_INIT_SINGLETHREADED; to_apt <= RO_INIT_MULTITHREADED; to_apt++) + { + struct EventLock lock = {SRWLOCK_INIT, SRWLOCK_INIT}; + struct test_EventSource_marshal_data data = {0}; + DWORD count, ret; + HANDLE thread; + + data.from_apt = from_apt; + data.to_apt = to_apt; + winetest_push_context("to_apt=%d", to_apt); + p_EventSourceInitialize(&data.source); + + 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); + 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; + } + + 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); + + count = p_EventSourceGetTargetArraySize(data.source); + ok(count == 2, "got size %lu\n", count); + + thread = CreateThread(NULL, 0, test_EventSource_marshal_proc, &data, 0, NULL); + ok(thread != NULL, "CreateThread failed: %lu\n", GetLastError()); + if (from_apt == RO_INIT_SINGLETHREADED) + flush_events(); + ret = WaitForSingleObject(thread, INFINITE); + ok(!ret, "WaitForSingleObject returned %lu\n", ret); + CloseHandle(thread); + + IInspectable_AddRef(data.agile_delegate); + IInspectable_AddRef(data.non_agile_delegate); + p_EventSourceUninitialize(&data.source); + /* Ensure no references have leaked. */ + count = IInspectable_Release(data.agile_delegate); + ok(count == 0, "got count %lu\n", count); + count = IInspectable_Release(data.non_agile_delegate); + ok(count == 0, "got count %lu\n", count); + winetest_pop_context(); + } + + RoUninitialize(); + winetest_pop_context(); + } +} + START_TEST(vccorlib) { if(!init()) @@ -2189,4 +2653,7 @@ START_TEST(vccorlib) test_GetWeakReference(); test___abi_ObjectToString(); test_ToString(); + test_Delegate(); + test_EventSource(); + test_EventSource_marshaling(); } diff --git a/dlls/vccorlib140/vccorlib140.spec b/dlls/vccorlib140/vccorlib140.spec index 6b1632e2a3c..2aab6f60e88 100644 --- a/dlls/vccorlib140/vccorlib140.spec +++ b/dlls/vccorlib140/vccorlib140.spec @@ -40,29 +40,29 @@ @ stub -arch=win64 ?Equals@uint64@default@@QEAA_NPE$AAVObject@Platform@@@Z @ stub -arch=win32 ?Equals@uint8@default@@QAA_NP$AAVObject@Platform@@@Z @ stub -arch=win64 ?Equals@uint8@default@@QEAA_NPE$AAVObject@Platform@@@Z -@ stub -arch=i386 ?EventSourceAdd@Details@Platform@@YG?AVEventRegistrationToken@Foundation@Windows@@PAPAXPAUEventLock@12@P$AAVDelegate@2@@Z -@ stub -arch=arm ?EventSourceAdd@Details@Platform@@YA?AVEventRegistrationToken@Foundation@Windows@@PAPAXPAUEventLock@12@P$AAVDelegate@2@@Z -@ stub -arch=win64 ?EventSourceAdd@Details@Platform@@YA?AVEventRegistrationToken@Foundation@Windows@@PEAPEAXPEAUEventLock@12@PE$AAVDelegate@2@@Z -@ stub -arch=i386 ?EventSourceGetTargetArray@Details@Platform@@YGPAXPAXPAUEventLock@12@@Z -@ stub -arch=arm ?EventSourceGetTargetArray@Details@Platform@@YAPAXPAXPAUEventLock@12@@Z -@ stub -arch=win64 ?EventSourceGetTargetArray@Details@Platform@@YAPEAXPEAXPEAUEventLock@12@@Z +@ stdcall -arch=i386 ?EventSourceAdd@Details@Platform@@YG?AVEventRegistrationToken@Foundation@Windows@@PAPAXPAUEventLock@12@P$AAVDelegate@2@@Z(ptr ptr ptr) EventSourceAdd +@ stdcall -arch=arm ?EventSourceAdd@Details@Platform@@YA?AVEventRegistrationToken@Foundation@Windows@@PAPAXPAUEventLock@12@P$AAVDelegate@2@@Z(ptr ptr ptr) EventSourceAdd +@ stdcall -arch=win64 ?EventSourceAdd@Details@Platform@@YA?AVEventRegistrationToken@Foundation@Windows@@PEAPEAXPEAUEventLock@12@PE$AAVDelegate@2@@Z(ptr ptr ptr) EventSourceAdd +@ stdcall -arch=i386 ?EventSourceGetTargetArray@Details@Platform@@YGPAXPAXPAUEventLock@12@@Z(ptr ptr) EventSourceGetTargetArray +@ stdcall -arch=arm ?EventSourceGetTargetArray@Details@Platform@@YAPAXPAXPAUEventLock@12@@Z(ptr ptr) EventSourceGetTargetArray +@ stdcall -arch=win64 ?EventSourceGetTargetArray@Details@Platform@@YAPEAXPEAXPEAUEventLock@12@@Z(ptr ptr) EventSourceGetTargetArray @ cdecl -arch=win32 ??0ClassNotRegisteredException@Platform@@Q$AAA@P$AAVString@1@@Z(ptr ptr) ClassNotRegisteredException_hstring_ctor @ cdecl -arch=win64 ??0ClassNotRegisteredException@Platform@@QE$AAA@PE$AAVString@1@@Z(ptr ptr) ClassNotRegisteredException_hstring_ctor -@ stub -arch=i386 ?EventSourceGetTargetArrayEvent@Details@Platform@@YGPAXPAXIPBXPA_J@Z -@ stub -arch=arm ?EventSourceGetTargetArrayEvent@Details@Platform@@YAPAXPAXIPBXPA_J@Z -@ stub -arch=win64 ?EventSourceGetTargetArrayEvent@Details@Platform@@YAPEAXPEAXIPEBXPEA_J@Z -@ stub -arch=i386 ?EventSourceGetTargetArraySize@Details@Platform@@YGIPAX@Z -@ stub -arch=arm ?EventSourceGetTargetArraySize@Details@Platform@@YAIPAX@Z -@ stub -arch=win64 ?EventSourceGetTargetArraySize@Details@Platform@@YAIPEAX@Z -@ stub -arch=i386 ?EventSourceInitialize@Details@Platform@@YGXPAPAX@Z -@ stub -arch=arm ?EventSourceInitialize@Details@Platform@@YAXPAPAX@Z -@ stub -arch=win64 ?EventSourceInitialize@Details@Platform@@YAXPEAPEAX@Z -@ stub -arch=i386 ?EventSourceRemove@Details@Platform@@YGXPAPAXPAUEventLock@12@VEventRegistrationToken@Foundation@Windows@@@Z -@ stub -arch=arm ?EventSourceRemove@Details@Platform@@YAXPAPAXPAUEventLock@12@VEventRegistrationToken@Foundation@Windows@@@Z -@ stub -arch=win64 ?EventSourceRemove@Details@Platform@@YAXPEAPEAXPEAUEventLock@12@VEventRegistrationToken@Foundation@Windows@@@Z -@ stub -arch=i386 ?EventSourceUninitialize@Details@Platform@@YGXPAPAX@Z -@ stub -arch=arm ?EventSourceUninitialize@Details@Platform@@YAXPAPAX@Z -@ stub -arch=win64 ?EventSourceUninitialize@Details@Platform@@YAXPEAPEAX@Z +@ stdcall -arch=i386 ?EventSourceGetTargetArrayEvent@Details@Platform@@YGPAXPAXIPBXPA_J@Z(ptr long ptr ptr) EventSourceGetTargetArrayEvent +@ stdcall -arch=arm ?EventSourceGetTargetArrayEvent@Details@Platform@@YAPAXPAXIPBXPA_J@Z(ptr long ptr ptr) EventSourceGetTargetArrayEvent +@ stdcall -arch=win64 ?EventSourceGetTargetArrayEvent@Details@Platform@@YAPEAXPEAXIPEBXPEA_J@Z(ptr long ptr ptr) EventSourceGetTargetArrayEvent +@ stdcall -arch=i386 ?EventSourceGetTargetArraySize@Details@Platform@@YGIPAX@Z(ptr) EventSourceGetTargetArraySize +@ stdcall -arch=arm ?EventSourceGetTargetArraySize@Details@Platform@@YAIPAX@Z(ptr) EventSourceGetTargetArraySize +@ stdcall -arch=win64 ?EventSourceGetTargetArraySize@Details@Platform@@YAIPEAX@Z(ptr) EventSourceGetTargetArraySize +@ stdcall -arch=i386 ?EventSourceInitialize@Details@Platform@@YGXPAPAX@Z(ptr) EventSourceInitialize +@ stdcall -arch=arm ?EventSourceInitialize@Details@Platform@@YAXPAPAX@Z(ptr) EventSourceInitialize +@ stdcall -arch=win64 ?EventSourceInitialize@Details@Platform@@YAXPEAPEAX@Z(ptr) EventSourceInitialize +@ stdcall -arch=i386 ?EventSourceRemove@Details@Platform@@YGXPAPAXPAUEventLock@12@VEventRegistrationToken@Foundation@Windows@@@Z(ptr ptr int64) EventSourceRemove +@ stdcall -arch=arm ?EventSourceRemove@Details@Platform@@YAXPAPAXPAUEventLock@12@VEventRegistrationToken@Foundation@Windows@@@Z(ptr ptr int64) EventSourceRemove +@ stdcall -arch=win64 ?EventSourceRemove@Details@Platform@@YAXPEAPEAXPEAUEventLock@12@VEventRegistrationToken@Foundation@Windows@@@Z(ptr ptr int64) EventSourceRemove +@ stdcall -arch=i386 ?EventSourceUninitialize@Details@Platform@@YGXPAPAX@Z(ptr) EventSourceUninitialize +@ stdcall -arch=arm ?EventSourceUninitialize@Details@Platform@@YAXPAPAX@Z(ptr) EventSourceUninitialize +@ stdcall -arch=win64 ?EventSourceUninitialize@Details@Platform@@YAXPEAPEAX@Z(ptr) EventSourceUninitialize @ stub -arch=i386 ?FlushFactoryCache@@YGXXZ @ stub -arch=arm ?FlushFactoryCache@@YAXXZ @ stub -arch=win64 ?FlushFactoryCache@@YAXXZ @@ -98,8 +98,8 @@ @ stub -arch=win64 ?GetHashCode@Object@Platform@@QE$AAAHXZ @ stub -arch=win32 ?GetHashCode@OnePhaseConstructedAttribute@CompilerServices@Runtime@Platform@@Q$AAAHXZ @ stub -arch=win64 ?GetHashCode@OnePhaseConstructedAttribute@CompilerServices@Runtime@Platform@@QE$AAAHXZ -@ stub -arch=win32 ??0Delegate@Platform@@Q$AAA@XZ -@ stub -arch=win64 ??0Delegate@Platform@@QE$AAA@XZ +@ cdecl -arch=win32 ??0Delegate@Platform@@Q$AAA@XZ(ptr) Delegate_ctor +@ cdecl -arch=win64 ??0Delegate@Platform@@QE$AAA@XZ(ptr) Delegate_ctor @ stub -arch=win32 ?GetHashCode@STAThreadAttribute@Platform@@Q$AAAHXZ @ stub -arch=win64 ?GetHashCode@STAThreadAttribute@Platform@@QE$AAAHXZ @ stub -arch=win32 ?GetHashCode@Type@Platform@@U$AAAHXZ
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; }