wine/dlls/combase/tests/roapi.c
2025-08-07 22:18:28 +09:00

599 lines
21 KiB
C

/*
* Copyright (c) 2018 Alistair Leslie-Hughes
*
* 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 <stdarg.h>
#include "windef.h"
#include "winbase.h"
#include "winerror.h"
#include "winstring.h"
#include "winternl.h"
#include "initguid.h"
#include "roapi.h"
#include "roerrorapi.h"
#include "wine/test.h"
#define EXPECT_REF(obj,ref) _expect_ref((IUnknown*)obj, ref, __LINE__)
static void _expect_ref(IUnknown* obj, ULONG ref, int line)
{
ULONG rc;
IUnknown_AddRef(obj);
rc = IUnknown_Release(obj);
ok_(__FILE__,line)(rc == ref, "expected refcount %ld, got %ld\n", ref, rc);
}
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();
}
}
static void load_resource(const WCHAR *filename)
{
DWORD written;
HANDLE file;
HRSRC res;
void *ptr;
file = CreateFileW(filename, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, 0);
ok(file != INVALID_HANDLE_VALUE, "failed to create %s, error %lu\n", debugstr_w(filename), GetLastError());
res = FindResourceW(NULL, filename, L"TESTDLL");
ok(res != 0, "couldn't find resource\n");
ptr = LockResource(LoadResource(GetModuleHandleW(NULL), res));
WriteFile(file, ptr, SizeofResource(GetModuleHandleW(NULL), res), &written, NULL);
ok(written == SizeofResource(GetModuleHandleW(NULL), res), "couldn't write resource\n");
CloseHandle(file);
}
static void test_ActivationFactories(void)
{
HRESULT hr;
HSTRING str, str2;
IActivationFactory *factory = NULL;
IInspectable *inspect = NULL;
ULONG ref;
hr = WindowsCreateString(L"Windows.Data.Xml.Dom.XmlDocument",
ARRAY_SIZE(L"Windows.Data.Xml.Dom.XmlDocument") - 1, &str);
ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
hr = WindowsCreateString(L"Does.Not.Exist", ARRAY_SIZE(L"Does.Not.Exist") - 1, &str2);
ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
hr = RoInitialize(RO_INIT_MULTITHREADED);
ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
hr = RoGetActivationFactory(str2, &IID_IActivationFactory, (void **)&factory);
ok(hr == REGDB_E_CLASSNOTREG, "Unexpected hr %#lx.\n", hr);
hr = RoGetActivationFactory(str, &IID_IActivationFactory, (void **)&factory);
todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
if(factory)
IActivationFactory_Release(factory);
hr = RoActivateInstance(str2, &inspect);
ok(hr == REGDB_E_CLASSNOTREG, "Unexpected hr %#lx.\n", hr);
hr = RoActivateInstance(str, &inspect);
todo_wine ok(hr == S_OK, "UNexpected hr %#lx.\n", hr);
if(inspect)
IInspectable_Release(inspect);
WindowsDeleteString(str2);
WindowsDeleteString(str);
hr = WindowsCreateString(L"Wine.Test.Missing", ARRAY_SIZE(L"Wine.Test.Missing") - 1, &str);
ok(hr == S_OK, "WindowsCreateString returned %#lx.\n", hr);
hr = RoGetActivationFactory(str, &IID_IActivationFactory, (void **)&factory);
ok(hr == REGDB_E_CLASSNOTREG, "RoGetActivationFactory returned %#lx.\n", hr);
ok(factory == NULL, "got factory %p.\n", factory);
WindowsDeleteString(str);
hr = WindowsCreateString(L"Wine.Test.Class", ARRAY_SIZE(L"Wine.Test.Class") - 1, &str);
ok(hr == S_OK, "WindowsCreateString returned %#lx.\n", hr);
hr = RoGetActivationFactory(str, &IID_IActivationFactory, (void **)&factory);
todo_wine
ok(hr == E_NOTIMPL || broken(hr == REGDB_E_CLASSNOTREG) /* <= w1064v1809 */,
"RoGetActivationFactory returned %#lx.\n", hr);
todo_wine
ok(factory == NULL, "got factory %p.\n", factory);
if (factory) IActivationFactory_Release(factory);
WindowsDeleteString(str);
hr = WindowsCreateString(L"Wine.Test.Trusted", ARRAY_SIZE(L"Wine.Test.Trusted") - 1, &str);
ok(hr == S_OK, "WindowsCreateString returned %#lx.\n", hr);
hr = RoGetActivationFactory(str, &IID_IActivationFactory, (void **)&factory);
ok(hr == S_OK || broken(hr == REGDB_E_CLASSNOTREG) /* <= w1064v1809 */,
"RoGetActivationFactory returned %#lx.\n", hr);
if (hr == REGDB_E_CLASSNOTREG)
ok(!factory, "got factory %p.\n", factory);
else
ok(!!factory, "got factory %p.\n", factory);
if (!factory) ref = 0;
else ref = IActivationFactory_Release(factory);
ok(ref == 0, "Release returned %lu\n", ref);
WindowsDeleteString(str);
RoUninitialize();
}
static APTTYPE check_thread_apttype;
static APTTYPEQUALIFIER check_thread_aptqualifier;
static HRESULT check_thread_hr;
static DWORD WINAPI check_apartment_thread(void *dummy)
{
check_thread_apttype = 0xdeadbeef;
check_thread_aptqualifier = 0xdeadbeef;
check_thread_hr = CoGetApartmentType(&check_thread_apttype, &check_thread_aptqualifier);
return 0;
}
#define check_thread_apartment(a) check_thread_apartment_(__LINE__, FALSE, a)
#define check_thread_apartment_broken(a) check_thread_apartment_(__LINE__, TRUE, a)
static void check_thread_apartment_(unsigned int line, BOOL broken_fail, HRESULT expected_hr_thread)
{
HANDLE thread;
check_thread_hr = 0xdeadbeef;
thread = CreateThread(NULL, 0, check_apartment_thread, NULL, 0, NULL);
WaitForSingleObject(thread, INFINITE);
CloseHandle(thread);
ok_(__FILE__, line)(check_thread_hr == expected_hr_thread
|| broken(broken_fail && expected_hr_thread == S_OK && check_thread_hr == CO_E_NOTINITIALIZED),
"got %#lx, expected %#lx.\n", check_thread_hr, expected_hr_thread);
if (SUCCEEDED(check_thread_hr))
{
ok_(__FILE__, line)(check_thread_apttype == APTTYPE_MTA, "got %d.\n", check_thread_apttype);
ok_(__FILE__, line)(check_thread_aptqualifier == APTTYPEQUALIFIER_IMPLICIT_MTA, "got %d.\n", check_thread_aptqualifier);
}
}
static HANDLE mta_init_thread_init_done_event, mta_init_thread_done_event;
static DWORD WINAPI mta_init_thread(void *dummy)
{
HRESULT hr;
hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
ok(hr == S_OK, "got %#lx.\n", hr);
SetEvent(mta_init_thread_init_done_event);
WaitForSingleObject(mta_init_thread_done_event, INFINITE);
CoUninitialize();
return 0;
}
static DWORD WINAPI mta_init_implicit_thread(void *dummy)
{
IActivationFactory *factory;
HSTRING str;
HRESULT hr;
hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
ok(hr == S_OK, "got %#lx.\n", hr);
hr = WindowsCreateString(L"Does.Not.Exist", ARRAY_SIZE(L"Does.Not.Exist") - 1, &str);
ok(hr == S_OK, "got %#lx.\n", hr);
hr = RoGetActivationFactory(str, &IID_IActivationFactory, (void **)&factory);
ok(hr == REGDB_E_CLASSNOTREG, "got %#lx.\n", hr);
WindowsDeleteString(str);
SetEvent(mta_init_thread_init_done_event);
WaitForSingleObject(mta_init_thread_done_event, INFINITE);
/* No CoUninitialize(), testing cleanup on thread exit. */
return 0;
}
enum oletlsflags
{
OLETLS_UUIDINITIALIZED = 0x2,
OLETLS_DISABLE_OLE1DDE = 0x40,
OLETLS_APARTMENTTHREADED = 0x80,
OLETLS_MULTITHREADED = 0x100,
};
struct oletlsdata
{
void *threadbase;
void *smallocator;
DWORD id;
DWORD flags;
};
static DWORD get_oletlsflags(void)
{
struct oletlsdata *data = NtCurrentTeb()->ReservedForOle;
return data ? data->flags : 0;
}
static void test_implicit_mta(void)
{
static const struct
{
BOOL ro_init;
BOOL mta;
}
tests[] =
{
{ FALSE, FALSE },
{ FALSE, TRUE },
{ TRUE, FALSE },
{ TRUE, TRUE },
};
APTTYPEQUALIFIER aptqualifier;
IActivationFactory *factory;
APTTYPE apttype;
unsigned int i;
HANDLE thread;
HSTRING str;
HRESULT hr;
hr = WindowsCreateString(L"Does.Not.Exist", ARRAY_SIZE(L"Does.Not.Exist") - 1, &str);
ok(hr == S_OK, "got %#lx.\n", hr);
/* RoGetActivationFactory doesn't implicitly initialize COM. */
hr = RoGetActivationFactory(str, &IID_IActivationFactory, (void **)&factory);
ok(hr == CO_E_NOTINITIALIZED, "got %#lx.\n", hr);
check_thread_apartment(CO_E_NOTINITIALIZED);
/* RoGetActivationFactory initializes implicit MTA. */
for (i = 0; i < ARRAY_SIZE(tests); ++i)
{
winetest_push_context("test %u", i);
if (tests[i].ro_init)
{
DWORD flags = tests[i].mta ? OLETLS_MULTITHREADED : OLETLS_APARTMENTTHREADED;
hr = RoInitialize(tests[i].mta ? RO_INIT_MULTITHREADED : RO_INIT_SINGLETHREADED);
flags |= OLETLS_DISABLE_OLE1DDE;
ok((get_oletlsflags() & flags) == flags, "get_oletlsflags() = %lx\n", get_oletlsflags());
}
else
hr = CoInitializeEx(NULL, tests[i].mta ? COINIT_MULTITHREADED : COINIT_APARTMENTTHREADED);
ok(hr == S_OK, "got %#lx.\n", hr);
check_thread_apartment(tests[i].mta ? S_OK : CO_E_NOTINITIALIZED);
hr = RoGetActivationFactory(str, &IID_IActivationFactory, (void **)&factory);
ok(hr == REGDB_E_CLASSNOTREG, "got %#lx.\n", hr);
check_thread_apartment_broken(S_OK); /* Broken on Win8. */
hr = RoGetActivationFactory(str, &IID_IActivationFactory, (void **)&factory);
ok(hr == REGDB_E_CLASSNOTREG, "got %#lx.\n", hr);
check_thread_apartment_broken(S_OK); /* Broken on Win8. */
if (tests[i].ro_init)
RoUninitialize();
else
CoUninitialize();
check_thread_apartment(CO_E_NOTINITIALIZED);
winetest_pop_context();
}
mta_init_thread_init_done_event = CreateEventW(NULL, FALSE, FALSE, NULL);
mta_init_thread_done_event = CreateEventW(NULL, FALSE, FALSE, NULL);
/* RoGetActivationFactory references implicit MTA in a current thread
* even if implicit MTA was already initialized: check with STA init
* after RoGetActivationFactory(). */
thread = CreateThread(NULL, 0, mta_init_thread, NULL, 0, NULL);
ok(!!thread, "failed.\n");
WaitForSingleObject(mta_init_thread_init_done_event, INFINITE);
check_thread_apartment(S_OK);
hr = RoGetActivationFactory(str, &IID_IActivationFactory, (void **)&factory);
ok(hr == REGDB_E_CLASSNOTREG, "got %#lx.\n", hr);
check_thread_apartment(S_OK);
hr = CoGetApartmentType(&apttype, &aptqualifier);
ok(hr == S_OK, "got %#lx.\n", hr);
ok(apttype == APTTYPE_MTA, "got %d.\n", apttype);
ok(aptqualifier == APTTYPEQUALIFIER_IMPLICIT_MTA, "got %d.\n", aptqualifier);
hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
ok(hr == S_OK, "got %#lx.\n", hr);
hr = CoGetApartmentType(&apttype, &aptqualifier);
ok(hr == S_OK, "got %#lx.\n", hr);
ok(apttype == APTTYPE_MAINSTA, "got %d.\n", apttype);
ok(aptqualifier == APTTYPEQUALIFIER_NONE, "got %d.\n", aptqualifier);
SetEvent(mta_init_thread_done_event);
WaitForSingleObject(thread, INFINITE);
CloseHandle(thread);
check_thread_apartment_broken(S_OK); /* Broken on Win8. */
CoUninitialize();
check_thread_apartment(CO_E_NOTINITIALIZED);
/* RoGetActivationFactory references implicit MTA in a current thread
* even if implicit MTA was already initialized: check with STA init
* before RoGetActivationFactory(). */
thread = CreateThread(NULL, 0, mta_init_thread, NULL, 0, NULL);
ok(!!thread, "failed.\n");
WaitForSingleObject(mta_init_thread_init_done_event, INFINITE);
check_thread_apartment(S_OK);
hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
ok(hr == S_OK, "got %#lx.\n", hr);
hr = RoGetActivationFactory(str, &IID_IActivationFactory, (void **)&factory);
ok(hr == REGDB_E_CLASSNOTREG, "got %#lx.\n", hr);
check_thread_apartment(S_OK);
SetEvent(mta_init_thread_done_event);
WaitForSingleObject(thread, INFINITE);
CloseHandle(thread);
check_thread_apartment_broken(S_OK); /* Broken on Win8. */
CoUninitialize();
check_thread_apartment(CO_E_NOTINITIALIZED);
/* Test implicit MTA apartment thread exit. */
thread = CreateThread(NULL, 0, mta_init_implicit_thread, NULL, 0, NULL);
ok(!!thread, "failed.\n");
WaitForSingleObject(mta_init_thread_init_done_event, INFINITE);
check_thread_apartment_broken(S_OK); /* Broken on Win8. */
SetEvent(mta_init_thread_done_event);
WaitForSingleObject(thread, INFINITE);
CloseHandle(thread);
check_thread_apartment(CO_E_NOTINITIALIZED);
CloseHandle(mta_init_thread_init_done_event);
CloseHandle(mta_init_thread_done_event);
WindowsDeleteString(str);
}
struct unk_impl
{
IUnknown IUnknown_iface;
LONG ref;
};
static inline struct unk_impl *impl_from_IUnknown(IUnknown *iface)
{
return CONTAINING_RECORD(iface, struct unk_impl, IUnknown_iface);
}
static HRESULT WINAPI unk_QueryInterface(IUnknown *iface, REFIID riid, void **ppv)
{
if (IsEqualGUID(riid, &IID_IUnknown))
{
*ppv = iface;
IUnknown_AddRef(iface);
return S_OK;
}
return E_NOINTERFACE;
}
static HRESULT WINAPI unk_no_marshal_QueryInterface(IUnknown *iface, REFIID riid, void **ppv)
{
if (IsEqualGUID(riid, &IID_IUnknown)
|| IsEqualGUID(riid, &IID_INoMarshal))
{
*ppv = iface;
IUnknown_AddRef(iface);
return S_OK;
}
return E_NOINTERFACE;
}
static ULONG WINAPI unk_AddRef(IUnknown *iface)
{
struct unk_impl *impl = impl_from_IUnknown(iface);
return InterlockedIncrement(&impl->ref);
}
static ULONG WINAPI unk_Release(IUnknown *iface)
{
struct unk_impl *impl = impl_from_IUnknown(iface);
return InterlockedDecrement(&impl->ref);
}
static const IUnknownVtbl unk_vtbl =
{
unk_QueryInterface,
unk_AddRef,
unk_Release
};
static const IUnknownVtbl unk_no_marshal_vtbl =
{
unk_no_marshal_QueryInterface,
unk_AddRef,
unk_Release
};
struct test_RoGetAgileReference_thread_param
{
enum AgileReferenceOptions option;
RO_INIT_TYPE from_type;
RO_INIT_TYPE to_type;
IAgileReference *agile_reference;
IUnknown *unk_obj;
};
static DWORD CALLBACK test_RoGetAgileReference_thread_proc(void *arg)
{
struct test_RoGetAgileReference_thread_param *param = (struct test_RoGetAgileReference_thread_param *)arg;
IUnknown *unknown;
HRESULT hr;
winetest_push_context("%d %d %d", param->option, param->from_type, param->to_type);
RoInitialize(param->to_type);
unknown = NULL;
hr = IAgileReference_Resolve(param->agile_reference, &IID_IUnknown, (void **)&unknown);
ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr);
ok(!!unknown, "Expected pointer not NULL.\n");
if (param->from_type == RO_INIT_MULTITHREADED && param->to_type == RO_INIT_MULTITHREADED)
{
ok(unknown == param->unk_obj, "Expected the same object.\n");
todo_wine_if(param->option == AGILEREFERENCE_DEFAULT)
EXPECT_REF(param->unk_obj, param->option == AGILEREFERENCE_DEFAULT ? 5 : 6);
}
else
{
ok(unknown != param->unk_obj, "Expected a different object pointer.\n");
todo_wine_if(param->option == AGILEREFERENCE_DEFAULT)
EXPECT_REF(param->unk_obj, param->option == AGILEREFERENCE_DEFAULT ? 4 : 5);
EXPECT_REF(unknown, 1);
}
IUnknown_Release(unknown);
RoUninitialize();
winetest_pop_context();
return 0;
}
static void test_RoGetAgileReference(void)
{
struct test_RoGetAgileReference_thread_param param;
struct unk_impl unk_no_marshal_obj = {{&unk_no_marshal_vtbl}, 1};
struct unk_impl unk_obj = {{&unk_vtbl}, 1};
enum AgileReferenceOptions option;
IAgileReference *agile_reference;
RO_INIT_TYPE from_type, to_type;
IUnknown *unknown, *unknown2;
IAgileObject *agile_object;
HANDLE thread;
HRESULT hr;
DWORD ret;
for (option = AGILEREFERENCE_DEFAULT; option <= AGILEREFERENCE_DELAYEDMARSHAL; option++)
{
for (from_type = RO_INIT_SINGLETHREADED; from_type <= RO_INIT_MULTITHREADED; from_type++)
{
winetest_push_context("%d %d", option, from_type);
hr = RoGetAgileReference(option, &IID_IUnknown, &unk_obj.IUnknown_iface, &agile_reference);
ok(hr == CO_E_NOTINITIALIZED, "Got unexpected hr %#lx.\n", hr);
RoInitialize(from_type);
agile_reference = NULL;
EXPECT_REF(&unk_obj, 1);
/* Invalid option */
hr = RoGetAgileReference(AGILEREFERENCE_DELAYEDMARSHAL + 1, &IID_IUnknown, &unk_obj.IUnknown_iface, &agile_reference);
ok(hr == E_INVALIDARG, "Got unexpected hr %#lx.\n", hr);
/* Non-existent interface for the object */
hr = RoGetAgileReference(option, &IID_IActivationFactory, &unk_obj.IUnknown_iface, &agile_reference);
ok(hr == E_NOINTERFACE, "Got unexpected hr %#lx.\n", hr);
/* Objects that implements INoMarshal */
hr = RoGetAgileReference(option, &IID_IUnknown, &unk_no_marshal_obj.IUnknown_iface, &agile_reference);
ok(hr == CO_E_NOT_SUPPORTED, "Got unexpected hr %#lx.\n", hr);
/* Create agile reference object */
hr = RoGetAgileReference(option, &IID_IUnknown, &unk_obj.IUnknown_iface, &agile_reference);
ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr);
ok(!!agile_reference, "Got unexpected agile_reference.\n");
todo_wine_if(option == AGILEREFERENCE_DEFAULT)
EXPECT_REF(&unk_obj, 2);
/* Check the created agile reference object has IAgileObject */
hr = IAgileReference_QueryInterface(agile_reference, &IID_IAgileObject, (void **)&agile_object);
ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr);
IAgileObject_Release(agile_object);
/* Resolve once */
unknown = NULL;
hr = IAgileReference_Resolve(agile_reference, &IID_IUnknown, (void **)&unknown);
ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr);
ok(!!unknown, "Expected pointer not NULL.\n");
ok(unknown == &unk_obj.IUnknown_iface, "Expected the same object.\n");
todo_wine
EXPECT_REF(&unk_obj, 3);
/* Resolve twice */
unknown2 = NULL;
hr = IAgileReference_Resolve(agile_reference, &IID_IUnknown, (void **)&unknown2);
ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr);
ok(!!unknown2, "Expected pointer not NULL.\n");
ok(unknown2 == &unk_obj.IUnknown_iface, "Expected the same object.\n");
todo_wine
EXPECT_REF(&unk_obj, 4);
/* Resolve in another apartment */
for (to_type = RO_INIT_SINGLETHREADED; to_type <= RO_INIT_MULTITHREADED; to_type++)
{
param.option = option;
param.from_type = from_type;
param.to_type = to_type;
param.agile_reference = agile_reference;
param.unk_obj = &unk_obj.IUnknown_iface;
thread = CreateThread(NULL, 0, test_RoGetAgileReference_thread_proc, &param, 0, NULL);
flush_events();
ret = WaitForSingleObject(thread, 100);
ok(!ret, "WaitForSingleObject failed, error %ld.\n", GetLastError());
}
IUnknown_Release(unknown2);
IUnknown_Release(unknown);
IAgileReference_Release(agile_reference);
EXPECT_REF(&unk_obj, 1);
RoUninitialize();
winetest_pop_context();
}
}
}
static void test_RoGetErrorReportingFlags(void)
{
UINT32 flags;
HRESULT hr;
hr = RoGetErrorReportingFlags(NULL);
ok(hr == E_POINTER, "Got unexpected hr %#lx.\n", hr);
hr = RoGetErrorReportingFlags(&flags);
ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr);
ok(flags == RO_ERROR_REPORTING_USESETERRORINFO, "Got unexpected flag %#x.\n", flags);
}
START_TEST(roapi)
{
BOOL ret;
load_resource(L"wine.combase.test.dll");
test_implicit_mta();
test_ActivationFactories();
test_RoGetAgileReference();
test_RoGetErrorReportingFlags();
SetLastError(0xdeadbeef);
ret = DeleteFileW(L"wine.combase.test.dll");
todo_wine_if(!ret && GetLastError() == ERROR_ACCESS_DENIED)
ok(ret, "Failed to delete file, error %lu\n", GetLastError());
}