Windows NT 4.0 source code leak
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.
 
 
 
 
 
 

1098 lines
32 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 <userapis.h>
#include <chancont.hxx>
#include <channelb.hxx>
#include <threads.hxx>
#include <objerror.h>
#include <callctrl.hxx>
#include <service.hxx>
#include <ipidtbl.hxx>
/* Prototypes. */
void Cancel ( CChannelCallInfo ** );
HRESULT ModalLoop ( CChannelCallInfo *call );
HRESULT ProtectedPostToSTA( OXIDEntry *, CChannelCallInfo *call );
HRESULT TransmitCall( OXIDEntry *, CChannelCallInfo * );
/***************************************************************************/
/* Globals. */
// Rpc worker thread cache.
CRpcThreadCache gRpcThreadCache;
// Event cache.
CEventCache gEventCache;
HANDLE CEventCache::_list[] = {0,0,0,0,0,0,0,0};
DWORD CEventCache::_ifree = 0;
extern LPTSTR gOleWindowClass;
extern BOOL gfChannelProcessInitialized;
extern BOOL gfDestroyingMainWindow;
/***************************************************************************/
CChannelCallInfo::CChannelCallInfo( CALLCATEGORY callcat,
RPCOLEMESSAGE *message,
DWORD flags,
REFIPID ipidServer,
DWORD destctx,
CRpcChannelBuffer *channel,
DWORD authn_level )
{
// The call info must hold a reference to the channel on the client side
// because the channel holds the binding handle that ThreadSendReceive
// uses.
category = callcat;
event = NULL;
iFlags = flags;
eState = in_progress_cs;
pmessage = message;
ipid = ipidServer;
iDestCtx = destctx;
pNext = NULL;
pHeader = NULL;
pChannel = channel;
lSavedAuthnLevel = 0;
lAuthnLevel = authn_level;
if (pChannel != NULL)
pChannel->AddRef();
}
/***************************************************************************/
CChannelCallInfo::~CChannelCallInfo()
{
if (event != NULL)
gEventCache.Free(event);
// Release the reply buffer.
if (eState == canceled_cs && pmessage->Buffer != NULL)
DeallocateBuffer(pmessage);
// Release the channel.
if (pChannel != NULL)
pChannel->Release();
}
/***************************************************************************/
#if DBG==1
void DebugIsValidWindow(void *hWnd)
{
// USER could be out of memory and unable to validate the handle.
// GetDesktopWindow only returns NULL if USER is out of memory. So
// we only assert if USER is not out of memory and our window handle
// is invalid.
if (GetDesktopWindow() == NULL)
return;
Win4Assert( IsWindow((HWND) hWnd));
}
#else
inline void DebugIsValidWindow(void *hWnd) {}
#endif
/***************************************************************************/
void Cancel( CChannelCallInfo **call )
{
DWORD result;
// If the call is still in progress, change it to canceled.
LOCK
if ((*call)->eState == in_progress_cs)
(*call)->eState = canceled_cs;
UNLOCK
// If the call completed before it could be canceled, wait for it to
// signal the completion event and clean up.
if ((*call)->eState == server_done_cs || (*call)->eState == got_done_msg_cs)
{
(*call)->eState = canceled_cs;
if (IsWOWThread() && (*call)->Local())
{
// If the reply has arrived, the call can be deleted.
if ((*call)->eState == got_done_msg_cs)
{
delete *call;
}
// Otherwise
// 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.
return;
}
else
{
// A call that completed in TransmitCall (ie, didn't create an event)
// cannot be canceled.
Win4Assert( (*call)->event != NULL );
result = WaitForSingleObject((*call)->event, INFINITE);
Win4Assert( result == WAIT_OBJECT_0 );
delete *call;
}
}
// Null the CChannelCallInfo pointer so no one tries to access it.
*call = NULL;
}
/***************************************************************************/
HRESULT GetToSTA( OXIDEntry *pOxid, CChannelCallInfo *call )
{
TRACECALL(TRACE_RPC, "GetToSTA");
ComDebOut((DEB_CHANNEL, "GetToSTA pCall:%x\n", call));
gOXIDTbl.ValidateOXID();
ASSERT_LOCK_HELD
HRESULT result;
Win4Assert(call->event == NULL);
Win4Assert(pOxid->dwTid != GetCurrentThreadId());
// Don't accept calls if this thread has been uninitialized.
if (pOxid->dwFlags & OXIDF_STOPPED)
return RPC_E_SERVER_DIED_DNE;
if (call->category == CALLCAT_INPUTSYNC)
{
UNLOCK
ASSERT_LOCK_RELEASED
// On CoUninitialize this may fail when the window is destroyed.
// Pass the thread id to aid debugging.
SetLastError( 0 );
SendMessage((HWND)pOxid->hServerSTA, WM_OLE_ORPC_SEND,
GetCurrentThreadId(), (DWORD) call);
if (GetLastError() == 0)
result = call->hResult;
else
result = RPC_E_SERVER_DIED;
ASSERT_LOCK_RELEASED
LOCK
}
else if (call->category == CALLCAT_ASYNC)
{
// async call; copy message, post message and return.
// NOTE that in the MTA case, async was converted to SYNC by
// the call control.
CChannelCallInfo *copy = MakeAsyncCopy( call );
if (copy == NULL)
{
result = RPC_E_OUT_OF_RESOURCES;
}
else
{
// Post a message and wait for the app to get back to GetMessage.
result = ProtectedPostToSTA( pOxid, copy );
if (result != S_OK)
{
// error in posting; free packet and return error (result set above)
delete copy;
}
}
}
else
{
Win4Assert( call->category == CALLCAT_SYNCHRONOUS );
// Get completion event. May cause an event to be created.
result = gEventCache.Get( &call->event );
if (result == S_OK)
{
result = ProtectedPostToSTA( pOxid, call );
if (result == S_OK)
{
UNLOCK
ASSERT_LOCK_RELEASED
// Wait for the app to finish processing the request.
if (WaitForSingleObject(call->event, INFINITE) == WAIT_OBJECT_0)
result = call->hResult;
else
result = RPC_E_SYS_CALL_FAILED;
ASSERT_LOCK_RELEASED
LOCK
}
}
}
ASSERT_LOCK_HELD
gOXIDTbl.ValidateOXID();
return result;
}
/***************************************************************************/
HRESULT ModalLoop( CChannelCallInfo *pcall )
{
ASSERT_LOCK_RELEASED
DWORD result;
// we should only enter the modal loop for synchronous calls or input
// synchronous calls to another process or to an MTA apartment within
// the current process.
Win4Assert(pcall->category == CALLCAT_SYNCHRONOUS ||
pcall->category == CALLCAT_INPUTSYNC);
// detemine if we are using an event or a postmessage for the call
// completion signal. We use PostMessage only for process local
// calls in WOW, otherwise we use events and the OleModalLoop determines
// if the call completed or not.
BOOL fMsg = (pcall->Local() && IsWOWThread());
BOOL fWait = TRUE;
CAptCallCtrl *pACC = GetAptCallCtrl();
CCliModalLoop *pCML = pACC->GetTopCML();
ComDebOut((DEB_CALLCONT,"ModalLoop: wait on %s\n",(fMsg) ? "Msg" : "Event"));
// Wait at least once so the event is returned to the cache in the
// unsignalled state.
do
{
Win4Assert(fMsg || pcall->event);
result = OleModalLoopBlockFn(NULL, pCML, pcall->event);
if (fMsg)
{
if (result == RPC_E_CALL_CANCELED)
{
fWait = FALSE;
}
else
{
// loop until the call's state indicates the arrival of the
// reply message.
fWait = (pcall->eState != got_done_msg_cs);
result = S_OK;
}
}
else
{
// loop until the OleModalLoop tells us the call is no longer
// pending.
fWait = (result == RPC_S_CALLPENDING);
}
} while (fWait);
ASSERT_LOCK_RELEASED
return result;
}
#if DBG==1
/***************************************************************************/
LONG ProtectedPostExceptionFilter( DWORD lCode,
LPEXCEPTION_POINTERS lpep )
{
ComDebOut((DEB_ERROR, "Exception 0x%x in ProtectedPostToCOMThread at address 0x%x\n",
lCode, lpep->ExceptionRecord->ExceptionAddress));
DebugBreak();
return EXCEPTION_EXECUTE_HANDLER;
}
#endif // DBG
/***************************************************************************/
// executed on client thread (in local case) and RPC thread (in remote case);
// posts a message to the server thread, guarding against disconnected threads
HRESULT ProtectedPostToSTA( OXIDEntry *pOxid, CChannelCallInfo *call )
{
ComDebOut((DEB_CHANNEL, "ProtectedPostToSTA hWnd:%x pCall:%x\n",
pOxid->hServerSTA, call));
// ensure we are not posting to ourself and that the apartment is not
// an MTA apartment.
Win4Assert((pOxid->dwTid != GetCurrentThreadId()) &&
((pOxid->dwFlags & OXIDF_MTASERVER) == 0));
ASSERT_LOCK_HELD
HRESULT result;
if (!(pOxid->dwFlags & OXIDF_STOPPED))
{
#if DBG==1
DebugIsValidWindow(pOxid->hServerSTA);
_try
{
#endif
// Pass the thread id to aid debugging.
if (PostMessage((HWND)pOxid->hServerSTA, WM_OLE_ORPC_POST,
GetCurrentThreadId(), (DWORD)call))
result = S_OK;
else
result = RPC_E_SYS_CALL_FAILED;
#if DBG==1
}
_except( ProtectedPostExceptionFilter(GetExceptionCode(),
GetExceptionInformation()) )
{
}
Win4Assert( IsWindow((HWND) pOxid->hServerSTA) );
#endif
}
else
result = RPC_E_SERVER_DIED_DNE;
return result;
}
/***************************************************************************/
HRESULT SwitchSTA( OXIDEntry *pOxid, CChannelCallInfo **call )
{
TRACECALL(TRACE_RPC, "SwitchSTA");
ComDebOut((DEB_CHANNEL, "SwitchSTA hWnd:%x pCall:%x hEvent:%x\n",
(*call)->hWndCaller, (*call), (*call)->event));
gOXIDTbl.ValidateOXID();
ASSERT_LOCK_RELEASED
// Transmit the call.
HRESULT result = TransmitCall( pOxid, *call );
// the transmit was successful and the reply isn't already here so wait.
if (result == RPC_S_CALLPENDING)
{
// This is a single-threaded apartment so enter the modal loop.
result = ModalLoop( *call );
}
if (result == S_OK)
result = (*call)->hResult;
else if (result == RPC_E_CALL_CANCELED)
Cancel( call );
ASSERT_LOCK_RELEASED
gOXIDTbl.ValidateOXID();
ComDebOut((DEB_CHANNEL, "SwitchSTA hr:%x\n", result));
return result;
}
/***************************************************************************/
/*
This routine is called by the OLE Worker thread on the client side,
and by ThreadWndProc on the server side.
For the client case, it calls ThreadSendReceive 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.
*/
void ThreadDispatch( CChannelCallInfo **ppcall)
{
CChannelCallInfo *pcall = *ppcall;
gOXIDTbl.ValidateOXID();
// Dispatch the call.
if (pcall->edispatch == invoke_wd)
pcall->hResult = ComInvoke( pcall );
else
pcall->hResult = ThreadSendReceive( 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->edispatch == invoke_wd && !pcall->Local())
{
// non-local recipient; just set to done
Win4Assert(pcall->eState == in_progress_cs);
pcall->eState = server_done_cs;
}
else
{
// sender or local case; use lock in case other thread accesses it
LOCK
if (pcall->eState == in_progress_cs)
pcall->eState = server_done_cs;
UNLOCK
}
// If the call completed, wake up the waiting thread. For local calls
// the client thread is waiting. For remote calls the helper thread is
// waiting.
if (pcall->eState == server_done_cs)
{
// only need to wake somebody for synchronous calls
if (pcall->category == CALLCAT_SYNCHRONOUS ||
pcall->category == CALLCAT_INPUTSYNC)
{
// Don't do anything in an STA server for input synchronous
// calls since the other thread called here with SendMessage.
if (pcall->category == CALLCAT_SYNCHRONOUS ||
pcall->edispatch == sendreceive_wd ||
IsMTAThread())
{
if (!pcall->Local() || !IsWOWThread())
{
// remote calls (outside this process) always use events for
// notification. 32bit uses events for local calls too.
// someone waiting (e.g., not a SendMessage-type call)
ComDebOut((DEB_CHANNEL,"SetEvent pInfo:%x hEvent:%x\n",
pcall, pcall->event));
SetEvent( pcall->event );
}
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.
ComDebOut((DEB_CHANNEL,
"PostMessage Reply hWnd:%x pCall:%x hEvent:%x\n",
pcall->hWndCaller, pcall, pcall->event));
// Pass the thread id to aid debugging.
Verify(PostMessage(pcall->hWndCaller,
WM_OLE_ORPC_DONE,
GetCurrentThreadId(), (DWORD)pcall));
}
// pcall likely invalid here as other thread probably deleted it
}
}
// Must be asynchronous.
else if (pcall->edispatch == invoke_wd)
{
// async call and on recipient side, free packet (no one waiting)
Win4Assert( pcall->category == CALLCAT_ASYNC );
delete pcall;
*ppcall = NULL;
}
}
// If the call was canceled, clean up.
else
{
// can only cancel when on client side or local call
Win4Assert(pcall->edispatch == sendreceive_wd || pcall->Local());
delete pcall;
*ppcall = NULL;
}
gOXIDTbl.ValidateOXID();
}
//+-------------------------------------------------------------------------
//
// Member: ThreadStart
//
// Synopsis: Apartment model only. Setup the window used for MSWMSG,
// local thread switches and the call control.
//
// History: 08-02-95 Rickhi Created, from various pieces
//
//--------------------------------------------------------------------------
HRESULT ThreadStart(void)
{
Win4Assert(IsSTAThread());
HRESULT hr = S_OK;
RPC_STATUS sc;
LOCK // lock since GetLocalOXIDEntry expects it
OXIDEntry *pOxid = GetLocalOXIDEntry();
Win4Assert(pOxid != NULL); //already created so cant fail
UNLOCK
if (GetCurrentThreadId() == gdwMainThreadId && hwndOleMainThread != NULL)
{
// this is the main thread, we can just re-use the already
// existing gMainThreadWnd.
pOxid->hServerSTA = hwndOleMainThread;
}
else
{
// Create a new window for use by the current thread for the
// apartment model. The window is destroyed in ThreadStop.
Win4Assert(gOleWindowClass != NULL);
pOxid->hServerSTA = CreateWindowEx(0,
gOleWindowClass,
TEXT("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 (pOxid->hServerSTA)
{
DebugIsValidWindow(pOxid->hServerSTA);
// Override the window proc function
SetWindowLong((HWND)pOxid->hServerSTA, GWL_WNDPROC, (LONG)ThreadWndProc);
// get the local call control object, and register the
// the window with it. Note that it MUST exist cause we
// created it in ChannelThreadInitialize.
CAptCallCtrl *pCallCtrl = GetAptCallCtrl();
pCallCtrl->Register((HWND) pOxid->hServerSTA, WM_USER, 0x7fff );
}
else
{
hr = MAKE_SCODE(SEVERITY_ERROR, FACILITY_WIN32, GetLastError());
}
ComDebOut((DEB_CALLCONT, "ThreadStart returns %x\n", hr));
return hr;
}
//+-------------------------------------------------------------------------
//
// Member: ThreadCleanup
//
// Synopsis: Release the window for the thread.
//
//--------------------------------------------------------------------------
void ThreadCleanup()
{
LOCK
OXIDEntry *pOxid = GetLocalOXIDEntry();
UNLOCK
if (pOxid != NULL)
{
Win4Assert( (pOxid->dwFlags & OXIDF_MTASERVER) == 0 );
// Destroy the window. This will unblock any pending SendMessages.
if (pOxid->hServerSTA == hwndOleMainThread)
{
// restore the window proceedure
SetWindowLong(hwndOleMainThread, GWL_WNDPROC,
(LONG)OleMainThreadWndProc);
}
else
{
// This may fail if threads get terminated.
DestroyWindow((HWND) pOxid->hServerSTA);
}
pOxid->hServerSTA = NULL;
}
ComDebOut((DEB_CALLCONT, "ThreadCleanup called.\n"));
}
//+-------------------------------------------------------------------------
//
// Member: ThreadStop
//
// Synopsis: Per thread uninitialization
//
// History: ??-???-?? ? Created
// 05-Jul-94 AlexT Separated thread and process uninit
//
// Notes: We are not holding the single thread mutex during this call
//
//--------------------------------------------------------------------------
STDAPI_(void) ThreadStop(void)
{
LOCK
OXIDEntry *pOxid = GetLocalOXIDEntry();
if (pOxid != NULL)
{
// Change state
pOxid->dwFlags |= OXIDF_STOPPED;
}
UNLOCK
if (pOxid != NULL)
{
// Stop MSWMSG.
I_RpcServerStopListening();
if (pOxid->dwFlags & OXIDF_MTASERVER)
{
if (pOxid->cCalls != 0)
{
Win4Assert( pOxid->hComplete != NULL );
g_mxsSingleThreadOle.Release();
WaitForSingleObject( pOxid->hComplete, INFINITE );
g_mxsSingleThreadOle.Request();
// a new thread may have been initialized while we released
// the lock, so we cant assert that the cCalls is zero.
}
}
else
{
// Single-threaded apartment so wait for all current calls
// to complete.
ASSERT_LOCK_RELEASED
MSG msg;
BOOL got_quit = FALSE;
WPARAM quit_val;
while(PeekMessage(&msg, (HWND) pOxid->hServerSTA, WM_USER,
0x7fff, PM_REMOVE | PM_NOYIELD))
{
if (msg.message == WM_QUIT)
{
got_quit = TRUE;
quit_val = msg.wParam;
}
else
{
DispatchMessage(&msg);
}
}
if (got_quit)
{
PostQuitMessage( quit_val );
}
}
}
ComDebOut((DEB_CALLCONT, "ThreadStop called.\n"));
}
//+-------------------------------------------------------------------------
//
// Function: ThreadWndProc, Internal
//
// Synopsis: Dispatch COM windows messages. This routine is only called
// for Single-Threaded Apartments. It dispatches calls and call
// complete messages. If it does not recognize the message, it
// calls MSWMSG to dispatch it.
//
//--------------------------------------------------------------------------
LRESULT ThreadWndProc(HWND window, UINT message, WPARAM unused, LPARAM params)
{
Win4Assert(IsSTAThread());
if (message == WM_OLE_ORPC_POST ||
message == WM_OLE_ORPC_SEND)
{
ASSERT_LOCK_RELEASED
CChannelCallInfo *call = (CChannelCallInfo *) params;
ComDebOut((DEB_CHANNEL, "ThreadWndProc: Incoming Call pCall:%x\n", call));
// Dispatch all calls through ThreadDispatch. Local calls may be
// canceled. Server-side, non-local calls cannot be canceled. Send
// message calls (event == NULL) are handled as well.
call->edispatch = invoke_wd;
ThreadDispatch( &call );
ASSERT_LOCK_RELEASED
return 0;
}
else if (message == WM_OLE_ORPC_DONE)
{
ASSERT_LOCK_RELEASED
// call completed - only happens InWow()
CChannelCallInfo *call = (CChannelCallInfo *) params;
ComDebOut((DEB_CHANNEL, "ThreadWndProc: Call Completed hWnd:%x pCall:%x\n", window, call));
if (call->eState == canceled_cs)
{
// canceled, throw it away
delete call;
}
else
{
// Notify the modal loop that the call is complete.
call->eState = got_done_msg_cs;
}
ASSERT_LOCK_RELEASED
return 0;
}
else if (message == WM_OLE_ORPC_RELRIFREF)
{
ASSERT_LOCK_RELEASED
HandlePostReleaseRifRef(params);
ASSERT_LOCK_RELEASED
return 0;
}
else if (message == WM_OLE_GETCLASS)
{
return OleMainThreadWndProc(window, message, unused, params);
}
else
{
// when the window is first created we are holding the lock, and the
// creation of the window causes some messages to be dispatched.
ASSERT_LOCK_DONTCARE
// check if the window is being destroyed because of UninitMainWindow
// or because of system shut down. Only destroy it in the former case.
if ((message == WM_DESTROY || message == WM_CLOSE) &&
window == hwndOleMainThread &&
gfDestroyingMainWindow == FALSE)
{
ComDebOut((DEB_WARN, "Attempted to destroy window outside of UninitMainThreadWnd"));
return 0;
}
#ifdef _CHICAGO_
// Otherwise let the default window procedure have the message.
return DefWindowProc( window, message, unused, params );
#else
return I_RpcWindowProc( window, message, unused, params );
#endif
}
}
/***************************************************************************/
/*
Return S_OK if the call completed successfully.
Return RPC_S_CALL_PENDING if the caller should block.
Return an error if the call failed.
*/
HRESULT TransmitCall( OXIDEntry *pOxid, CChannelCallInfo *call )
{
ComDebOut((DEB_CHANNEL, "TransmitCall pCall:%x\n", call));
ASSERT_LOCK_RELEASED
BOOL fDispCall = FALSE;
BOOLEAN wait = FALSE;
HRESULT result;
// Don't touch the call hresult after the other thread starts,
// otherwise we might erase the results of the other thread.
// Since we never want signalled events returned to the cache, always
// wait on the event at least once. For example, the post message
// succeeds and the call completes immediately. Return RPC_S_CALLPENDING even
// though the call already has a S_OK in it.
if (call->Local())
{
// server is in this process.
if (!(pOxid->dwFlags & OXIDF_MTASERVER))
{
// server is in an STA apartment
if (call->category == CALLCAT_INPUTSYNC)
{
// Inputsync call. Send the message.
if (!(pOxid->dwFlags & OXIDF_STOPPED))
{
// On CoUninitialize this may fail when the window is destroyed.
// Pass the thread id to aid debugging.
SetLastError( 0 );
SendMessage((HWND)pOxid->hServerSTA, WM_OLE_ORPC_SEND,
GetCurrentThreadId(), (DWORD) call);
if (GetLastError() != 0)
{
call->hResult = RPC_E_SERVER_DIED;
}
}
else
{
call->hResult = RPC_E_SERVER_DIED_DNE;
}
}
else if (call->category == CALLCAT_ASYNC)
{
// Async call. Copy message, post message and return.
LOCK
CChannelCallInfo *copy = MakeAsyncCopy( call );
if (copy == NULL)
{
call->hResult = RPC_E_OUT_OF_RESOURCES;
}
else
{
call->hResult = ProtectedPostToSTA( pOxid, copy );
if (call->hResult != S_OK)
{
delete copy;
}
}
UNLOCK
}
else
{
// Sync call. Post the message and wait for a reply.
LOCK
Win4Assert(call->category == CALLCAT_SYNCHRONOUS);
call->hResult = S_OK;
if (!IsWOWThread())
{
// Get an event from the cache. In 32bit, replyies are done
// via Events, but for 16bit, repliest are done with PostMsg,
// so we dont need an event. Not having an event makes the
// callctrl modal loop a little faster.
call->hResult = gEventCache.Get( &call->event );
}
else
{
Win4Assert( GetLocalOXIDEntry() != NULL );
call->hWndCaller = (HWND) GetLocalOXIDEntry()->hServerSTA;
call->event = NULL;
}
if (call->hResult == S_OK)
{
// Post a message to server
call->hResult = RPC_S_CALLPENDING;
result = ProtectedPostToSTA( pOxid, call );
if (result != S_OK)
call->hResult = result;
else
wait = TRUE;
}
UNLOCK
}
}
else
{
// server is in an MTA apartment. Transmit the call by having
// a worker thread invoke the server directly. Async calls to
// a FT server are treated as SYNC calls and should have been
// converted by this point, so we never expect to see callcat
// ASYNC.
Win4Assert(call->category != CALLCAT_ASYNC);
wait = TRUE;
call->edispatch = invoke_wd;
fDispCall = TRUE;
}
}
else
{
// server is in a different process or on a different machine.
if (call->category == CALLCAT_ASYNC)
{
// For async calls to other local processes, just make an RPC call.
call->hResult = ThreadSendReceive(call);
}
else
{
// Get a worker thread to do the RPC call.
wait = TRUE;
call->edispatch = sendreceive_wd;
fDispCall = TRUE;
}
}
if (fDispCall)
{
// Dispatch to a worker thread to make the call
LOCK
call->hResult = gEventCache.Get( &call->event );
UNLOCK
if (call->hResult == S_OK)
{
call->hResult = RPC_S_CALLPENDING;
result = gRpcThreadCache.Dispatch( call );
if (result != S_OK)
{
call->hResult = result;
wait = FALSE;
}
}
}
ComDebOut((DEB_CHANNEL, "TransmitCall call->hResult:%x fWait:%x\n",
call->hResult, wait));
Win4Assert(wait || call->hResult != RPC_S_CALLPENDING);
return (wait) ? RPC_S_CALLPENDING : call->hResult;
}
//+-------------------------------------------------------------------------
//
// Member: CEventCache::Cleanup
//
// Synopsis: Empty the event cache
//
// Notes: This function must be thread safe because Canceled calls
// can complete at any time.
//
//--------------------------------------------------------------------------
void CEventCache::Cleanup(void)
{
ASSERT_LOCK_HELD
while (_ifree > 0)
{
_ifree--; // decrement the index first!
Verify(CloseHandle(_list[_ifree]));
_list[_ifree] = NULL; // NULL slot so we dont need to re-init
}
// reset the index to 0 so reinitialization is not needed
_ifree = 0;
}
//+-------------------------------------------------------------------------
//
// Member: CEventCache::Free
//
// Synopsis: returns an event to the cache if there are any available
// slots, frees the event if not.
//
// Notes: This function must be thread safe because Canceled calls
// can complete at any time.
//
//--------------------------------------------------------------------------
void CEventCache::Free( HANDLE hEvent )
{
// there better be an event
Win4Assert(hEvent != NULL);
LOCK
// dont return anything to the cache if the process is no longer init'd.
if (_ifree < CEVENTCACHE_MAX_EVENT && gfChannelProcessInitialized)
{
// 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] != hEvent && "Free: event already in cache!");
}
// ensure that the event is in the non-signalled state
Win4Assert(WaitForSingleObject(hEvent, 0) == WAIT_TIMEOUT &&
"Free: Signalled event returned to cache!\n");
#endif
_list[_ifree] = hEvent;
_ifree++;
}
else
{
// Otherwise really free it.
Verify(CloseHandle(hEvent));
}
UNLOCK
}
//+-------------------------------------------------------------------------
//
// Member: CEventCache::Get
//
// Synopsis: gets an event from the cache if there are any available,
// allocates one if not.
//
// Notes: This function must be thread safe because Canceled calls
// can complete at any time.
//
//--------------------------------------------------------------------------
HRESULT CEventCache::Get( HANDLE *hEvent )
{
ASSERT_LOCK_HELD
Win4Assert(_ifree <= CEVENTCACHE_MAX_EVENT);
if (_ifree > 0)
{
// there is an event in the cache, use it.
_ifree--;
*hEvent = _list[_ifree];
#if DBG==1
// in debug, NULL the slot.
_list[_ifree] = NULL;
#endif
return S_OK;
}
// no free event in the cache, allocate a new one.
#ifdef _CHICAGO_
*hEvent = CreateEventA( NULL, FALSE, FALSE, NULL );
#else //_CHICAGO_
*hEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
#endif //_CHICAGO_
if (*hEvent)
return S_OK;
Win4Assert(*hEvent != NULL && "CEventCache:GetEvent returning NULL");
return RPC_E_OUT_OF_RESOURCES;
}