- Make apartment access thread-safe by introducing refcounting and

wider usage of the apartment lock.
- Rework OLE TLS management to eliminate uninitialised apartments and
  parent chaining.
This commit is contained in:
Mike Hearn 2005-01-05 17:14:33 +00:00 committed by Alexandre Julliard
parent a790b2d1c5
commit a6a416cb4e
6 changed files with 230 additions and 181 deletions

View File

@ -30,6 +30,8 @@
* - Rewrite the CoLockObjectExternal code, it does totally the wrong
* thing currently (should be controlling the stub manager)
*
* - Make the MTA dynamically allocated and refcounted
*
* - Implement the service control manager (in rpcss) to keep track
* of registered class objects: ISCM::ServerRegisterClsid et al
* - Implement the OXID resolver so we don't need magic pipe names for
@ -99,7 +101,8 @@ static void COM_ExternalLockFreeList(void);
const CLSID CLSID_StdGlobalInterfaceTable = { 0x00000323, 0, 0, {0xc0, 0, 0, 0, 0, 0, 0, 0x46} };
APARTMENT MTA, *apts;
APARTMENT MTA;
struct list apts = LIST_INIT( apts );
static CRITICAL_SECTION csApartment;
static CRITICAL_SECTION_DEBUG critsect_debug =
@ -236,6 +239,7 @@ static void COM_UninitMTA(void)
MTA.oxid = 0;
}
/* creates an apartment structure which stores OLE thread-local
* information. Call with COINIT_UNINITIALIZED to create an apartment
* that will be initialized with a model later. Note: do not call
@ -243,93 +247,131 @@ static void COM_UninitMTA(void)
* with a different COINIT value */
APARTMENT* COM_CreateApartment(DWORD model)
{
APARTMENT *apt;
BOOL create = (NtCurrentTeb()->ReservedForOle == NULL);
APARTMENT *apt = COM_CurrentApt();
if (create)
if (!apt)
{
if (model & COINIT_MULTITHREADED)
{
TRACE("thread 0x%lx is entering the multithreaded apartment\n", GetCurrentThreadId());
COM_CurrentInfo()->apt = &MTA;
return apt;
}
TRACE("creating new apartment, model=%ld\n", model);
apt = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(APARTMENT));
apt->tid = GetCurrentThreadId();
DuplicateHandle(GetCurrentProcess(), GetCurrentThread(),
GetCurrentProcess(), &apt->thread,
THREAD_ALL_ACCESS, FALSE, 0);
}
else
apt = NtCurrentTeb()->ReservedForOle;
list_init(&apt->proxies);
list_init(&apt->stubmgrs);
apt->oidc = 1;
apt->refs = 1;
InitializeCriticalSection(&apt->cs);
apt->model = model;
if (model & COINIT_APARTMENTTHREADED) {
/* we don't ref the apartment as CoInitializeEx will do it for us */
if (model & COINIT_APARTMENTTHREADED)
{
/* FIXME: how does windoze create OXIDs? */
apt->oxid = MTA.oxid | GetCurrentThreadId();
apt->win = CreateWindowA(aptWinClass, NULL, 0,
0, 0, 0, 0,
0, 0, OLE32_hInstance, NULL);
InitializeCriticalSection(&apt->cs);
}
else if (!(model & COINIT_UNINITIALIZED)) {
apt->parent = &MTA;
apt->oxid = MTA.oxid;
}
EnterCriticalSection(&csApartment);
if (create)
{
if (apts) apts->prev = apt;
apt->next = apts;
apts = apt;
}
list_add_head(&apts, &apt->entry);
LeaveCriticalSection(&csApartment);
NtCurrentTeb()->ReservedForOle = apt;
COM_CurrentInfo()->apt = apt;
}
return apt;
}
static void COM_DestroyApartment(APARTMENT *apt)
DWORD COM_ApartmentAddRef(struct apartment *apt)
{
return InterlockedIncrement(&apt->refs);
}
DWORD COM_ApartmentRelease(struct apartment *apt)
{
DWORD ret;
EnterCriticalSection(&csApartment);
if (apt->prev) apt->prev->next = apt->next;
if (apt->next) apt->next->prev = apt->prev;
if (apts == apt) apts = apt->next;
apt->prev = NULL; apt->next = NULL;
LeaveCriticalSection(&csApartment);
if (apt->model & COINIT_APARTMENTTHREADED) {
/* disconnect proxies to release the corresponding stubs.
* It is confirmed in "Essential COM" in the sub-chapter on
* "Lifecycle Management and Marshalling" that the native version also
* disconnects proxies in this function. */
/* FIXME: this should also be called for MTA destruction, but that
* requires restructuring how apartments work slightly. */
ret = InterlockedDecrement(&apt->refs);
if (ret == 0)
{
TRACE("destroying apartment %p, oxid %s\n", apt, wine_dbgstr_longlong(apt->oxid));
MARSHAL_Disconnect_Proxies(apt);
if (apt->win) DestroyWindow(apt->win);
DeleteCriticalSection(&apt->cs);
list_remove(&apt->entry);
if ((apt->model & COINIT_APARTMENTTHREADED) && apt->win) DestroyWindow(apt->win);
if (!list_empty(&apt->stubmgrs))
{
FIXME("PANIC: Apartment being destroyed with outstanding stubs, what do we do now?\n");
}
if (apt->filter) IUnknown_Release(apt->filter);
DeleteCriticalSection(&apt->cs);
CloseHandle(apt->thread);
HeapFree(GetProcessHeap(), 0, apt);
}
/* The given OXID must be local to this process: you cannot use apartment
windows to send RPCs to other processes. This all needs to move to rpcrt4 */
apt = NULL;
}
APARTMENT *COM_ApartmentFromOXID(OXID oxid)
{
APARTMENT *apt = NULL;
EnterCriticalSection(&csApartment);
apt = apts;
while (apt && apt->oxid != oxid) apt = apt->next;
LeaveCriticalSection(&csApartment);
return apt;
return ret;
}
HWND COM_GetApartmentWin(OXID oxid)
/* The given OXID must be local to this process: you cannot use
* apartment windows to send RPCs to other processes. This all needs
* to move to rpcrt4.
*
* The ref parameter is here mostly to ensure people remember that
* they get one, you should normally take a ref for thread safety.
*/
APARTMENT *COM_ApartmentFromOXID(OXID oxid, BOOL ref)
{
APARTMENT *apt = COM_ApartmentFromOXID(oxid);
APARTMENT *result = NULL;
struct list *cursor;
return apt ? apt->win : NULL;
EnterCriticalSection(&csApartment);
LIST_FOR_EACH( cursor, &apts )
{
struct apartment *apt = LIST_ENTRY( cursor, struct apartment, entry );
if (apt->oxid == oxid)
{
result = apt;
if (ref) COM_ApartmentAddRef(result);
break;
}
}
LeaveCriticalSection(&csApartment);
return result;
}
HWND COM_GetApartmentWin(OXID oxid, BOOL ref)
{
APARTMENT *apt;
apt = COM_ApartmentFromOXID(oxid, ref);
if (!apt) return NULL;
return apt->win;
}
/* Currently inter-thread marshalling is not fully implemented, so this does nothing */
@ -467,8 +509,7 @@ HRESULT WINAPI CoInitialize(
* CoUninitialize
*/
HRESULT WINAPI CoInitializeEx(
LPVOID lpReserved, /* [in] pointer to win32 malloc interface
(obsolete, should be NULL) */
LPVOID lpReserved, /* [in] pointer to win32 malloc interface (obsolete, should be NULL) */
DWORD dwCoInit /* [in] A value from COINIT specifies the threading model */
)
{
@ -482,21 +523,6 @@ HRESULT WINAPI CoInitializeEx(
ERR("(%p, %x) - Bad parameter passed-in %p, must be an old Windows Application\n", lpReserved, (int)dwCoInit, lpReserved);
}
apt = NtCurrentTeb()->ReservedForOle;
if (apt && !(apt->model == COINIT_UNINITIALIZED))
{
if (dwCoInit != apt->model)
{
/* Changing the threading model after it's been set is illegal. If this warning is triggered by Wine
code then we are probably using the wrong threading model to implement that API. */
ERR("Attempt to change threading model of this apartment from 0x%lx to 0x%lx\n", apt->model, dwCoInit);
return RPC_E_CHANGED_MODE;
}
hr = S_FALSE;
}
else
hr = S_OK;
/*
* Check the lock count. If this is the first time going through the initialize
* process, we have to initialize the libraries.
@ -512,13 +538,27 @@ HRESULT WINAPI CoInitializeEx(
COM_InitMTA();
/* we may need to defer this until after apartment initialisation */
RunningObjectTableImpl_Initialize();
}
if (!apt || apt->model == COINIT_UNINITIALIZED) apt = COM_CreateApartment(dwCoInit);
if (!(apt = COM_CurrentInfo()->apt))
{
apt = COM_CreateApartment(dwCoInit);
}
else
{
InterlockedIncrement(&apt->refs);
}
if (dwCoInit != apt->model)
{
/* Changing the threading model after it's been set is illegal. If this warning is triggered by Wine
code then we are probably using the wrong threading model to implement that API. */
ERR("Attempt to change threading model of this apartment from 0x%lx to 0x%lx\n", apt->model, dwCoInit);
return RPC_E_CHANGED_MODE;
}
InterlockedIncrement(&apt->inits);
if (hr == S_OK) NtCurrentTeb()->ReservedForOle = apt;
return hr;
}
@ -529,14 +569,20 @@ HRESULT WINAPI CoInitializeEx(
void COM_FlushMessageQueue(void)
{
MSG message;
APARTMENT *apt = NtCurrentTeb()->ReservedForOle;
APARTMENT *apt = COM_CurrentApt();
if (!apt || !apt->win) return;
TRACE("Flushing STA message queue\n");
while (PeekMessageA(&message, NULL, 0, 0, PM_REMOVE)) {
if (message.hwnd != apt->win) continue;
while (PeekMessageA(&message, NULL, 0, 0, PM_REMOVE))
{
if (message.hwnd != apt->win)
{
WARN("discarding message 0x%x for window %p\n", message.message, message.hwnd);
continue;
}
TranslateMessage(&message);
DispatchMessageA(&message);
}
@ -561,17 +607,11 @@ void COM_FlushMessageQueue(void)
void WINAPI CoUninitialize(void)
{
LONG lCOMRefCnt;
APARTMENT *apt;
TRACE("()\n");
apt = NtCurrentTeb()->ReservedForOle;
if (!apt) return;
if (InterlockedDecrement(&apt->inits)==0) {
NtCurrentTeb()->ReservedForOle = NULL;
COM_DestroyApartment(apt);
apt = NULL;
}
if (!COM_ApartmentRelease(COM_CurrentInfo()->apt))
COM_CurrentInfo()->apt = NULL;
/*
* Decrease the reference count.
@ -2000,19 +2040,19 @@ HRESULT WINAPI CoInitializeWOW(DWORD x,DWORD y) {
*/
HRESULT WINAPI CoGetState(IUnknown ** ppv)
{
APARTMENT * apt = COM_CurrentInfo();
HRESULT hr = E_FAIL;
FIXME("\n");
if(apt && apt->state) {
IUnknown_AddRef(apt->state);
*ppv = apt->state;
FIXME("-- %p\n", *ppv);
return S_OK;
}
*ppv = NULL;
return E_FAIL;
if (COM_CurrentInfo()->state)
{
IUnknown_AddRef(COM_CurrentInfo()->state);
*ppv = COM_CurrentInfo()->state;
TRACE("apt->state=%p\n", COM_CurrentInfo()->state);
hr = S_OK;
}
return hr;
}
/***********************************************************************
@ -2021,21 +2061,16 @@ HRESULT WINAPI CoGetState(IUnknown ** ppv)
*/
HRESULT WINAPI CoSetState(IUnknown * pv)
{
APARTMENT * apt = COM_CurrentInfo();
if (pv) IUnknown_AddRef(pv);
if (!apt) apt = COM_CreateApartment(COINIT_UNINITIALIZED);
FIXME("(%p),stub!\n", pv);
if (pv) {
IUnknown_AddRef(pv);
if (COM_CurrentInfo()->state)
{
TRACE("-- release %p now\n", COM_CurrentInfo()->state);
IUnknown_Release(COM_CurrentInfo()->state);
}
if (apt->state) {
TRACE("-- release %p now\n", apt->state);
IUnknown_Release(apt->state);
}
apt->state = pv;
COM_CurrentInfo()->state = pv;
return S_OK;
}

View File

@ -38,12 +38,8 @@
#include "winreg.h"
#include "winternl.h"
/* Windows maps COINIT values to 0x80 for apartment threaded, 0x140
* for free threaded, and 0 for uninitialized apartments. There is
* no real advantage in us doing this and certainly no release version
* of an app should be poking around with these flags. So we need a
* special value for uninitialized */
#define COINIT_UNINITIALIZED 0x100
struct apartment;
/* exported interface */
typedef struct tagXIF {
@ -59,7 +55,7 @@ typedef struct tagXIF {
/* exported object */
typedef struct tagXOBJECT {
IRpcStubBufferVtbl *lpVtbl;
struct tagAPARTMENT *parent;
struct apartment *parent;
struct tagXOBJECT *next;
LPUNKNOWN obj; /* object identity (IUnknown) */
OID oid; /* object ID */
@ -83,7 +79,7 @@ struct ifproxy
struct proxy_manager
{
const IInternalUnknownVtbl *lpVtbl;
struct tagAPARTMENT *parent;
struct apartment *parent;
struct list entry;
LPRPCCHANNELBUFFER chan; /* channel to object */
OXID oxid; /* object exported ID */
@ -93,11 +89,13 @@ struct proxy_manager
CRITICAL_SECTION cs; /* thread safety for this object and children */
};
/* apartment */
typedef struct tagAPARTMENT {
struct tagAPARTMENT *next, *prev, *parent;
/* this needs to become a COM object that implements IRemUnknown */
struct apartment
{
struct list entry;
DWORD refs; /* refcount of the apartment */
DWORD model; /* threading model */
DWORD inits; /* CoInitialize count */
DWORD tid; /* thread id */
HANDLE thread; /* thread handle */
OXID oxid; /* object exporter ID */
@ -107,13 +105,11 @@ typedef struct tagAPARTMENT {
LPMESSAGEFILTER filter; /* message filter */
XOBJECT *objs; /* exported objects */
struct list proxies; /* imported objects */
LPUNKNOWN state; /* state object (see Co[Get,Set]State) */
LPVOID ErrorInfo; /* thread error info */
DWORD listenertid; /* id of apartment_listener_thread */
struct list stubmgrs; /* stub managers for exported objects */
} APARTMENT;
};
extern APARTMENT MTA, *apts;
typedef struct apartment APARTMENT;
extern void* StdGlobalInterfaceTable_Construct(void);
extern void StdGlobalInterfaceTable_Destroy(void* self);
@ -196,26 +192,40 @@ int WINAPI FileMonikerImpl_DecomposePath(LPCOLESTR str, LPOLESTR** stringTable);
HRESULT WINAPI __CLSIDFromStringA(LPCSTR idstr, CLSID *id);
/* compobj.c */
APARTMENT *COM_CreateApartment(DWORD model);
APARTMENT *COM_ApartmentFromOXID(OXID oxid, BOOL ref);
DWORD COM_ApartmentAddRef(struct apartment *apt);
DWORD COM_ApartmentRelease(struct apartment *apt);
extern CRITICAL_SECTION csApartment;
/* this is what is stored in TEB->ReservedForOle */
struct oletls
{
struct apartment *apt;
IErrorInfo *errorinfo; /* see errorinfo.c */
IUnknown *state; /* see CoSetState */
};
/*
* Per-thread values are stored in the TEB on offset 0xF80,
* see http://www.microsoft.com/msj/1099/bugslayer/bugslayer1099.htm
*/
static inline APARTMENT* COM_CurrentInfo(void)
/* will create if necessary */
static inline struct oletls *COM_CurrentInfo(void)
{
APARTMENT* apt = NtCurrentTeb()->ReservedForOle;
return apt;
}
static inline APARTMENT* COM_CurrentApt(void)
{
APARTMENT* apt = COM_CurrentInfo();
if (apt && apt->parent) apt = apt->parent;
return apt;
if (!NtCurrentTeb()->ReservedForOle)
NtCurrentTeb()->ReservedForOle = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(struct oletls));
return NtCurrentTeb()->ReservedForOle;
}
/* compobj.c */
APARTMENT *COM_CreateApartment(DWORD model);
HWND COM_GetApartmentWin(OXID oxid);
APARTMENT *COM_ApartmentFromOXID(OXID oxid);
static inline APARTMENT* COM_CurrentApt(void)
{
return COM_CurrentInfo()->apt;
}
#define ICOM_THIS_MULTI(impl,field,iface) impl* const This=(impl*)((char*)(iface) - offsetof(impl,field))

View File

@ -20,7 +20,7 @@
* NOTES:
*
* The errorinfo is a per-thread object. The reference is stored in the
* TEB at offset 0xf80
* TEB at offset 0xf80.
*/
#include <stdarg.h>
@ -483,20 +483,20 @@ HRESULT WINAPI CreateErrorInfo(ICreateErrorInfo **pperrinfo)
*/
HRESULT WINAPI GetErrorInfo(ULONG dwReserved, IErrorInfo **pperrinfo)
{
APARTMENT * apt = COM_CurrentInfo();
TRACE("(%ld, %p, %p)\n", dwReserved, pperrinfo, COM_CurrentInfo()->ErrorInfo);
TRACE("(%ld, %p, %p)\n", dwReserved, pperrinfo, COM_CurrentInfo()->errorinfo);
if(!pperrinfo) return E_INVALIDARG;
if (!apt || !apt->ErrorInfo)
if (!COM_CurrentInfo()->errorinfo)
{
*pperrinfo = NULL;
return S_FALSE;
}
*pperrinfo = (IErrorInfo*)(apt->ErrorInfo);
*pperrinfo = COM_CurrentInfo()->errorinfo;
/* clear thread error state */
apt->ErrorInfo = NULL;
COM_CurrentInfo()->errorinfo = NULL;
return S_OK;
}
@ -506,18 +506,16 @@ HRESULT WINAPI GetErrorInfo(ULONG dwReserved, IErrorInfo **pperrinfo)
HRESULT WINAPI SetErrorInfo(ULONG dwReserved, IErrorInfo *perrinfo)
{
IErrorInfo * pei;
APARTMENT * apt = COM_CurrentInfo();
TRACE("(%ld, %p)\n", dwReserved, perrinfo);
if (!apt) apt = COM_CreateApartment(COINIT_UNINITIALIZED);
/* release old errorinfo */
pei = (IErrorInfo*)apt->ErrorInfo;
if(pei) IErrorInfo_Release(pei);
pei = COM_CurrentInfo()->errorinfo;
if (pei) IErrorInfo_Release(pei);
/* set to new value */
apt->ErrorInfo = perrinfo;
if(perrinfo) IErrorInfo_AddRef(perrinfo);
COM_CurrentInfo()->errorinfo = perrinfo;
if (perrinfo) IErrorInfo_AddRef(perrinfo);
return S_OK;
}

View File

@ -109,9 +109,13 @@ static HRESULT register_ifstub(wine_marshal_id *mid, IUnknown *obj, IRpcStubBuff
}
else
{
struct apartment *apt;
TRACE("constructing new stub manager\n");
manager = new_stub_manager(COM_ApartmentFromOXID(mid->oxid), obj);
apt = COM_ApartmentFromOXID(mid->oxid, TRUE);
manager = new_stub_manager(apt, obj);
COM_ApartmentRelease(apt);
if (!manager) return E_OUTOFMEMORY;
if (!tablemarshal) stub_manager_ref(manager, 1);

View File

@ -308,8 +308,6 @@ PipeBuf_Release(LPRPCCHANNELBUFFER iface) {
if (ref)
return ref;
FIXME("Free all stuff\n");
memcpy(&header.mid, &This->mid, sizeof(wine_marshal_id));
pipe = PIPE_FindByMID(&This->mid);
@ -892,7 +890,7 @@ static DWORD WINAPI apartment_listener_thread(LPVOID param)
HANDLE listenPipe;
APARTMENT *apt = (APARTMENT *) param;
/* we must join the marshalling threads apartment */
/* we must join the marshalling threads apartment. we already have a ref here */
NtCurrentTeb()->ReservedForOle = apt;
sprintf(pipefn,OLESTUBMGR"_%08lx%08lx", (DWORD)(apt->oxid >> 32), (DWORD)(apt->oxid));

View File

@ -41,6 +41,7 @@
WINE_DEFAULT_DEBUG_CHANNEL(ole);
/* this refs the apartment on success, otherwise there is no ref */
struct stub_manager *new_stub_manager(APARTMENT *apt, IUnknown *object)
{
struct stub_manager *sm;
@ -71,6 +72,7 @@ struct stub_manager *new_stub_manager(APARTMENT *apt, IUnknown *object)
list_add_head(&apt->stubmgrs, &sm->entry);
LeaveCriticalSection(&apt->cs);
COM_ApartmentAddRef(apt);
TRACE("Created new stub manager (oid=%s) at %p for object with IUnknown %p\n", wine_dbgstr_longlong(sm->oid), sm, object);
return sm;
@ -82,7 +84,7 @@ struct stub_manager *get_stub_manager_from_object(OXID oxid, void *object)
struct list *cursor;
APARTMENT *apt;
if (!(apt = COM_ApartmentFromOXID(oxid)))
if (!(apt = COM_ApartmentFromOXID(oxid, TRUE)))
{
WARN("Could not map OXID %s to apartment object\n", wine_dbgstr_longlong(oxid));
return NULL;
@ -101,6 +103,8 @@ struct stub_manager *get_stub_manager_from_object(OXID oxid, void *object)
}
LeaveCriticalSection(&apt->cs);
COM_ApartmentRelease(apt);
TRACE("found %p from object %p\n", result, object);
return result;
@ -112,14 +116,13 @@ struct stub_manager *get_stub_manager(OXID oxid, OID oid)
struct list *cursor;
APARTMENT *apt;
if (!(apt = COM_ApartmentFromOXID(oxid)))
if (!(apt = COM_ApartmentFromOXID(oxid, TRUE)))
{
WARN("Could not map OXID %s to apartment object\n", wine_dbgstr_longlong(oxid));
return NULL;
}
EnterCriticalSection(&apt->cs);
LIST_FOR_EACH( cursor, &apt->stubmgrs )
{
struct stub_manager *m = LIST_ENTRY( cursor, struct stub_manager, entry );
@ -130,9 +133,10 @@ struct stub_manager *get_stub_manager(OXID oxid, OID oid)
break;
}
}
LeaveCriticalSection(&apt->cs);
COM_ApartmentRelease(apt);
TRACE("found %p from oid %s\n", result, wine_dbgstr_longlong(oid));
return result;