/* * Copyright 1995 Martin von Loewis * Copyright 1998 Justin Bradford * Copyright 1999 Francis Beaudet * Copyright 1999 Sylvain St-Germain * Copyright 2002 Marcus Meissner * Copyright 2004 Mike Hearn * Copyright 2005-2006 Robert Shearman (for CodeWeavers) * * 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 * */ #include #include #define COBJMACROS #define NONAMELESSUNION #include "windef.h" #include "winbase.h" #include "servprov.h" #include "combase_private.h" #include "wine/debug.h" #include "wine/list.h" WINE_DEFAULT_DEBUG_CHANNEL(ole); enum comclass_threadingmodel { ThreadingModel_Apartment = 1, ThreadingModel_Free = 2, ThreadingModel_No = 3, ThreadingModel_Both = 4, ThreadingModel_Neutral = 5 }; static struct apartment *mta; static struct apartment *main_sta; /* the first STA */ static struct list apts = LIST_INIT(apts); static CRITICAL_SECTION apt_cs; static CRITICAL_SECTION_DEBUG apt_cs_debug = { 0, 0, &apt_cs, { &apt_cs_debug.ProcessLocksList, &apt_cs_debug.ProcessLocksList }, 0, 0, { (DWORD_PTR)(__FILE__ ": apt_cs") } }; static CRITICAL_SECTION apt_cs = { &apt_cs_debug, -1, 0, 0, 0, 0 }; static struct list dlls = LIST_INIT(dlls); static CRITICAL_SECTION dlls_cs; static CRITICAL_SECTION_DEBUG dlls_cs_debug = { 0, 0, &dlls_cs, { &dlls_cs_debug.ProcessLocksList, &dlls_cs_debug.ProcessLocksList }, 0, 0, { (DWORD_PTR)(__FILE__ ": dlls_cs") } }; static CRITICAL_SECTION dlls_cs = { &dlls_cs_debug, -1, 0, 0, 0, 0 }; typedef HRESULT (WINAPI *DllGetClassObjectFunc)(REFCLSID clsid, REFIID iid, void **obj); typedef HRESULT (WINAPI *DllCanUnloadNowFunc)(void); struct opendll { LONG refs; LPWSTR library_name; HANDLE library; DllGetClassObjectFunc DllGetClassObject; DllCanUnloadNowFunc DllCanUnloadNow; struct list entry; }; struct apartment_loaded_dll { struct list entry; struct opendll *dll; DWORD unload_time; BOOL multi_threaded; }; static struct opendll *apartment_get_dll(const WCHAR *library_name) { struct opendll *ptr, *ret = NULL; EnterCriticalSection(&dlls_cs); LIST_FOR_EACH_ENTRY(ptr, &dlls, struct opendll, entry) { if (!wcsicmp(library_name, ptr->library_name) && (InterlockedIncrement(&ptr->refs) != 1) /* entry is being destroyed if == 1 */) { ret = ptr; break; } } LeaveCriticalSection(&dlls_cs); return ret; } /* caller must ensure that library_name is not already in the open dll list */ static HRESULT apartment_add_dll(const WCHAR *library_name, struct opendll **ret) { struct opendll *entry; int len; HRESULT hr = S_OK; HANDLE hLibrary; DllCanUnloadNowFunc DllCanUnloadNow; DllGetClassObjectFunc DllGetClassObject; TRACE("%s\n", debugstr_w(library_name)); *ret = apartment_get_dll(library_name); if (*ret) return S_OK; /* Load outside of dlls lock to avoid dependency on the loader lock */ hLibrary = LoadLibraryExW(library_name, 0, LOAD_WITH_ALTERED_SEARCH_PATH); if (!hLibrary) { ERR("couldn't load in-process dll %s\n", debugstr_w(library_name)); /* failure: DLL could not be loaded */ return E_ACCESSDENIED; /* FIXME: or should this be CO_E_DLLNOTFOUND? */ } /* DllCanUnloadNow is optional */ DllCanUnloadNow = (void *)GetProcAddress(hLibrary, "DllCanUnloadNow"); DllGetClassObject = (void *)GetProcAddress(hLibrary, "DllGetClassObject"); if (!DllGetClassObject) { /* failure: the dll did not export DllGetClassObject */ ERR("couldn't find function DllGetClassObject in %s\n", debugstr_w(library_name)); FreeLibrary(hLibrary); return CO_E_DLLNOTFOUND; } EnterCriticalSection(&dlls_cs); *ret = apartment_get_dll(library_name); if (*ret) { /* another caller to this function already added the dll while we * weren't in the critical section */ FreeLibrary(hLibrary); } else { len = lstrlenW(library_name); entry = malloc(sizeof(*entry)); if (entry) entry->library_name = malloc((len + 1) * sizeof(WCHAR)); if (entry && entry->library_name) { memcpy(entry->library_name, library_name, (len + 1)*sizeof(WCHAR)); entry->library = hLibrary; entry->refs = 1; entry->DllCanUnloadNow = DllCanUnloadNow; entry->DllGetClassObject = DllGetClassObject; list_add_tail(&dlls, &entry->entry); *ret = entry; } else { free(entry); hr = E_OUTOFMEMORY; FreeLibrary(hLibrary); } } LeaveCriticalSection(&dlls_cs); return hr; } /* pass FALSE for free_entry to release a reference without destroying the * entry if it reaches zero or TRUE otherwise */ static void apartment_release_dll(struct opendll *entry, BOOL free_entry) { if (!InterlockedDecrement(&entry->refs) && free_entry) { EnterCriticalSection(&dlls_cs); list_remove(&entry->entry); LeaveCriticalSection(&dlls_cs); TRACE("freeing %p\n", entry->library); FreeLibrary(entry->library); free(entry->library_name); free(entry); } } /* frees memory associated with active dll list */ static void apartment_release_dlls(void) { struct opendll *entry, *cursor2; EnterCriticalSection(&dlls_cs); LIST_FOR_EACH_ENTRY_SAFE(entry, cursor2, &dlls, struct opendll, entry) { list_remove(&entry->entry); free(entry->library_name); free(entry); } LeaveCriticalSection(&dlls_cs); DeleteCriticalSection(&dlls_cs); } /* * This is a marshallable object exposing registered local servers. * IServiceProvider is used only because it happens meet requirements * and already has proxy/stub code. If more functionality is needed, * a custom interface may be used instead. */ struct local_server { IServiceProvider IServiceProvider_iface; LONG refcount; struct apartment *apt; IStream *marshal_stream; }; static inline struct local_server *impl_from_IServiceProvider(IServiceProvider *iface) { return CONTAINING_RECORD(iface, struct local_server, IServiceProvider_iface); } static HRESULT WINAPI local_server_QueryInterface(IServiceProvider *iface, REFIID riid, void **obj) { struct local_server *local_server = impl_from_IServiceProvider(iface); TRACE("%p, %s, %p\n", iface, debugstr_guid(riid), obj); if (IsEqualGUID(riid, &IID_IUnknown) || IsEqualGUID(riid, &IID_IServiceProvider)) { *obj = &local_server->IServiceProvider_iface; } else { *obj = NULL; return E_NOINTERFACE; } IUnknown_AddRef((IUnknown *)*obj); return S_OK; } static ULONG WINAPI local_server_AddRef(IServiceProvider *iface) { struct local_server *local_server = impl_from_IServiceProvider(iface); LONG refcount = InterlockedIncrement(&local_server->refcount); TRACE("%p, refcount %ld\n", iface, refcount); return refcount; } static ULONG WINAPI local_server_Release(IServiceProvider *iface) { struct local_server *local_server = impl_from_IServiceProvider(iface); LONG refcount = InterlockedDecrement(&local_server->refcount); TRACE("%p, refcount %ld\n", iface, refcount); if (!refcount) { assert(!local_server->apt); free(local_server); } return refcount; } static HRESULT WINAPI local_server_QueryService(IServiceProvider *iface, REFGUID guid, REFIID riid, void **obj) { struct local_server *local_server = impl_from_IServiceProvider(iface); struct apartment *apt = com_get_current_apt(); HRESULT hr = E_FAIL; IUnknown *unk; TRACE("%p, %s, %s, %p\n", iface, debugstr_guid(guid), debugstr_guid(riid), obj); if (!local_server->apt) return E_UNEXPECTED; if ((unk = com_get_registered_class_object(apt, guid, CLSCTX_LOCAL_SERVER))) { hr = IUnknown_QueryInterface(unk, riid, obj); IUnknown_Release(unk); } return hr; } static const IServiceProviderVtbl local_server_vtbl = { local_server_QueryInterface, local_server_AddRef, local_server_Release, local_server_QueryService }; HRESULT apartment_get_local_server_stream(struct apartment *apt, IStream **ret) { HRESULT hr = S_OK; EnterCriticalSection(&apt->cs); if (!apt->local_server) { struct local_server *obj; obj = malloc(sizeof(*obj)); if (obj) { obj->IServiceProvider_iface.lpVtbl = &local_server_vtbl; obj->refcount = 1; obj->apt = apt; hr = CreateStreamOnHGlobal(0, TRUE, &obj->marshal_stream); if (SUCCEEDED(hr)) { hr = CoMarshalInterface(obj->marshal_stream, &IID_IServiceProvider, (IUnknown *)&obj->IServiceProvider_iface, MSHCTX_LOCAL, NULL, MSHLFLAGS_TABLESTRONG); if (FAILED(hr)) IStream_Release(obj->marshal_stream); } if (SUCCEEDED(hr)) apt->local_server = obj; else free(obj); } else hr = E_OUTOFMEMORY; } if (SUCCEEDED(hr)) hr = IStream_Clone(apt->local_server->marshal_stream, ret); LeaveCriticalSection(&apt->cs); if (FAILED(hr)) ERR("Failed: %#lx\n", hr); return hr; } /* Creates new apartment for given model */ static struct apartment *apartment_construct(DWORD model) { struct apartment *apt; TRACE("creating new apartment, model %ld\n", model); apt = calloc(1, sizeof(*apt)); apt->tid = GetCurrentThreadId(); list_init(&apt->proxies); list_init(&apt->stubmgrs); list_init(&apt->loaded_dlls); list_init(&apt->usage_cookies); apt->ipidc = 0; apt->refs = 1; apt->remunk_exported = FALSE; apt->oidc = 1; InitializeCriticalSection(&apt->cs); apt->cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": apartment"); apt->multi_threaded = !(model & COINIT_APARTMENTTHREADED); if (apt->multi_threaded) { /* FIXME: should be randomly generated by in an RPC call to rpcss */ apt->oxid = ((OXID)GetCurrentProcessId() << 32) | 0xcafe; } else { /* FIXME: should be randomly generated by in an RPC call to rpcss */ apt->oxid = ((OXID)GetCurrentProcessId() << 32) | GetCurrentThreadId(); } TRACE("Created apartment on OXID %s\n", wine_dbgstr_longlong(apt->oxid)); list_add_head(&apts, &apt->entry); return apt; } /* Frees unused libraries loaded into apartment */ void apartment_freeunusedlibraries(struct apartment *apt, DWORD delay) { struct apartment_loaded_dll *entry, *next; EnterCriticalSection(&apt->cs); LIST_FOR_EACH_ENTRY_SAFE(entry, next, &apt->loaded_dlls, struct apartment_loaded_dll, entry) { if (entry->dll->DllCanUnloadNow && (entry->dll->DllCanUnloadNow() == S_OK)) { DWORD real_delay = delay; if (real_delay == INFINITE) { /* DLLs that return multi-threaded objects aren't unloaded * straight away to cope for programs that have races between * last object destruction and threads in the DLLs that haven't * finished, despite DllCanUnloadNow returning S_OK */ if (entry->multi_threaded) real_delay = 10 * 60 * 1000; /* 10 minutes */ else real_delay = 0; } if (!real_delay || (entry->unload_time && ((int)(GetTickCount() - entry->unload_time) > 0))) { list_remove(&entry->entry); apartment_release_dll(entry->dll, TRUE); free(entry); } else { entry->unload_time = GetTickCount() + real_delay; if (!entry->unload_time) entry->unload_time = 1; } } else if (entry->unload_time) entry->unload_time = 0; } LeaveCriticalSection(&apt->cs); } void apartment_release(struct apartment *apt) { DWORD refcount; EnterCriticalSection(&apt_cs); refcount = InterlockedDecrement(&apt->refs); TRACE("%s: after = %ld\n", wine_dbgstr_longlong(apt->oxid), refcount); if (apt->being_destroyed) { LeaveCriticalSection(&apt_cs); return; } /* destruction stuff that needs to happen under global */ if (!refcount) { apt->being_destroyed = TRUE; if (apt == mta) mta = NULL; else if (apt == main_sta) main_sta = NULL; list_remove(&apt->entry); } LeaveCriticalSection(&apt_cs); if (!refcount) { struct list *cursor, *cursor2; TRACE("destroying apartment %p, oxid %s\n", apt, wine_dbgstr_longlong(apt->oxid)); if (apt->local_server) { struct local_server *local_server = apt->local_server; LARGE_INTEGER zero; memset(&zero, 0, sizeof(zero)); IStream_Seek(local_server->marshal_stream, zero, STREAM_SEEK_SET, NULL); CoReleaseMarshalData(local_server->marshal_stream); IStream_Release(local_server->marshal_stream); local_server->marshal_stream = NULL; apt->local_server = NULL; local_server->apt = NULL; IServiceProvider_Release(&local_server->IServiceProvider_iface); } /* Release the references to the registered class objects */ apartment_revoke_all_classes(apt); /* no locking is needed for this apartment, because no other thread * can access it at this point */ apartment_disconnectproxies(apt); if (apt->win) DestroyWindow(apt->win); if (apt->host_apt_tid) PostThreadMessageW(apt->host_apt_tid, WM_QUIT, 0, 0); LIST_FOR_EACH_SAFE(cursor, cursor2, &apt->stubmgrs) { struct stub_manager *stubmgr = LIST_ENTRY(cursor, struct stub_manager, entry); /* release the implicit reference given by the fact that the * stub has external references (it must do since it is in the * stub manager list in the apartment and all non-apartment users * must have a ref on the apartment and so it cannot be destroyed). */ stub_manager_int_release(stubmgr); } /* if this assert fires, then another thread took a reference to a * stub manager without taking a reference to the containing * apartment, which it must do. */ assert(list_empty(&apt->stubmgrs)); if (apt->filter) IMessageFilter_Release(apt->filter); /* free as many unused libraries as possible... */ apartment_freeunusedlibraries(apt, 0); /* ... and free the memory for the apartment loaded dll entry and * release the dll list reference without freeing the library for the * rest */ while ((cursor = list_head(&apt->loaded_dlls))) { struct apartment_loaded_dll *apartment_loaded_dll = LIST_ENTRY(cursor, struct apartment_loaded_dll, entry); apartment_release_dll(apartment_loaded_dll->dll, FALSE); list_remove(cursor); free(apartment_loaded_dll); } apt->cs.DebugInfo->Spare[0] = 0; DeleteCriticalSection(&apt->cs); free(apt); } } static DWORD apartment_addref(struct apartment *apt) { DWORD refs = InterlockedIncrement(&apt->refs); TRACE("%s: before = %ld\n", wine_dbgstr_longlong(apt->oxid), refs - 1); return refs; } /* Gets existing apartment or creates a new one and enters it */ static struct apartment *apartment_get_or_create(DWORD model) { struct apartment *apt = com_get_current_apt(); struct tlsdata *data; if (!apt) { com_get_tlsdata(&data); if (model & COINIT_APARTMENTTHREADED) { EnterCriticalSection(&apt_cs); apt = apartment_construct(model); if (!main_sta) { main_sta = apt; apt->main = TRUE; TRACE("Created main-threaded apartment with OXID %s\n", wine_dbgstr_longlong(apt->oxid)); } data->flags |= OLETLS_APARTMENTTHREADED; if (model & COINIT_DISABLE_OLE1DDE) data->flags |= OLETLS_DISABLE_OLE1DDE; LeaveCriticalSection(&apt_cs); if (apt->main) apartment_createwindowifneeded(apt); } else { EnterCriticalSection(&apt_cs); /* The multi-threaded apartment (MTA) contains zero or more threads interacting * with free threaded (ie thread safe) COM objects. There is only ever one MTA * in a process */ if (mta) { TRACE("entering the multithreaded apartment %s\n", wine_dbgstr_longlong(mta->oxid)); apartment_addref(mta); } else mta = apartment_construct(model); data->flags |= OLETLS_MULTITHREADED | OLETLS_DISABLE_OLE1DDE; apt = mta; LeaveCriticalSection(&apt_cs); } data->apt = apt; } return apt; } struct apartment * apartment_get_mta(void) { struct apartment *apt; EnterCriticalSection(&apt_cs); if ((apt = mta)) apartment_addref(apt); LeaveCriticalSection(&apt_cs); return apt; } /* Return the current apartment if it exists, or, failing that, the MTA. Caller * must free the returned apartment in either case. */ struct apartment * apartment_get_current_or_mta(void) { struct apartment *apt = com_get_current_apt(); if (apt) { apartment_addref(apt); return apt; } return apartment_get_mta(); } /* The given OXID must be local to this process */ struct apartment * apartment_findfromoxid(OXID oxid) { struct apartment *result = NULL, *apt; EnterCriticalSection(&apt_cs); LIST_FOR_EACH_ENTRY(apt, &apts, struct apartment, entry) { if (apt->oxid == oxid) { result = apt; apartment_addref(result); break; } } LeaveCriticalSection(&apt_cs); return result; } /* gets the apartment which has a given creator thread ID. The caller must * release the reference from the apartment as soon as the apartment pointer * is no longer required. */ struct apartment * apartment_findfromtid(DWORD tid) { struct apartment *result = NULL, *apt; EnterCriticalSection(&apt_cs); LIST_FOR_EACH_ENTRY(apt, &apts, struct apartment, entry) { if (apt != mta && apt->tid == tid) { result = apt; apartment_addref(result); break; } } if (!result && mta && mta->tid == tid) { result = mta; apartment_addref(result); } LeaveCriticalSection(&apt_cs); return result; } /* gets the main apartment if it exists. The caller must * release the reference from the apartment as soon as the apartment pointer * is no longer required. */ static struct apartment *apartment_findmain(void) { struct apartment *result; EnterCriticalSection(&apt_cs); result = main_sta; if (result) apartment_addref(result); LeaveCriticalSection(&apt_cs); return result; } struct host_object_params { struct class_reg_data regdata; CLSID clsid; /* clsid of object to marshal */ IID iid; /* interface to marshal */ HANDLE event; /* event signalling when ready for multi-threaded case */ HRESULT hr; /* result for multi-threaded case */ IStream *stream; /* stream that the object will be marshaled into */ BOOL apartment_threaded; /* is the component purely apartment-threaded? */ }; /* Returns expanded dll path from the registry or activation context. */ static BOOL get_object_dll_path(const struct class_reg_data *regdata, WCHAR *dst, DWORD dstlen) { DWORD ret; if (regdata->origin == CLASS_REG_REGISTRY) { DWORD keytype; WCHAR src[MAX_PATH]; DWORD dwLength = dstlen * sizeof(WCHAR); if ((ret = RegQueryValueExW(regdata->u.hkey, NULL, NULL, &keytype, (BYTE*)src, &dwLength)) == ERROR_SUCCESS) { if (keytype == REG_EXPAND_SZ) { if (dstlen <= ExpandEnvironmentStringsW(src, dst, dstlen)) ret = ERROR_MORE_DATA; } else { const WCHAR *quote_start; quote_start = wcschr(src, '\"'); if (quote_start) { const WCHAR *quote_end = wcschr(quote_start + 1, '\"'); if (quote_end) { memmove(src, quote_start + 1, (quote_end - quote_start - 1) * sizeof(WCHAR)); src[quote_end - quote_start - 1] = '\0'; } } lstrcpynW(dst, src, dstlen); } } return !ret; } else { ULONG_PTR cookie; *dst = 0; ActivateActCtx(regdata->u.actctx.hactctx, &cookie); ret = SearchPathW(NULL, regdata->u.actctx.module_name, L".dll", dstlen, dst, NULL); DeactivateActCtx(0, cookie); return *dst != 0; } } /* gets the specified class object by loading the appropriate DLL, if * necessary and calls the DllGetClassObject function for the DLL */ static HRESULT apartment_getclassobject(struct apartment *apt, LPCWSTR dllpath, BOOL apartment_threaded, REFCLSID rclsid, REFIID riid, void **ppv) { HRESULT hr = S_OK; BOOL found = FALSE; struct apartment_loaded_dll *apartment_loaded_dll; if (!wcsicmp(dllpath, L"ole32.dll")) { HRESULT (WINAPI *p_ole32_DllGetClassObject)(REFCLSID clsid, REFIID riid, void **obj); p_ole32_DllGetClassObject = (void *)GetProcAddress(GetModuleHandleW(L"ole32.dll"), "DllGetClassObject"); /* we don't need to control the lifetime of this dll, so use the local * implementation of DllGetClassObject directly */ TRACE("calling ole32!DllGetClassObject\n"); hr = p_ole32_DllGetClassObject(rclsid, riid, ppv); if (hr != S_OK) ERR("DllGetClassObject returned error %#lx for dll %s\n", hr, debugstr_w(dllpath)); return hr; } EnterCriticalSection(&apt->cs); LIST_FOR_EACH_ENTRY(apartment_loaded_dll, &apt->loaded_dlls, struct apartment_loaded_dll, entry) if (!wcsicmp(dllpath, apartment_loaded_dll->dll->library_name)) { TRACE("found %s already loaded\n", debugstr_w(dllpath)); found = TRUE; break; } if (!found) { apartment_loaded_dll = malloc(sizeof(*apartment_loaded_dll)); if (!apartment_loaded_dll) hr = E_OUTOFMEMORY; if (SUCCEEDED(hr)) { apartment_loaded_dll->unload_time = 0; apartment_loaded_dll->multi_threaded = FALSE; hr = apartment_add_dll(dllpath, &apartment_loaded_dll->dll); if (FAILED(hr)) free(apartment_loaded_dll); } if (SUCCEEDED(hr)) { TRACE("added new loaded dll %s\n", debugstr_w(dllpath)); list_add_tail(&apt->loaded_dlls, &apartment_loaded_dll->entry); } } LeaveCriticalSection(&apt->cs); if (SUCCEEDED(hr)) { /* one component being multi-threaded overrides any number of * apartment-threaded components */ if (!apartment_threaded) apartment_loaded_dll->multi_threaded = TRUE; TRACE("calling DllGetClassObject %p\n", apartment_loaded_dll->dll->DllGetClassObject); /* OK: get the ClassObject */ hr = apartment_loaded_dll->dll->DllGetClassObject(rclsid, riid, ppv); if (hr != S_OK) ERR("DllGetClassObject returned error %#lx for dll %s\n", hr, debugstr_w(dllpath)); } return hr; } static HRESULT apartment_hostobject(struct apartment *apt, const struct host_object_params *params); struct host_thread_params { COINIT threading_model; HANDLE ready_event; HWND apartment_hwnd; }; /* thread for hosting an object to allow an object to appear to be created in * an apartment with an incompatible threading model */ static DWORD CALLBACK apartment_hostobject_thread(void *p) { struct host_thread_params *params = p; MSG msg; HRESULT hr; struct apartment *apt; TRACE("\n"); hr = CoInitializeEx(NULL, params->threading_model); if (FAILED(hr)) return hr; apt = com_get_current_apt(); if (params->threading_model == COINIT_APARTMENTTHREADED) { apartment_createwindowifneeded(apt); params->apartment_hwnd = apartment_getwindow(apt); } else params->apartment_hwnd = NULL; /* force the message queue to be created before signaling parent thread */ PeekMessageW(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); SetEvent(params->ready_event); params = NULL; /* can't touch params after here as it may be invalid */ while (GetMessageW(&msg, NULL, 0, 0)) { if (!msg.hwnd && (msg.message == DM_HOSTOBJECT)) { struct host_object_params *obj_params = (struct host_object_params *)msg.lParam; obj_params->hr = apartment_hostobject(apt, obj_params); SetEvent(obj_params->event); } else { TranslateMessage(&msg); DispatchMessageW(&msg); } } TRACE("exiting\n"); CoUninitialize(); return S_OK; } /* finds or creates a host apartment, creates the object inside it and returns * a proxy to it so that the object can be used in the apartment of the * caller of this function */ static HRESULT apartment_hostobject_in_hostapt(struct apartment *apt, BOOL multi_threaded, BOOL main_apartment, const struct class_reg_data *regdata, REFCLSID rclsid, REFIID riid, void **ppv) { struct host_object_params params; HWND apartment_hwnd = NULL; DWORD apartment_tid = 0; HRESULT hr; if (!multi_threaded && main_apartment) { struct apartment *host_apt = apartment_findmain(); if (host_apt) { apartment_hwnd = apartment_getwindow(host_apt); apartment_release(host_apt); } } if (!apartment_hwnd) { EnterCriticalSection(&apt->cs); if (!apt->host_apt_tid) { struct host_thread_params thread_params; HANDLE handles[2]; DWORD wait_value; thread_params.threading_model = multi_threaded ? COINIT_MULTITHREADED : COINIT_APARTMENTTHREADED; handles[0] = thread_params.ready_event = CreateEventW(NULL, FALSE, FALSE, NULL); thread_params.apartment_hwnd = NULL; handles[1] = CreateThread(NULL, 0, apartment_hostobject_thread, &thread_params, 0, &apt->host_apt_tid); if (!handles[1]) { CloseHandle(handles[0]); LeaveCriticalSection(&apt->cs); return E_OUTOFMEMORY; } wait_value = WaitForMultipleObjects(2, handles, FALSE, INFINITE); CloseHandle(handles[0]); CloseHandle(handles[1]); if (wait_value == WAIT_OBJECT_0) apt->host_apt_hwnd = thread_params.apartment_hwnd; else { LeaveCriticalSection(&apt->cs); return E_OUTOFMEMORY; } } if (multi_threaded || !main_apartment) { apartment_hwnd = apt->host_apt_hwnd; apartment_tid = apt->host_apt_tid; } LeaveCriticalSection(&apt->cs); } /* another thread may have become the main apartment in the time it took * us to create the thread for the host apartment */ if (!apartment_hwnd && !multi_threaded && main_apartment) { struct apartment *host_apt = apartment_findmain(); if (host_apt) { apartment_hwnd = apartment_getwindow(host_apt); apartment_release(host_apt); } } params.regdata = *regdata; params.clsid = *rclsid; params.iid = *riid; hr = CreateStreamOnHGlobal(NULL, TRUE, ¶ms.stream); if (FAILED(hr)) return hr; params.apartment_threaded = !multi_threaded; if (multi_threaded) { params.hr = S_OK; params.event = CreateEventW(NULL, FALSE, FALSE, NULL); if (!PostThreadMessageW(apartment_tid, DM_HOSTOBJECT, 0, (LPARAM)¶ms)) hr = E_OUTOFMEMORY; else { WaitForSingleObject(params.event, INFINITE); hr = params.hr; } CloseHandle(params.event); } else { if (!apartment_hwnd) { ERR("host apartment didn't create window\n"); hr = E_OUTOFMEMORY; } else hr = SendMessageW(apartment_hwnd, DM_HOSTOBJECT, 0, (LPARAM)¶ms); } if (SUCCEEDED(hr)) hr = CoUnmarshalInterface(params.stream, riid, ppv); IStream_Release(params.stream); return hr; } static enum comclass_threadingmodel get_threading_model(const struct class_reg_data *data) { if (data->origin == CLASS_REG_REGISTRY) { WCHAR threading_model[10 /* lstrlenW(L"apartment")+1 */]; DWORD dwLength = sizeof(threading_model); DWORD keytype; DWORD ret; ret = RegQueryValueExW(data->u.hkey, L"ThreadingModel", NULL, &keytype, (BYTE*)threading_model, &dwLength); if ((ret != ERROR_SUCCESS) || (keytype != REG_SZ)) threading_model[0] = '\0'; if (!wcsicmp(threading_model, L"Apartment")) return ThreadingModel_Apartment; if (!wcsicmp(threading_model, L"Free")) return ThreadingModel_Free; if (!wcsicmp(threading_model, L"Both")) return ThreadingModel_Both; /* there's not specific handling for this case */ if (threading_model[0]) return ThreadingModel_Neutral; return ThreadingModel_No; } else return data->u.actctx.threading_model; } HRESULT apartment_get_inproc_class_object(struct apartment *apt, const struct class_reg_data *regdata, REFCLSID rclsid, REFIID riid, DWORD class_context, void **ppv) { WCHAR dllpath[MAX_PATH+1]; BOOL apartment_threaded; if (!(class_context & CLSCTX_PS_DLL)) { enum comclass_threadingmodel model = get_threading_model(regdata); if (model == ThreadingModel_Apartment) { apartment_threaded = TRUE; if (apt->multi_threaded) return apartment_hostobject_in_hostapt(apt, FALSE, FALSE, regdata, rclsid, riid, ppv); } else if (model == ThreadingModel_Free) { apartment_threaded = FALSE; if (!apt->multi_threaded) return apartment_hostobject_in_hostapt(apt, TRUE, FALSE, regdata, rclsid, riid, ppv); } /* everything except "Apartment", "Free" and "Both" */ else if (model != ThreadingModel_Both) { apartment_threaded = TRUE; /* everything else is main-threaded */ if (model != ThreadingModel_No) FIXME("unrecognised threading model %d for object %s, should be main-threaded?\n", model, debugstr_guid(rclsid)); if (apt->multi_threaded || !apt->main) return apartment_hostobject_in_hostapt(apt, FALSE, TRUE, regdata, rclsid, riid, ppv); } else apartment_threaded = FALSE; } else apartment_threaded = !apt->multi_threaded; if (!get_object_dll_path(regdata, dllpath, ARRAY_SIZE(dllpath))) { /* failure: CLSID is not found in registry */ WARN("class %s not registered inproc\n", debugstr_guid(rclsid)); return REGDB_E_CLASSNOTREG; } return apartment_getclassobject(apt, dllpath, apartment_threaded, rclsid, riid, ppv); } static HRESULT apartment_hostobject(struct apartment *apt, const struct host_object_params *params) { static const LARGE_INTEGER llZero; WCHAR dllpath[MAX_PATH+1]; IUnknown *object; HRESULT hr; TRACE("clsid %s, iid %s\n", debugstr_guid(¶ms->clsid), debugstr_guid(¶ms->iid)); if (!get_object_dll_path(¶ms->regdata, dllpath, ARRAY_SIZE(dllpath))) { /* failure: CLSID is not found in registry */ WARN("class %s not registered inproc\n", debugstr_guid(¶ms->clsid)); return REGDB_E_CLASSNOTREG; } hr = apartment_getclassobject(apt, dllpath, params->apartment_threaded, ¶ms->clsid, ¶ms->iid, (void **)&object); if (FAILED(hr)) return hr; hr = CoMarshalInterface(params->stream, ¶ms->iid, object, MSHCTX_INPROC, NULL, MSHLFLAGS_NORMAL); if (FAILED(hr)) IUnknown_Release(object); IStream_Seek(params->stream, llZero, STREAM_SEEK_SET, NULL); return hr; } struct dispatch_params; static LRESULT CALLBACK apartment_wndproc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case DM_EXECUTERPC: rpc_execute_call((struct dispatch_params *)lParam); return 0; case DM_HOSTOBJECT: return apartment_hostobject(com_get_current_apt(), (const struct host_object_params *)lParam); default: return DefWindowProcW(hWnd, msg, wParam, lParam); } } static BOOL apartment_is_model(const struct apartment *apt, DWORD model) { return (apt->multi_threaded == !(model & COINIT_APARTMENTTHREADED)); } HRESULT enter_apartment(struct tlsdata *data, DWORD model) { HRESULT hr = S_OK; if (!data->apt) { if (!apartment_get_or_create(model)) return E_OUTOFMEMORY; } else if (!apartment_is_model(data->apt, model)) { WARN("Attempt to change threading model of this apartment from %s to %s\n", data->apt->multi_threaded ? "multi-threaded" : "apartment threaded", model & COINIT_APARTMENTTHREADED ? "apartment threaded" : "multi-threaded" ); return RPC_E_CHANGED_MODE; } else hr = S_FALSE; data->inits++; return hr; } void leave_apartment(struct tlsdata *data) { if (!--data->inits) { if (data->ole_inits) WARN( "Uninitializing apartment while Ole is still initialized\n" ); apartment_release(data->apt); data->apt = NULL; data->flags &= ~(OLETLS_DISABLE_OLE1DDE | OLETLS_APARTMENTTHREADED | OLETLS_MULTITHREADED); } } struct mta_cookie { struct list entry; }; HRESULT apartment_increment_mta_usage(CO_MTA_USAGE_COOKIE *cookie) { struct mta_cookie *mta_cookie; *cookie = NULL; if (!(mta_cookie = malloc(sizeof(*mta_cookie)))) return E_OUTOFMEMORY; EnterCriticalSection(&apt_cs); if (mta) apartment_addref(mta); else mta = apartment_construct(COINIT_MULTITHREADED); list_add_head(&mta->usage_cookies, &mta_cookie->entry); LeaveCriticalSection(&apt_cs); *cookie = (CO_MTA_USAGE_COOKIE)mta_cookie; return S_OK; } void apartment_decrement_mta_usage(CO_MTA_USAGE_COOKIE cookie) { struct mta_cookie *mta_cookie = (struct mta_cookie *)cookie; EnterCriticalSection(&apt_cs); if (mta) { struct mta_cookie *cur; LIST_FOR_EACH_ENTRY(cur, &mta->usage_cookies, struct mta_cookie, entry) { if (mta_cookie == cur) { list_remove(&cur->entry); free(cur); apartment_release(mta); break; } } } LeaveCriticalSection(&apt_cs); } static const WCHAR aptwinclassW[] = L"OleMainThreadWndClass"; static ATOM apt_win_class; static BOOL WINAPI register_class( INIT_ONCE *once, void *param, void **context ) { WNDCLASSW wclass; /* Dispatching to the correct thread in an apartment is done through * window messages rather than RPC transports. When an interface is * marshalled into another apartment in the same process, a window of the * following class is created. The *caller* of CoMarshalInterface (i.e., the * application) is responsible for pumping the message loop in that thread. * The WM_USER messages which point to the RPCs are then dispatched to * apartment_wndproc by the user's code from the apartment in which the * interface was unmarshalled. */ memset(&wclass, 0, sizeof(wclass)); wclass.lpfnWndProc = apartment_wndproc; wclass.hInstance = hProxyDll; wclass.lpszClassName = aptwinclassW; apt_win_class = RegisterClassW(&wclass); return TRUE; } /* create a window for the apartment or return the current one if one has * already been created */ HRESULT apartment_createwindowifneeded(struct apartment *apt) { static INIT_ONCE class_init_once = INIT_ONCE_STATIC_INIT; if (apt->multi_threaded) return S_OK; if (!apt->win) { HWND hwnd; InitOnceExecuteOnce( &class_init_once, register_class, NULL, NULL ); hwnd = CreateWindowW(aptwinclassW, NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, 0, hProxyDll, NULL); if (!hwnd) { ERR("CreateWindow failed with error %ld\n", GetLastError()); return HRESULT_FROM_WIN32(GetLastError()); } if (InterlockedCompareExchangePointer((void **)&apt->win, hwnd, NULL)) /* someone beat us to it */ DestroyWindow(hwnd); } return S_OK; } /* retrieves the window for the main- or apartment-threaded apartment */ HWND apartment_getwindow(const struct apartment *apt) { assert(!apt->multi_threaded); return apt->win; } OXID apartment_getoxid(const struct apartment *apt) { return apt->oxid; } void apartment_global_cleanup(void) { if (apt_win_class) UnregisterClassW((const WCHAR *)MAKEINTATOM(apt_win_class), hProxyDll); apartment_release_dlls(); DeleteCriticalSection(&apt_cs); }