-- v4: vccorlib140: Implement delegate 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..c68303a7181 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 = (ULONG_PTR)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 == (ULONG_PTR)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 = (ULONG_PTR)targets->delegates[idx]; + return out; }
-ULONG WINAPI EventSourceGetTargetArraySize(void *source) +ULONG WINAPI EventSourceGetTargetArraySize(struct EventTargetArray *targets) { - FIXME("(%p): stub!\n", source); - return 0; + TRACE("(%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; }
On Thu Nov 13 15:25:04 2025 +0000, Vibhav Pant wrote:
changed this line in [version 4 of the diff](/wine/wine/-/merge_requests/9425/diffs?diff_id=223820&start_sha=adcbc051dc0b0c7eab606964d06d198a524cd600#d640f5d56345fd2f632b6d9638c36fbe320d41d2_411_411)
Fixed, thanks.
On Thu Nov 13 15:25:51 2025 +0000, Piotr Caban wrote:
In future, please try to divide changes into smaller patches (it could have been done easily in this case). It makes it easier to review the changes (it's also useful in case of regression).
Yeah, sorry about that. I could've split this into separate commits for EventSourceAdd, EventSourceDelete, and the EventTargetArray* functions.
On Wed Nov 12 18:43:28 2025 +0000, Piotr Caban wrote:
Is the size of IInspectable matching with the size of Delegate class?
Yes, `Platform::Delegate` inherits from `Platform::Object`, which I think is just `IInspectable`. The default methods set here will simply abort the program if called, as `Delegate` is an abstract class. The user-provided subclass constructor is mean to override all of these methods, so the default entries here are never meant to be called.
This merge request was approved by Piotr Caban.