mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1363 lines
38 KiB
1363 lines
38 KiB
/*++
|
|
|
|
copyright (c) 1992 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
chancont.cxx
|
|
|
|
Abstract:
|
|
|
|
This module contains thread switching code for the single threaded mode
|
|
and the message filter hooks
|
|
|
|
Author:
|
|
|
|
Alex Mitchell
|
|
|
|
Revision History:
|
|
|
|
Mar 1994 JohannP Added call category support.
|
|
29 Dec 1993 Alex Mitchell Creation.
|
|
19 Jul 1994 CraigWi Added support for ASYNC calls
|
|
27-Jan-95 BruceMa Don't get on CChannelControl list unless
|
|
constructor is successsful
|
|
|
|
Functions:
|
|
|
|
--*/
|
|
|
|
#include <ole2int.h>
|
|
|
|
#include <chancont.hxx>
|
|
#include <channelb.hxx>
|
|
#include <threads.hxx>
|
|
#include <objerror.h>
|
|
#include <callmain.hxx>
|
|
|
|
// Name of window class and message class for dispatching messages.
|
|
#define CHANNEL_WINDOW_CLASS L"OleObjectRpcWindow"
|
|
|
|
|
|
/***************************************************************************/
|
|
/* Globals. */
|
|
|
|
// Windows identifiers needed to post to the messsage queue.
|
|
ATOM ChannelClass = 0;
|
|
|
|
// Window message id used by channel in the channel window class.
|
|
UINT channel_message = WM_USER;
|
|
UINT channel_message_send = WM_USER+1;
|
|
UINT channel_message_done = WM_USER+2;
|
|
|
|
// Rpc worker thread cache.
|
|
CRpcThreadCache RpcThreadCache;
|
|
|
|
// Event cache.
|
|
CEventCache EventCache;
|
|
|
|
// List of channel controllers.
|
|
CChannelControl *CChannelControl::_pChanContRoot = NULL;
|
|
COleStaticMutexSem CChannelControl::lock;
|
|
CChannelControl *ProcessChannelControl = NULL;
|
|
|
|
// caller-side ctor for packet;can be used for GetOffCOMThread or SwithCOMThread
|
|
STHREADCALLINFO::STHREADCALLINFO(TRANSMIT_FN fnTrans,
|
|
CALLCATEGORY callcat, DWORD tid)
|
|
{
|
|
// filter.lid set by GetOffCOMThread and SwitchCOMThread.
|
|
filter.id = CALLDATAID_UNUSED;
|
|
filter.TIDCallee= tid;
|
|
filter.CallCat = callcat;
|
|
filter.Event = NULL;
|
|
filter.fDirectedYield = FALSE;
|
|
// filter.pRpcMsg set by caller if needed via ???
|
|
fnDispatch = fnTrans;
|
|
pServer = NULL;
|
|
fLocal = FALSE;
|
|
eState = in_progress_cs;
|
|
// hResult set later
|
|
}
|
|
|
|
|
|
// server-side ctor (a.k.a. recipient side); used for GetToCOMThread.
|
|
STHREADCALLINFO::STHREADCALLINFO(DISPATCH_FN fnDisp,
|
|
CALLCATEGORY callcat, REFLID lid)
|
|
{
|
|
filter.lid = lid;
|
|
// filter.id not set; only used on the transmit side (by the call control)
|
|
// filter.TIDCallee not set; only used on the transmit side
|
|
// (for RetryRejectedCall)
|
|
filter.CallCat = callcat;
|
|
filter.Event = NULL;
|
|
filter.fDirectedYield = FALSE;
|
|
// filter.pRpcMsg set by caller if needed via ???
|
|
fnDispatch = fnDisp;
|
|
pServer = NULL;
|
|
fLocal = FALSE;
|
|
eState = in_progress_cs;
|
|
// hResult set later
|
|
}
|
|
|
|
|
|
// virtual destructor
|
|
STHREADCALLINFO::~STHREADCALLINFO()
|
|
{
|
|
if (filter.Event != NULL)
|
|
EventCache.Free(filter.Event);
|
|
if (pServer != NULL)
|
|
pServer->Release();
|
|
}
|
|
|
|
|
|
// called to make a copy of the packet in the newly allocated memory; ptciNew
|
|
// has only been allocated, not initialized; this avoids redundant code.
|
|
// the original packet is not modified. This can only be called on the
|
|
// receiving side of the call (pServer != NULL).
|
|
STHREADCALLINFO *STHREADCALLINFO::MakeAsyncCopy(STHREADCALLINFO *ptciNew)
|
|
{
|
|
Win4Assert(ptciNew != NULL); // must have new one to fill in
|
|
Win4Assert(IsValidInterface(pServer)); // must be on receiving side
|
|
|
|
// the following is like a ctor on the receving (dispatch) side,
|
|
// but more efficient in the copy case
|
|
ptciNew->filter.lid = filter.lid;
|
|
// filter.id set by call control
|
|
// filter.TIDCallee not set ???
|
|
ptciNew->filter.CallCat = filter.CallCat;
|
|
ptciNew->filter.Event = NULL;
|
|
ptciNew->filter.fDirectedYield = FALSE;
|
|
ptciNew->filter.pRpcMsg = filter.pRpcMsg;
|
|
ptciNew->fnDispatch = fnDispatch;
|
|
ptciNew->pServer = pServer;
|
|
pServer->AddRef();
|
|
ptciNew->fLocal = fLocal;
|
|
ptciNew->eState = eState;
|
|
// hResult set later
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
// convert this packet into a reply packet that indicates success;
|
|
// returns FALSE if OOM. Most of the work done by the derived classes.
|
|
BOOL STHREADCALLINFO::FormulateAsyncReply()
|
|
{
|
|
hResult = S_OK;
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/***************************************************************************/
|
|
STDMETHODIMP CChannelControl::QueryInterface( THIS_ REFIID riid, LPVOID FAR* ppvObj)
|
|
{
|
|
if (IsEqualIID(riid, IID_IUnknown)
|
|
// || IsEqualIID(riid, IID_IChannelControl)
|
|
)
|
|
{
|
|
*ppvObj = (IChannelControl *) this;
|
|
}
|
|
else
|
|
{
|
|
*ppvObj = NULL;
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
AddRef();
|
|
return S_OK;
|
|
}
|
|
|
|
/***************************************************************************/
|
|
STDMETHODIMP_(ULONG) CChannelControl::Release( THIS )
|
|
{
|
|
ULONG retval = ref_count - 1;
|
|
|
|
if (InterlockedDecrement( (long*) &ref_count ) == 0)
|
|
{
|
|
delete this;
|
|
return 0;
|
|
}
|
|
else
|
|
return retval;
|
|
}
|
|
|
|
/***************************************************************************/
|
|
STDMETHODIMP_(ULONG) CChannelControl::AddRef( THIS )
|
|
{
|
|
InterlockedIncrement( (long *) &ref_count );
|
|
return ref_count;
|
|
}
|
|
|
|
|
|
/***************************************************************************/
|
|
// executed on client thread (in local case) and RPC thread (in remote case);
|
|
// posts a message to the server thread, guarding against disconnected channels
|
|
HRESULT CChannelControl::ProtectedPostToCOMThread(STHREADCALLINFO *call)
|
|
{
|
|
CairoleDebugOut((DEB_CHANNEL, "ProtectedPostToCOMThread hWnd:%x pCall:%x\n",
|
|
ChannelWindow, call));
|
|
|
|
HRESULT result;
|
|
|
|
// NOTE: this lock is on the server's channel control, not the client's;
|
|
// in the apartment model this makes a difference because the state we
|
|
// are checking is in the server channel control.
|
|
|
|
lock.Request();
|
|
if (state == cool_ccs)
|
|
{
|
|
if (PostMessage(ChannelWindow, channel_message, 0, (DWORD)call))
|
|
result = S_OK;
|
|
else
|
|
result = RPC_E_SYS_CALL_FAILED;
|
|
}
|
|
else
|
|
result = RPC_E_SERVER_DIED_DNE;
|
|
lock.Release();
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/***************************************************************************/
|
|
STDMETHODIMP_(void) CChannelControl::Cancel( STHREADCALLINFO **call )
|
|
{
|
|
DWORD result;
|
|
|
|
// If the call is still in progress, change it to canceled.
|
|
lock.Request();
|
|
if ((*call)->eState == in_progress_cs)
|
|
(*call)->eState = canceled_cs;
|
|
lock.Release();
|
|
|
|
// If the call completed before it could be canceled, wait for it to
|
|
// signal the completion event and clean up.
|
|
if ((*call)->eState == done_cs)
|
|
{
|
|
if (IsWOWThread() && ((*call)->fLocal))
|
|
// cant cancel inputsync calls
|
|
{
|
|
// the completion routine will have posted a message instead of
|
|
// setting an event, so we have to mark it as canceled and cleanup
|
|
// when the Reply msg comes in.
|
|
(*call)->eState = canceled_cs;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// A call that completed in TransmitCall (ie, didn't create an event)
|
|
// cannot be canceled.
|
|
|
|
Win4Assert( (*call)->filter.Event != NULL );
|
|
result = WaitForSingleObject((*call)->filter.Event, INFINITE);
|
|
Win4Assert( result == WAIT_OBJECT_0 );
|
|
|
|
delete *call;
|
|
}
|
|
}
|
|
|
|
// Null the STHREADCALLINFO pointer so no one tries to access it.
|
|
*call = NULL;
|
|
}
|
|
|
|
/***************************************************************************/
|
|
/*
|
|
This routine is called by the OLE Worker thread on the client side,
|
|
by the RPC worker thread on the server side for remote calls, and
|
|
by ThreadWndProc for local calls on the server side.
|
|
|
|
For the client case, it calls the dispatch routine which will send the
|
|
the data over to the server side.
|
|
This routine notifies the COM thread when the call is complete. If the
|
|
call is canceled before completion, the routine cleans up.
|
|
|
|
CODEWORK: the ThreadDispatch call could be reimplented to be faster
|
|
since there are various constraints on its operation (as
|
|
asserted in threads.cxx).
|
|
*/
|
|
/* static */
|
|
void CChannelControl::ThreadDispatch( STHREADCALLINFO **ppcall,
|
|
BOOL dispatch )
|
|
{
|
|
STHREADCALLINFO *pcall = *ppcall;
|
|
|
|
// Dispatch the call.
|
|
if (dispatch)
|
|
pcall->hResult = pcall->fnDispatch( pcall );
|
|
|
|
// Change the state to done; we cheat on non-local, recipient side since
|
|
// there is only one thread accessing the channel control; no need to
|
|
// lock and no need to check for cancel since it can't happen.
|
|
if (pcall->pServer == NULL || pcall->fLocal)
|
|
{
|
|
// sender or local case; use lock in case other thread accesses it
|
|
lock.Request();
|
|
if (pcall->eState == in_progress_cs)
|
|
pcall->eState = done_cs;
|
|
lock.Release();
|
|
}
|
|
else
|
|
{
|
|
// non-local recipient; just set to done and skip the next test
|
|
Win4Assert(pcall->eState == in_progress_cs);
|
|
pcall->eState = done_cs;
|
|
goto Done;
|
|
}
|
|
|
|
// If the call completed, wake up the client COM thread.
|
|
if (pcall->eState == done_cs)
|
|
{
|
|
Done:
|
|
if (pcall->filter.Event != NULL)
|
|
{
|
|
if (!IsWOWThread() || (!pcall->fLocal && pcall->pServer) ||
|
|
pcall->filter.CallCat == CALLCAT_INPUTSYNC ||
|
|
pcall->filter.CallCat == CALLCAT_INTERNALINPUTSYNC)
|
|
{
|
|
// 32bit always uses events for notification
|
|
// remote server side calls always use events
|
|
// client side INPUTSYNC always uses events
|
|
|
|
// someone waiting (e.g., not a SendMessage-type call)
|
|
CairoleDebugOut((DEB_CHANNEL,"SetEvent pInfo:%x hEvent:%x\n",
|
|
pcall, pcall->filter.Event));
|
|
SetEvent( pcall->filter.Event );
|
|
//
|
|
// We know that the other thread is waiting to run at this
|
|
// point. If we yield here, then the other thread will be
|
|
// able to return to our client. This sleep of zero will give up
|
|
// the rest of our timeslice, and allow the other thread
|
|
// to run.
|
|
//
|
|
Sleep(0);
|
|
|
|
}
|
|
else
|
|
{
|
|
// NOTE NOTE NOTE NOTE NOTE NOTE NOTE
|
|
// 16bit OLE used to do PostMessage for the Reply; we
|
|
// tried using SetEvent (which is faster) but this caused
|
|
// compatibility problems for applications which had bugs that
|
|
// were hidden by the 16bit OLE DLLs because messages happened
|
|
// to be dispatched in a particular order (see NtBug 21616 for
|
|
// an example). To retain the old behavior, we do a
|
|
// PostMessage here.
|
|
|
|
CairoleDebugOut((DEB_CHANNEL,
|
|
"PostMessage Reply hWnd:%x pCall:%x hEvent:%x\n",
|
|
pcall->hWndCaller, pcall, pcall->filter.Event));
|
|
|
|
Verify(PostMessage(pcall->hWndCaller,
|
|
channel_message_done, 0, (DWORD)pcall));
|
|
}
|
|
|
|
// pcall likely invalid here as other thread probably deleted it
|
|
}
|
|
else if (pcall->pServer != NULL && pcall->filter.CallCat == CALLCAT_ASYNC)
|
|
{
|
|
// async call and on recipient side, free packet (no one waiting)
|
|
delete pcall;
|
|
*ppcall = NULL;
|
|
}
|
|
}
|
|
|
|
// If the call was canceled, clean up.
|
|
else
|
|
{
|
|
// can only cancel when on client side or local call
|
|
Win4Assert(pcall->pServer == NULL || pcall->fLocal);
|
|
|
|
delete pcall;
|
|
*ppcall = NULL;
|
|
}
|
|
}
|
|
|
|
/***************************************************************************/
|
|
STDMETHODIMP CChannelControl::DispatchCall( PDISPATCHDATA data )
|
|
{
|
|
STHREADCALLINFO *call = (STHREADCALLINFO *) data->pData;
|
|
return call->fnDispatch( call );
|
|
}
|
|
|
|
/***************************************************************************/
|
|
STDMETHODIMP CChannelControl::OnEvent( PCALLDATA data )
|
|
{
|
|
STHREADCALLINFO *call = STHREADCALLINFO::MapCDToTCI(data);
|
|
SERVERCALLEX type;
|
|
|
|
// NOTE: the event used to be freed here; it is now freed in the dtor
|
|
// of STHREADCALLINFO.
|
|
|
|
HRESULT hr = call->hResult;
|
|
|
|
// CODEWORK: just return the error and let the callcontrol set its
|
|
// own state...eliminates a callback
|
|
|
|
if (hr == RPC_E_SERVERCALL_RETRYLATER)
|
|
type = SERVERCALLEX_RETRYLATER;
|
|
else if (hr == RPC_E_SERVERCALL_REJECTED)
|
|
type = SERVERCALLEX_REJECTED;
|
|
else
|
|
{
|
|
type = SERVERCALLEX_ISHANDLED;
|
|
hr = S_OK;
|
|
}
|
|
|
|
return _pCallControl->SetCallState(data, type, hr);
|
|
}
|
|
|
|
/***************************************************************************/
|
|
/* This function is static. */
|
|
CChannelControl *CChannelControl::Lookup( HAPT apt )
|
|
{
|
|
CChannelControl *pChannelControl;
|
|
|
|
lock.Request();
|
|
pChannelControl = _pChanContRoot;
|
|
if (pChannelControl != NULL && !FreeThreading)
|
|
{
|
|
do
|
|
{
|
|
if (pChannelControl->_dwMyThreadId == apt.dwThreadId)
|
|
break;
|
|
pChannelControl = pChannelControl->_pChanContNext;
|
|
}
|
|
while (pChannelControl != _pChanContRoot);
|
|
if (pChannelControl->_dwMyThreadId != apt.dwThreadId &&
|
|
apt.dwThreadId != ANY_APT.dwThreadId)
|
|
pChannelControl = NULL;
|
|
}
|
|
|
|
if (pChannelControl != NULL)
|
|
pChannelControl->AddRef();
|
|
lock.Release();
|
|
|
|
return pChannelControl;
|
|
}
|
|
|
|
|
|
/***************************************************************************/
|
|
/*
|
|
Return a failure code to indicate that no event will ever be set.
|
|
Then the message filter will pass the result back to the channel controller
|
|
who will set up the STHREADCALLINFO. Return S_OK if the event will be
|
|
signalled some time. In that case you must set the error fields of
|
|
STHREADCALLINFO before returning.
|
|
|
|
Note that the CallControl knows about the input_sync case. In that
|
|
case we call SendMessage which blocks. There is never an event in the
|
|
input_sync case.
|
|
*/
|
|
STDMETHODIMP CChannelControl::TransmitCall( PCALLDATA pdata )
|
|
{
|
|
STHREADCALLINFO *call = STHREADCALLINFO::MapCDToTCI(pdata);
|
|
HRESULT result = S_OK;
|
|
BOOL set_call_state = TRUE;
|
|
SERVERCALLEX type = SERVERCALLEX_ERROR;
|
|
|
|
if (FreeThreading)
|
|
{
|
|
call->hResult = call->fnDispatch( call );
|
|
type = SERVERCALLEX_ISHANDLED;
|
|
}
|
|
|
|
// Dispatch directly to the server thread.
|
|
else if (call->fLocal)
|
|
{
|
|
if (call->filter.CallCat == CALLCAT_INPUTSYNC ||
|
|
call->filter.CallCat == CALLCAT_INTERNALINPUTSYNC)
|
|
{
|
|
// Send the message.
|
|
if (state == cool_ccs)
|
|
{
|
|
// On CoUninitialize this may fail when the window is destroyed.
|
|
if (SendMessage(call->pServer->ChannelWindow,
|
|
channel_message_send, 0, (DWORD) call))
|
|
{
|
|
if (call->hResult == RPC_E_SERVERCALL_RETRYLATER)
|
|
type = SERVERCALLEX_RETRYLATER;
|
|
else if (call->hResult == RPC_E_SERVERCALL_REJECTED)
|
|
type = SERVERCALLEX_REJECTED;
|
|
else
|
|
type = SERVERCALLEX_ISHANDLED;
|
|
}
|
|
else
|
|
{
|
|
result = RPC_E_SERVER_DIED;
|
|
}
|
|
}
|
|
else
|
|
result = RPC_E_SERVER_DIED_DNE;
|
|
}
|
|
else if (call->filter.CallCat == CALLCAT_ASYNC)
|
|
{
|
|
// async call; copy message, post message and return.
|
|
// NOTE that the FreeThreading case is caught above. Async
|
|
// support is only for single-threaded apps.
|
|
|
|
STHREADCALLINFO *callT;
|
|
if ((callT = call->MakeAsyncCopy(NULL)) == NULL)
|
|
{
|
|
result = RPC_E_OUT_OF_RESOURCES;
|
|
}
|
|
else if (!call->FormulateAsyncReply())
|
|
{
|
|
delete callT;
|
|
result = RPC_E_OUT_OF_RESOURCES;
|
|
}
|
|
else
|
|
{
|
|
result = callT->pServer->ProtectedPostToCOMThread(callT);
|
|
|
|
if (result == S_OK)
|
|
{
|
|
// post succeeded; will be dispatched and freed;
|
|
// set call state to indicate a successful call;
|
|
// simulate overall success (normally set by ThreadDispatch);
|
|
// FormulateAsyncReply setup the buffer for successful return
|
|
// including setting hResult to S_OK.
|
|
type = SERVERCALLEX_ISHANDLED;
|
|
}
|
|
else
|
|
{
|
|
// result and type already set; delete copy of message
|
|
delete callT;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Get an event from the cache.
|
|
result = call->AllocEvent();
|
|
if (result == S_OK)
|
|
{
|
|
// Post a message to server
|
|
result = call->pServer->ProtectedPostToCOMThread(call);
|
|
|
|
if (result == S_OK)
|
|
{
|
|
// post successful, but call not complete; don't set call state;
|
|
// call->hResult not set also
|
|
set_call_state = FALSE;
|
|
if (IsWOWThread())
|
|
{
|
|
if (call->filter.TIDCallee == 0)
|
|
{
|
|
// This happens when there is a call made by internal
|
|
// RPC. We therefore get the thread id from the
|
|
// window that we just posted the message so we can
|
|
//yield to the correct thread.
|
|
call->filter.TIDCallee = GetWindowThreadProcessId(
|
|
call->pServer->ChannelWindow, NULL);
|
|
}
|
|
|
|
call->filter.fDirectedYield = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (call->filter.CallCat == CALLCAT_ASYNC)
|
|
{
|
|
// inter-proceess async call; make rpc call to other process (which will
|
|
// post a message) directly on this thread. This is done to avoid any
|
|
// processing of incoming calls. This call has no event to signal and
|
|
// can not be canceled (this might change in the network case).
|
|
// Much like the free threading case above.
|
|
call->hResult = call->fnDispatch( call );
|
|
type = SERVERCALLEX_ISHANDLED;
|
|
}
|
|
|
|
// Get a RPC thread to do the work.
|
|
else
|
|
{
|
|
// dispatch to a worker thread to make the call
|
|
result = call->AllocEvent();
|
|
if (result == S_OK)
|
|
{
|
|
result = RpcThreadCache.Dispatch( call );
|
|
set_call_state = FALSE;
|
|
}
|
|
}
|
|
|
|
if (set_call_state)
|
|
_pCallControl->SetCallState( &call->filter, type, result );
|
|
return result;
|
|
}
|
|
|
|
/***************************************************************************/
|
|
/* The result of this routine indicates comm status. This routine throws
|
|
exceptions to indicate fault status. If FreeThreading is false, a
|
|
thread_switch_data record is used to communicate with the thread doing the
|
|
work. That thread will return both kinds of results in the record.
|
|
If a request comes in, this routine will call AppInvoke which will catch
|
|
all exceptions and return both types as its result. If FreeThreading is
|
|
TRUE, this routine just calls I_RpcSendReceive. For now, the result of
|
|
I_RpcSendReceive will always be treated as a comm status (and thus just
|
|
returned). Someday, I_RpcSendReceive will indicate server faults which
|
|
this routine can throw.
|
|
*/
|
|
HRESULT CChannelControl::GetOffCOMThread( STHREADCALLINFO **call )
|
|
{
|
|
TRACECALL(TRACE_RPC, "CChannelControl::GetOffCOMThread");
|
|
|
|
HRESULT result;
|
|
IID *logical_thread;
|
|
CChannelControl *pChannelControl;
|
|
RPC_STATUS status;
|
|
|
|
// assert initialized correctly.
|
|
Win4Assert((*call)->pServer == NULL && !(*call)->fLocal);
|
|
Win4Assert((*call)->filter.Event == NULL);
|
|
|
|
// Find the channel controller for this thread.
|
|
GetLocalChannelControl( pChannelControl );
|
|
if (pChannelControl)
|
|
{
|
|
(*call)->hWndCaller = pChannelControl->ChannelWindow;
|
|
|
|
// Generate a new logical thread for async calls.
|
|
if ((*call)->GetCallCat() == CALLCAT_ASYNC)
|
|
{
|
|
status = UuidCreate(&(*call)->filter.lid);
|
|
if (status != RPC_S_OK && status != RPC_S_UUID_LOCAL_ONLY)
|
|
return HRESULT_FROM_WIN32( status );
|
|
}
|
|
|
|
// Find the logical thread id.
|
|
else
|
|
{
|
|
logical_thread = TLSGetLogicalThread();
|
|
if (logical_thread)
|
|
(*call)->filter.lid = *logical_thread;
|
|
else
|
|
return RPC_E_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
CairoleDebugOut((DEB_CHANNEL,
|
|
"GetOffCOMThread hWnd:%x pCall:%x hEvent:%x\n",
|
|
(*call)->hWndCaller, (*call), (*call)->filter.Event));
|
|
|
|
// Let the modal loop transmit the call and wait for the reply.
|
|
result = pChannelControl->_pCallControl->CallRunModalLoop( &(*call)->filter );
|
|
if (result == S_OK)
|
|
result = (*call)->hResult;
|
|
else if (result == RPC_E_CALL_CANCELED)
|
|
Cancel( call );
|
|
}
|
|
|
|
// Can't get channel controller.
|
|
else
|
|
{
|
|
result = RPC_E_THREAD_NOT_INIT;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/***************************************************************************/
|
|
/* Really, I'm static. No, really. */
|
|
HRESULT CChannelControl::GetToCOMThread( HAPT apt, STHREADCALLINFO *call )
|
|
{
|
|
CChannelControl *pChannelControl;
|
|
HRESULT result;
|
|
|
|
pChannelControl = Lookup( apt );
|
|
if (pChannelControl != NULL)
|
|
{
|
|
result = pChannelControl->GetToCOMThread( call );
|
|
pChannelControl->Release();
|
|
}
|
|
else
|
|
{
|
|
result = RPC_E_SERVER_DIED_DNE;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/***************************************************************************/
|
|
HRESULT CChannelControl::GetToCOMThread( STHREADCALLINFO *call )
|
|
{
|
|
TRACECALL(TRACE_RPC, "CChannelControl::GetToCOMThread");
|
|
|
|
CairoleDebugOut((DEB_CHANNEL, "GetToCOMThread pCall:%x\n", call));
|
|
|
|
HRESULT result;
|
|
|
|
Win4Assert(call->pServer == NULL && !call->fLocal);
|
|
Win4Assert(call->filter.Event == NULL);
|
|
|
|
// ctor sets pServer to NULL; we need one; will be released in dtor
|
|
call->pServer = this;
|
|
AddRef();
|
|
|
|
if (FreeThreading)
|
|
{
|
|
// In the multithreaded case, just call the dispatch function
|
|
// directly from this thread.
|
|
if (TLSSetLogicalThread(call->filter.lid))
|
|
result = call->fnDispatch( call );
|
|
else
|
|
result = RPC_E_SYS_CALL_FAILED;
|
|
}
|
|
else if ( call->filter.CallCat == CALLCAT_INPUTSYNC
|
|
|| call->filter.CallCat == CALLCAT_INTERNALINPUTSYNC)
|
|
{
|
|
// input synchronous call, send a message
|
|
if (state == cool_ccs)
|
|
{
|
|
// On CoUninitialize this may fail when the window is destroyed.
|
|
if (SendMessage(ChannelWindow, channel_message_send, 0, (DWORD) call))
|
|
result = call->hResult;
|
|
else
|
|
result = RPC_E_SERVER_DIED;
|
|
}
|
|
else
|
|
{
|
|
result = RPC_E_SERVER_DIED_DNE;
|
|
}
|
|
}
|
|
else if ( call->filter.CallCat == CALLCAT_ASYNC)
|
|
{
|
|
// async call; copy message, post message and return.
|
|
// NOTE that the FreeThreading case is caught above. Async
|
|
// support is only for single-threaded apps.
|
|
|
|
STHREADCALLINFO *callT;
|
|
if ((callT = call->MakeAsyncCopy(NULL)) == NULL)
|
|
{
|
|
result = RPC_E_OUT_OF_RESOURCES;
|
|
}
|
|
else if (!call->FormulateAsyncReply())
|
|
{
|
|
delete callT;
|
|
result = RPC_E_OUT_OF_RESOURCES;
|
|
}
|
|
else
|
|
{
|
|
// Post a message and wait for the app to get back to GetMessage.
|
|
result = ProtectedPostToCOMThread(callT);
|
|
|
|
if (result == S_OK)
|
|
{
|
|
// post succeeded; will be dispatched and freed
|
|
// fnAsyncCopy setup the buffer for successful return
|
|
}
|
|
else
|
|
{
|
|
// error in posting; free packet and return error (result set above)
|
|
delete callT;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Get this thread's event. May cause an event to be created.
|
|
result = call->AllocEvent();
|
|
if (result == S_OK)
|
|
{
|
|
result = ProtectedPostToCOMThread(call);
|
|
|
|
if (result == S_OK)
|
|
{
|
|
// Wait for the app to finish processing the request.
|
|
if (WaitForSingleObject(call->filter.Event, INFINITE) == WAIT_OBJECT_0)
|
|
result = call->hResult;
|
|
else
|
|
result = RPC_E_SYS_CALL_FAILED;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/***************************************************************************/
|
|
/* Really, I'm static. No, really. */
|
|
HRESULT CChannelControl::SwitchCOMThread( HAPT apt, STHREADCALLINFO **call )
|
|
{
|
|
CChannelControl *pChannelControl;
|
|
HRESULT result;
|
|
|
|
pChannelControl = Lookup( apt );
|
|
if (pChannelControl != NULL)
|
|
{
|
|
result = pChannelControl->SwitchCOMThread( call );
|
|
pChannelControl->Release();
|
|
}
|
|
else
|
|
{
|
|
result = RPC_E_SERVER_DIED_DNE;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/***************************************************************************/
|
|
HRESULT CChannelControl::SwitchCOMThread( STHREADCALLINFO **call )
|
|
{
|
|
TRACECALL(TRACE_RPC, "CChannelControl::SwitchCOMThread");
|
|
|
|
HRESULT result;
|
|
IID *logical_thread;
|
|
CChannelControl *pChannelControl;
|
|
|
|
Win4Assert((*call)->pServer == NULL && !(*call)->fLocal);
|
|
Win4Assert((*call)->filter.Event == NULL);
|
|
|
|
Win4Assert( !FreeThreading );
|
|
|
|
// Generate a new logical thread for async calls.
|
|
if ((*call)->GetCallCat() == CALLCAT_ASYNC)
|
|
{
|
|
RPC_STATUS status = UuidCreate(&(*call)->filter.lid);
|
|
if (status != RPC_S_OK && status != RPC_S_UUID_LOCAL_ONLY)
|
|
return HRESULT_FROM_WIN32( status );
|
|
}
|
|
|
|
// Find the logical thread id.
|
|
else
|
|
{
|
|
logical_thread = TLSGetLogicalThread();
|
|
if (logical_thread)
|
|
(*call)->filter.lid = *logical_thread;
|
|
else
|
|
return RPC_E_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
// set fields we need different than ctor
|
|
(*call)->fLocal = TRUE;
|
|
(*call)->pServer = this;
|
|
AddRef(); // will be released in dtor
|
|
|
|
// Call the message filter for this thread, not the server's thread.
|
|
pChannelControl = (CChannelControl *) TLSGetChannelControl();
|
|
if (pChannelControl != NULL)
|
|
{
|
|
(*call)->hWndCaller = pChannelControl->ChannelWindow;
|
|
|
|
CairoleDebugOut((DEB_CHANNEL,
|
|
"SwitchCOMThread hWnd:%x pCall:%x hEvent:%x\n",
|
|
(*call)->hWndCaller, (*call), (*call)->filter.Event));
|
|
|
|
result = pChannelControl->_pCallControl->CallRunModalLoop( &(*call)->filter );
|
|
if (result == S_OK)
|
|
result = (*call)->hResult;
|
|
else if (result == RPC_E_CALL_CANCELED)
|
|
Cancel( call );
|
|
}
|
|
else
|
|
result = RPC_E_THREAD_NOT_INIT;
|
|
|
|
// NOTE: Event and pServer are cleaned up by dtor of STHREADCALLINFO.
|
|
return result;
|
|
}
|
|
|
|
/***************************************************************************/
|
|
/* This routine is only called if FreeThreading is false. AppInvoke will
|
|
catch all exceptions and return both comm status and server faults in
|
|
its result. This routine stuffs the result of comm status into a
|
|
thread_switch_data record that is passed back to ThreadInvoke on another
|
|
thread. */
|
|
|
|
LRESULT ThreadWndProc(HWND window, UINT message, WPARAM unused, LPARAM params)
|
|
{
|
|
if (message == channel_message ||
|
|
message == channel_message_send)
|
|
{
|
|
STHREADCALLINFO *call = (STHREADCALLINFO *) params;
|
|
CairoleDebugOut((DEB_CHANNEL, "ThreadWndProc: Incoming Call pCall:%x\n", call));
|
|
|
|
// If the server isn't taking calls, just fail this one.
|
|
if (call->pServer->state != cool_ccs)
|
|
{
|
|
call->hResult = RPC_E_SERVER_DIED_DNE;
|
|
|
|
// Wake up the caller; ThreadDispatch takes care of the two main
|
|
// cases: local (pServer != NULL and fLocal == TRUE) and server
|
|
// side non-local (pServer != NULL and fLocal == FALSE). It also
|
|
// handles the orthoginal cases of send message (filter.Event == NULL)
|
|
// and async (CallCat == CALLCAT_ASYNC and pServer != NULL).
|
|
|
|
// In the server-side non-local case, we assert that the call is not
|
|
// canceled. Canceling is handled only on the client side or local cases.
|
|
|
|
CChannelControl::ThreadDispatch( &call, FALSE );
|
|
}
|
|
|
|
// This server is running, dispatch the call.
|
|
else
|
|
{
|
|
// Set the logical thread id. Note this cant fail because we
|
|
// are on the app main thread and we pre-allocated the TLS
|
|
// in CoInitialize and the uuid allocation can't fail.
|
|
IID *threadid_ptr = TLSGetLogicalThread();
|
|
Win4Assert(threadid_ptr && "TLSGetLogicalThread failed.");
|
|
Win4Assert( !FreeThreading );
|
|
|
|
// save the original threadid & copy in the new one.
|
|
UUID saved_threadid = *threadid_ptr;
|
|
*threadid_ptr = call->filter.lid;
|
|
|
|
// Dispatch all calls through ThreadDispatch. Local calls may be
|
|
// canceled. Server-side, non-local calls cannot be canceled. Send
|
|
// message calls (filter.Event == NULL) are handled as well.
|
|
CChannelControl::ThreadDispatch( &call, TRUE );
|
|
|
|
// restore the original thread id.
|
|
*threadid_ptr = saved_threadid;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
else if (message == channel_message_done)
|
|
{
|
|
// call completed - only happens InWow()
|
|
STHREADCALLINFO *call = (STHREADCALLINFO *) params;
|
|
CairoleDebugOut((DEB_CHANNEL, "ThreadWndProc: Call Completed hWnd:%x pCall:%x\n", window, call));
|
|
|
|
if (call->eState == canceled_cs)
|
|
{
|
|
// canceled, throw it away
|
|
delete call;
|
|
}
|
|
else if (call->filter.Event)
|
|
{
|
|
// mark the call as done
|
|
((CChannelControl *) TLSGetChannelControl())->OnEvent( &call->filter );
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
// Otherwise let the default window procedure have the message.
|
|
return DefWindowProc( window, message, unused, params );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************/
|
|
CChannelControl::CChannelControl( HRESULT *result )
|
|
{
|
|
ORIGINDATA OriginDataQ;
|
|
|
|
state = bummin_ccs;
|
|
ref_count = 1;
|
|
*result = RPC_E_OUT_OF_RESOURCES;
|
|
_dwMyThreadId = GetCurrentThreadId();
|
|
|
|
|
|
if (!FreeThreading)
|
|
{
|
|
|
|
// Create hidden channel window.
|
|
ChannelWindow = CreateWindowEx(0,
|
|
(LPCWSTR) ChannelClass,
|
|
L"OLEChannelWnd",
|
|
// must use WS_POPUP so the window does not get
|
|
// assigned a hot key by user.
|
|
(WS_DISABLED | WS_POPUP),
|
|
CW_USEDEFAULT,
|
|
CW_USEDEFAULT,
|
|
CW_USEDEFAULT,
|
|
CW_USEDEFAULT,
|
|
NULL,
|
|
NULL,
|
|
g_hinst,
|
|
NULL );
|
|
if (!ChannelWindow)
|
|
{
|
|
CairoleDebugOut((DEB_ERROR,"CreateWindowEx failed in CChannelControl constructor\n"));
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ChannelWindow = NULL;
|
|
}
|
|
|
|
// Get a message filter.
|
|
if (FreeThreading)
|
|
OriginDataQ.CallOrigin = CALLORIGIN_RPC32_MULTITHREAD;
|
|
else
|
|
OriginDataQ.CallOrigin = CALLORIGIN_RPC32_APARTMENT;
|
|
|
|
OriginDataQ.pChCont = this;
|
|
OriginDataQ.hwnd = ChannelWindow;
|
|
OriginDataQ.wFirstMsg = channel_message;
|
|
OriginDataQ.wLastMsg = channel_message_done;
|
|
|
|
*result = CoGetCallControl( &OriginDataQ, &_pCallControl );
|
|
|
|
_pCMC = GetCallMainControlForThread();
|
|
|
|
if (SUCCEEDED(*result) && _pCMC)
|
|
{
|
|
// pre-allocate thread local storage so we dont have to test this on
|
|
// every dispatch later on. Don't preallocate the logical
|
|
// thread id itself since that casuses some a hang on Cairo startup;
|
|
// the allocation of the logical thread id (UuidCreate) can't fail.
|
|
|
|
COleTls tls(*result);
|
|
if (SUCCEEDED(*result))
|
|
{
|
|
// Everything was successful so get on the ChannelController list
|
|
lock.Request();
|
|
if (_pChanContRoot == NULL)
|
|
_pChanContRoot = _pChanContPrev = _pChanContNext = this;
|
|
else
|
|
{
|
|
_pChanContNext = _pChanContRoot->_pChanContNext;
|
|
_pChanContPrev = _pChanContRoot;
|
|
_pChanContRoot->_pChanContNext->_pChanContPrev = this;
|
|
_pChanContRoot->_pChanContNext = this;
|
|
}
|
|
lock.Release();
|
|
|
|
// Report success
|
|
*result = S_OK;
|
|
state = cool_ccs;
|
|
}
|
|
}
|
|
}
|
|
|
|
/***************************************************************************/
|
|
CChannelControl::~CChannelControl()
|
|
{
|
|
// We should have changed state and been removed from the channel controller
|
|
// list in ThreadUnintialize.
|
|
Assert( state == bummin_ccs );
|
|
}
|
|
|
|
/***************************************************************************/
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CChannelControl::ThreadStop
|
|
//
|
|
// Synopsis: Per thread uninitialization
|
|
//
|
|
// Effects:
|
|
//
|
|
// Modifies:
|
|
//
|
|
// Algorithm:
|
|
//
|
|
// History: ??-???-?? ? Created
|
|
// 05-Jul-94 AlexT Separated thread and process uninit
|
|
//
|
|
// Notes: We are not holding the single thread mutex during this call
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
void CChannelControl::ThreadStop(void)
|
|
{
|
|
MSG msg;
|
|
BOOL got_quit = FALSE;
|
|
WPARAM quit_val;
|
|
|
|
// Change state and get off pChannelControl list.
|
|
lock.Request();
|
|
state = bummin_ccs;
|
|
if (_pChanContNext == this)
|
|
_pChanContNext = _pChanContPrev = _pChanContRoot = NULL;
|
|
else
|
|
{
|
|
_pChanContNext->_pChanContPrev = _pChanContPrev;
|
|
_pChanContPrev->_pChanContNext = _pChanContNext;
|
|
if (_pChanContRoot == this)
|
|
_pChanContRoot = _pChanContNext;
|
|
}
|
|
lock.Release();
|
|
|
|
// Wait for all current calls to complete.
|
|
if (!FreeThreading)
|
|
{
|
|
while( PeekMessage( &msg, ChannelWindow, channel_message,
|
|
channel_message_send, PM_REMOVE | PM_NOYIELD) )
|
|
{
|
|
if (msg.message == WM_QUIT)
|
|
{
|
|
got_quit = TRUE;
|
|
quit_val = msg.wParam;
|
|
}
|
|
else
|
|
DispatchMessage( &msg );
|
|
}
|
|
}
|
|
|
|
// Destroy the window. This will unblock any pending send messages.
|
|
if (ChannelWindow != NULL)
|
|
{
|
|
// This may fail if threads get terminated.
|
|
DestroyWindow( ChannelWindow );
|
|
ChannelWindow = 0;
|
|
}
|
|
|
|
// Release the call controller.
|
|
if (_pCallControl != NULL)
|
|
{
|
|
_pCallControl->Release();
|
|
_pCallControl = NULL;
|
|
}
|
|
|
|
if (got_quit)
|
|
PostQuitMessage( quit_val );
|
|
}
|
|
|
|
/***************************************************************************/
|
|
HRESULT ChannelThreadInitialize()
|
|
{
|
|
WNDCLASS stuff;
|
|
HRESULT result = S_OK;
|
|
CChannelControl *ChannelControl;
|
|
|
|
// Only get window stuff for the single threaded mode.
|
|
Win4Assert(!FreeThreading);
|
|
|
|
// Register windows class.
|
|
if (ChannelClass == 0)
|
|
{
|
|
stuff.style = 0;
|
|
stuff.lpfnWndProc = ThreadWndProc;
|
|
stuff.cbClsExtra = 0;
|
|
stuff.cbWndExtra = 0;
|
|
stuff.hInstance = g_hinst;
|
|
stuff.hIcon = NULL;
|
|
stuff.hCursor = NULL;
|
|
stuff.hbrBackground = (HBRUSH) (COLOR_BACKGROUND + 1);
|
|
stuff.lpszMenuName = NULL;
|
|
stuff.lpszClassName = CHANNEL_WINDOW_CLASS;
|
|
|
|
ChannelClass = RegisterClass( &stuff );
|
|
if (ChannelClass == 0)
|
|
{
|
|
// it is possible the dll got unloaded without us having called
|
|
// unregister so we call it here and try again.
|
|
UnregisterClass( CHANNEL_WINDOW_CLASS, g_hinst );
|
|
ChannelClass = RegisterClass( &stuff );
|
|
if ( ChannelClass == 0 )
|
|
{
|
|
CairoleDebugOut((DEB_ERROR,"RegisterClass failed in ChannelThreadInitialize\n"));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ChannelClass != 0)
|
|
{
|
|
// Create a class for message filter hooks.
|
|
// This is released in ThreadUninitialize.
|
|
ChannelControl = new CChannelControl( &result );
|
|
|
|
//
|
|
// BUGBUG - We leak memory in a strange case. If OLE32 is FreeLibrary'd
|
|
// then the ThreadUninitialize function is never called. This will cause
|
|
// us to leak a CChannelControl for every apartment.
|
|
//
|
|
if (ChannelControl)
|
|
{
|
|
if (SUCCEEDED(result))
|
|
{
|
|
if (TLSSetChannelControl( ChannelControl ))
|
|
return S_OK;
|
|
}
|
|
delete ChannelControl;
|
|
}
|
|
}
|
|
|
|
// If we get here, something failed. Our caller (CoInitializeEx) will
|
|
// call the necessary uninitialization routines on our behalf.
|
|
|
|
return CO_E_INIT_TLS_CHANNEL_CONTROL;
|
|
}
|
|
|
|
/***************************************************************************/
|
|
void ChannelControlThreadUninitialize()
|
|
{
|
|
CChannelControl *pChannelControl;
|
|
|
|
if (!FreeThreading)
|
|
{
|
|
// Find the channel controller for this thread. Stop it and release it.
|
|
pChannelControl = (CChannelControl *) TLSGetChannelControl();
|
|
if (pChannelControl != NULL)
|
|
{
|
|
TLSSetChannelControl( NULL );
|
|
pChannelControl->ThreadStop();
|
|
|
|
// This release matches the create in ChannelThreadInitialize.
|
|
pChannelControl->Release();
|
|
}
|
|
}
|
|
}
|
|
|
|
/***************************************************************************/
|
|
HRESULT ChannelControlProcessInitialize(void)
|
|
{
|
|
// initialize the event cache
|
|
EventCache.Initialize();
|
|
|
|
HRESULT result = S_OK;
|
|
|
|
// Get a channel controller the first time we are initialized in
|
|
// the multithreaded mode.
|
|
if (FreeThreading)
|
|
{
|
|
// Create a class for message filter hooks.
|
|
result = E_OUTOFMEMORY;
|
|
ProcessChannelControl = new CChannelControl( &result );
|
|
Win4Assert(ProcessChannelControl && "Could not allocate ChannelControl");
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/***************************************************************************/
|
|
void ChannelControlProcessUninitialize(void)
|
|
{
|
|
BOOL success;
|
|
|
|
if (!FreeThreading)
|
|
{
|
|
// When the process is stopping, tell RPC to stop listening.
|
|
StopListen();
|
|
|
|
// Free up the resources needed for single threading.
|
|
// Deregister the window class.
|
|
if (ChannelClass != 0)
|
|
{
|
|
success = UnregisterClass( CHANNEL_WINDOW_CLASS, g_hinst );
|
|
#if DBG == 1
|
|
if (!success)
|
|
CairoleDebugOut((DEB_ERROR, "Could not UnregisterClass 0x%x\n",
|
|
GetLastError()));
|
|
#endif
|
|
ChannelClass = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Assert(FreeThreading && "Bad threading logic");
|
|
|
|
if (ProcessChannelControl != NULL)
|
|
{
|
|
ProcessChannelControl->ThreadStop();
|
|
StopListen();
|
|
|
|
ProcessChannelControl->Release();
|
|
ProcessChannelControl = NULL;
|
|
}
|
|
}
|
|
|
|
// release all cached threads
|
|
RpcThreadCache.ClearFreeList();
|
|
|
|
// release cached events
|
|
EventCache.Cleanup();
|
|
}
|
|
|
|
/***************************************************************************/
|
|
void CEventCache::Initialize()
|
|
{
|
|
CLock lck(_EventLock);
|
|
|
|
memset(_list, 0, sizeof(_list));
|
|
_ifree = 0;
|
|
}
|
|
|
|
|
|
/***************************************************************************/
|
|
void CEventCache::Cleanup(void)
|
|
{
|
|
CLock lck(_EventLock);
|
|
|
|
if (_ifree < CEVENTCACHE_MAX_EVENT + 1)
|
|
{
|
|
while (_ifree > 0)
|
|
{
|
|
_ifree--; // decrement the index first!
|
|
Verify(CloseHandle(_list[_ifree]));
|
|
_list[_ifree] = NULL;
|
|
}
|
|
}
|
|
|
|
// set the index high so that if an event is returned to the cache
|
|
// after Cleanup, it gets Closed instead of lost in the list.
|
|
_ifree = CEVENTCACHE_MAX_EVENT + 1;
|
|
}
|
|
|
|
|
|
/***************************************************************************/
|
|
void CEventCache::Free( HANDLE event )
|
|
{
|
|
// Do nothing if there is no event.
|
|
if (event == NULL)
|
|
return;
|
|
|
|
CLock lck(_EventLock);
|
|
|
|
if (_ifree < CEVENTCACHE_MAX_EVENT)
|
|
{
|
|
// there is space, save this event.
|
|
|
|
#if DBG==1
|
|
// in debug, ensure slot is NULL
|
|
Win4Assert(_list[_ifree] == NULL && "Free: _list[_ifree] != NULL");
|
|
|
|
// enusre not already in the list
|
|
for (ULONG j=0; j<_ifree; j++)
|
|
{
|
|
Win4Assert(_list[j] != event && "Free: event already in cache!");
|
|
}
|
|
|
|
// ensure that the event is in the non-signalled state
|
|
Win4Assert(WaitForSingleObject(event, 0) == WAIT_TIMEOUT &&
|
|
"Free: Signalled event returned to cache!\n");
|
|
|
|
#endif
|
|
|
|
_list[_ifree] = event;
|
|
_ifree++;
|
|
}
|
|
else
|
|
{
|
|
// Otherwise really free it.
|
|
Verify(CloseHandle(event));
|
|
}
|
|
}
|
|
|
|
/***************************************************************************/
|
|
HANDLE CEventCache::Get()
|
|
{
|
|
HANDLE event = NULL;
|
|
|
|
Win4Assert(_ifree <= CEVENTCACHE_MAX_EVENT);
|
|
|
|
{
|
|
CLock lck(_EventLock);
|
|
|
|
// If there is an event in the cache, use it.
|
|
if (_ifree > 0)
|
|
{
|
|
_ifree--;
|
|
event = _list[_ifree];
|
|
#if DBG==1
|
|
// in debug, NULL the slot.
|
|
_list[_ifree] = NULL;
|
|
#endif
|
|
}
|
|
|
|
// lock goes out of scope at this point.
|
|
}
|
|
|
|
// Otherwise allocate a new one.
|
|
if (event == NULL)
|
|
#ifdef _CHICAGO_
|
|
event = CreateEventA( NULL, FALSE, FALSE, NULL );
|
|
#else //_CHICAGO_
|
|
event = CreateEvent( NULL, FALSE, FALSE, NULL );
|
|
#endif //_CHICAGO_
|
|
|
|
Win4Assert(event != NULL && "CEventCache:GetEvent returning NULL");
|
|
|
|
return event;
|
|
}
|