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.
 
 
 
 
 
 

1820 lines
62 KiB

//+-------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1992 - 1993.
//
// File: callctrl.cxx
//
// Contents: Contains the ORPC CallControl code
//
// History: 21-Dec-93 Johannp Original Version
// 04-Nov-94 Rickhi ReWrite as layer over channel
//
//--------------------------------------------------------------------------
#include <ole2int.h>
#include <thkreg.h> // OLETHK_ defines
#include <dde.h>
#include <callctrl.hxx> // Class Definition
#include <objsrv.h> // IID_IObjServer
// private defines used only in this file
#define WM_SYSTIMER 0x0118
#define SYS_ALTDOWN 0x2000
#define WM_NCMOUSEFIRST WM_NCMOUSEMOVE
#define WM_NCMOUSELAST WM_NCMBUTTONDBLCLK
// empty slot in window registration
#define WD_EMPTY (HWND)-1
// the following table is used to quickly determine what windows
// message queue inputflag to specify for the various categories of
// outgoing calls in progress. The table is indexed by CALLCATEGORY.
DWORD gMsgQInputFlagTbl[4] = {
QS_ALLINPUT | QS_TRANSFER | QS_ALLPOSTMESSAGE, // NOCALL
QS_ALLINPUT | QS_TRANSFER | QS_ALLPOSTMESSAGE, // SYNCHRONOUS
QS_ALLINPUT | QS_TRANSFER | QS_ALLPOSTMESSAGE, // ASYNC
QS_SENDMESSAGE}; // INPUTSYNC
// the following table is used to map bit flags in the Rpc Message to
// the equivalent OLE CALLCATEGORY.
DWORD gRpcFlagToCallCatMap[3] = {
CALLCAT_SYNCHRONOUS, // no flags set
CALLCAT_INPUTSYNC, // RPCFLG_INPUT_SYNCHRONOUS
CALLCAT_ASYNC}; // RPCFLG_ASYNCHRONOUS
// prototype
HRESULT CopyMsgForRetry(RPCOLEMESSAGE *pMsg,
IRpcChannelBuffer *pChnl,
HRESULT hrIn);
//+-------------------------------------------------------------------------
//
// Function: CoRegisterMessageFilter, public
//
// Synopsis: registers an applications message filter with the call control
//
// Arguments: [pMsgFilter] - message filter to register
// [ppMsgFilter] - optional, where to return previous IMF
//
// Returns: S_OK - registered successfully
//
// History: 21-Dec-93 JohannP Created
//
//--------------------------------------------------------------------------
STDAPI CoRegisterMessageFilter(LPMESSAGEFILTER pMsgFilter,
LPMESSAGEFILTER *ppMsgFilter)
{
ComDebOut((DEB_MFILTER, "CoRegisterMessageFilter pMF:%x ppMFOld:%x\n",
pMsgFilter, ppMsgFilter));
CALLHOOKOBJECT(S_OK,CLSID_NULL,IID_IMessageFilter,(IUnknown **)&pMsgFilter);
// validate the parameters. NULL acceptable for either or both parameters.
if (pMsgFilter != NULL && !IsValidInterface(pMsgFilter))
{
return E_INVALIDARG;
}
if(ppMsgFilter != NULL && !IsValidPtrOut(ppMsgFilter, sizeof(ppMsgFilter)))
{
return E_INVALIDARG;
}
// this operation is not allowed on MTA Threads
if (IsMTAThread())
return CO_E_NOT_SUPPORTED;
// find the callcontrol for this apartment and replace the existing
// message filter. if no callctrl has been created yet, just stick
// the pMsgFilter in tls.
COleTls tls;
CAptCallCtrl *pACC = tls->pCallCtrl;
IMessageFilter *pOldMF;
if (pACC)
{
pOldMF = pACC->InstallMsgFilter(pMsgFilter);
}
else
{
pOldMF = tls->pMsgFilter;
if (pMsgFilter)
{
pMsgFilter->AddRef();
}
tls->pMsgFilter = pMsgFilter;
}
if (ppMsgFilter)
{
// return old MF to the caller
*ppMsgFilter = pOldMF;
}
else if (pOldMF)
{
// release the old MF
pOldMF->Release();
}
return S_OK;
}
//+-------------------------------------------------------------------------
//
// Method: CAptCallCtrl::InstallMsgFilter
//
// Synopsis: called to install a new application provided message filter
//
// Arguments: [pMF] - new message filter to install (or NULL)
//
// Returns: previous message filter if there was one
//
// History: 20-Dec-93 JohannP Created
//
//--------------------------------------------------------------------------
INTERNAL_(IMessageFilter *) CAptCallCtrl::InstallMsgFilter(IMessageFilter *pMF)
{
IMessageFilter *pMFOld = _pMF; // save the old one to return
_pMF = pMF; // install the new one
if (_pMF)
{
_pMF->AddRef();
}
return pMFOld;
}
//+-------------------------------------------------------------------------
//
// Method: CAptCallCtrl::CAptCallCtrl
//
// Synopsis: constructor for per apartment call control state
//
// History: 11-Nov-94 Rickhi Created
//
//--------------------------------------------------------------------------
CAptCallCtrl::CAptCallCtrl() :
_fInMsgFilter(FALSE),
_pTopCML(NULL)
{
// The first one is reserved for ORPC. An hWnd value of WD_EMPTY
// means the slot is available.
_WD[0].hWnd = WD_EMPTY;
// The second slot has fixed values for DDE
_WD[1].hWnd = NULL;
_WD[1].wFirstMsg = WM_DDE_FIRST;
_WD[1].wLastMsg = WM_DDE_LAST;
// put our pointer into thread local storage, and retrieve any previously
// registered message filter.
COleTls tls;
tls->pCallCtrl = this;
_pMF = tls->pMsgFilter;
tls->pMsgFilter = NULL;
}
//+-------------------------------------------------------------------------
//
// Method: CAptCallCtrl::~CAptCallCtrl
//
// Synopsis: destructor for per apartment call control state
//
// History: 11-Nov-94 Rickhi Created
//
//--------------------------------------------------------------------------
CAptCallCtrl::~CAptCallCtrl()
{
Win4Assert(_pTopCML == NULL); // no outgoing calls.
if (_pMF)
{
_pMF->Release();
}
// remove our pointer from thread local storage
COleTls tls;
tls->pCallCtrl = NULL;
}
//+-------------------------------------------------------------------------
//
// Method: CAptCallCtrl::Register/Revoke
//
// Synopsis: register or revoke RPC window data
//
// Arguments: [hWnd] - window handle to look for calls on
// [wFirstMsg] - msgid of first message in range to look for
// [wLastMsg] - msgid of last message in range to look for
//
// Returns: nothing
//
// Notes: This code is only ever called by the RpcChannel and by
// the DDE layer, and so error checking is kept to a minimum.
//
// History: 30-Apr-95 Rickhi Created
//
//--------------------------------------------------------------------------
void CAptCallCtrl::Register(HWND hWnd, UINT wFirstMsg, UINT wLastMsg)
{
Win4Assert(_WD[0].hWnd == WD_EMPTY && "Register Out of Space");
_WD[0].hWnd = hWnd;
_WD[0].wFirstMsg = wFirstMsg;
_WD[0].wLastMsg = wLastMsg;
}
void CAptCallCtrl::Revoke(HWND hWnd)
{
Win4Assert(_WD[0].hWnd == hWnd && "Revoke not found");
_WD[0].hWnd = WD_EMPTY;
}
//+-------------------------------------------------------------------------
//
// Function: GetSlowTimeFactor
//
// Synopsis: Get the time slowing factor for Wow apps
//
// Returns: The factor by which we need to slow time down.
//
// Algorithm: If there is a factor in the registry, we open and read the
// registry. Otherwise we just set it to the default.
//
// History: 22-Jul-94 Ricksa Created
// 09-Jun-95 Susia ANSI Chicago optimization
//
//--------------------------------------------------------------------------
#ifdef _CHICAGO_
#undef RegOpenKeyEx
#define RegOpenKeyEx RegOpenKeyExA
#undef RegQueryValueEx
#define RegQueryValueEx RegQueryValueExA
#endif
DWORD GetSlowTimeFactor(void)
{
// Default slowing time so we can just exit if there is no key which
// is assumed to be the common case.
DWORD dwSlowTimeFactor = OLETHK_DEFAULT_SLOWRPCTIME;
// Key for reading the value from the registry
HKEY hkeyOleThk;
// Get the Ole Thunk special value key
LONG lStatus = RegOpenKeyEx(HKEY_CLASSES_ROOT, OLETHK_KEY, 0, KEY_READ,
&hkeyOleThk);
if (lStatus == ERROR_SUCCESS)
{
DWORD dwType;
DWORD dwSizeData = sizeof(dwSlowTimeFactor);
lStatus = RegQueryValueEx(hkeyOleThk, OLETHK_SLOWRPCTIME_VALUE, NULL,
&dwType, (LPBYTE) &dwSlowTimeFactor, &dwSizeData);
if ((lStatus != ERROR_SUCCESS) || dwType != REG_DWORD)
{
// Guarantee that value is reasonable if something went wrong.
dwSlowTimeFactor = OLETHK_DEFAULT_SLOWRPCTIME;
}
// Close the key since we are done with it.
RegCloseKey(hkeyOleThk);
}
return dwSlowTimeFactor;
}
//+-------------------------------------------------------------------------
//
// Function: CanMakeOutCall
//
// Synopsis: called when the client app wants to make an outgoing call to
// determine if it is OK to do it now or not. Common subroutine
// to CAptRpcChnl::GetBuffer and RemoteReleaseRifRef.
//
// Arguments: [dwCallCatOut] - call category of call the app wants to make
// [pChnl] - ptr to channel call is being made on
// [riid] - interface call is being made on
//
// Returns: S_OK - ok to make the call
// RPC_E_CANTCALLOUT_INEXTERNALCALL - inside IMessageFilter
// RPC_E_CANTCALLOUT_INASYNCCALL - inside async call
// RPC_E_CANTCALLOUT_ININPUTSYNCCALL - inside input sync or SendMsg
//
// History: 21-Dec-93 Johannp Original Version
// 04-Nov-94 Rickhi ReWrite
// 03-Oct-95 Rickhi Made into common subroutine
//
//--------------------------------------------------------------------------
INTERNAL CanMakeOutCall(DWORD dwCallCatOut, REFIID riid)
{
// get the topmost incoming call state from Tls.
HRESULT hr;
COleTls tls(hr);
if (FAILED(hr))
return hr;
CSrvCallState *pSCS = tls->pTopSCS;
DWORD dwCallCatIn = (pSCS) ? pSCS->GetCallCatIn() : CALLCAT_NOCALL;
// if handling an incoming ASYNC call, only allow ASYNC outgoing calls,
// and local calls on IRemUnknown (which locally is actually IRundown).
if (dwCallCatIn == CALLCAT_ASYNC &&
dwCallCatOut != CALLCAT_ASYNC &&
!IsEqualGUID(riid, IID_IRundown))
{
return RPC_E_CANTCALLOUT_INASYNCCALL;
}
// if handling an incoming INPUTSYNC call, or if we are handling a
// SendMessage, dont allow SYNCHRONOUS calls out or we could deadlock
// since SYNC uses PostMessage and INPUTSYNC uses SendMessage.
if (dwCallCatOut == CALLCAT_SYNCHRONOUS &&
(dwCallCatIn == CALLCAT_INPUTSYNC || InSendMessage()))
{
return RPC_E_CANTCALLOUT_ININPUTSYNCCALL;
}
return S_OK;
}
//+-------------------------------------------------------------------------
//
// Method: CMTARpcChnl::CMTARpcChnl/~CMTARpcChnl
//
// Synopsis: constructor/destructor
//
// Parameters: [pStdId] - std identity for the object
// [pOXIDEntry] - OXIDEntry for the object server
// [eState] - state flags passed thru to CRpcChannelBuffer
// (ignored by CMTARpcCnl).
//
// History: 11-Nov-94 Rickhi Created
//
//--------------------------------------------------------------------------
CMTARpcChnl::CMTARpcChnl(CStdIdentity *pStdId,
OXIDEntry *pOXIDEntry,
DWORD eState) :
CRpcChannelBuffer(pStdId, pOXIDEntry, eState),
_dwTIDCallee(pOXIDEntry->dwTid),
_dwAptId(GetCurrentApartmentId())
{
ComDebOut((DEB_CALLCONT,"CMTARpcChnl::CMTARpcChnl this:%x\n", this));
}
CMTARpcChnl::~CMTARpcChnl()
{
ComDebOut((DEB_CALLCONT,"CMTARpcChnl::~CMTARpcChnl this:%x\n", this));
}
//+-------------------------------------------------------------------------
//
// Method: CMTARpcChnl::GetBuffer
//
// Synopsis: Ensure it is legal to call out now, then get a buffer.
//
// Parameters: [pMsg] - ptr to message structure
// [riid] - interface call is being made on
//
// History: 11-Nov-94 Rickhi Created
//
//--------------------------------------------------------------------------
STDMETHODIMP CMTARpcChnl::GetBuffer(RPCOLEMESSAGE *pMsg, REFIID riid)
{
HRESULT hr;
COleTls tls(hr); // use this form incase no calls made on this thread yet
if (FAILED(hr))
return hr;
if (!IsMTAThread())
{
ComDebOut((DEB_WARN,"CMTARpcChnl::GetBuffer - MTA proxy called on apartment thread, this: 0x%x\n",
this));
return RPC_E_WRONG_THREAD;
}
// Make sure we are allowed to make this outgoing call. We do that here
// so that we dont marshal all the parameters only to discover that we
// cant call out and then have to free all the marshalled parameters
// (especially the ones where marshalling has side effects).
if (!(_dwAptId == GetCurrentApartmentId() || CallableOnAnyApt()))
{
// we are not being called on a thread in the MTA apartment
return RPC_E_WRONG_THREAD;
}
if (pMsg->rpcFlags & RPCFLG_INPUT_SYNCHRONOUS)
{
// dont allow INPUTSYNC calls from an MTA apartment to anybody.
return RPC_E_CANTCALLOUT_ININPUTSYNCCALL;
}
// All ASYNC calls from an MTA apartment are treated as SYNCHRONOUS,
// so convert the call category here before proceeding.
pMsg->rpcFlags &= ~RPCFLG_ASYNCHRONOUS;
// ask the real channel for a buffer.
return CRpcChannelBuffer::ClientGetBuffer(pMsg, riid);
}
//+-------------------------------------------------------------------------
//
// Method: CAptRpcChnl::CAptRpcChnl/~CAptRpcChnl
//
// Synopsis: constructor/destructor
//
// Parameters: [pStdId] - std identity for the object
// [pOXIDEntry] - OXIDEntry for the object server
// [eState] - state flags passed thru to CRpcChannelBuffer
// (ignored by CAptRpcCnl).
//
// History: 11-Nov-94 Rickhi Created
//
//--------------------------------------------------------------------------
CAptRpcChnl::CAptRpcChnl(CStdIdentity *pStdId,
OXIDEntry *pOXIDEntry,
DWORD eState) :
CRpcChannelBuffer(pStdId, pOXIDEntry, eState),
_dwTIDCallee(pOXIDEntry->dwTid),
_dwAptId(GetCurrentApartmentId())
{
ComDebOut((DEB_CALLCONT,"CAptRpcChnl::CAptRpcChnl this:%x\n", this));
}
CAptRpcChnl::~CAptRpcChnl()
{
ComDebOut((DEB_CALLCONT,"CAptRpcChnl::~CAptRpcChnl this:%x\n", this));
}
//+-------------------------------------------------------------------------
//
// Method: CAptRpcChnl::GetBuffer
//
// Synopsis: Ensure it is legal to call out now, then get a buffer.
//
// Parameters: [pMsg] - ptr to message structure
// [riid] - interface call is being made on
//
// History: 11-Nov-94 Rickhi Created
//
//--------------------------------------------------------------------------
STDMETHODIMP CAptRpcChnl::GetBuffer(RPCOLEMESSAGE *pMsg, REFIID riid)
{
HRESULT hr;
COleTls tls(hr); // use this form incase no calls made on this thread yet
if (FAILED(hr))
return hr;
// first, make sure that we are being called on the correct thread
// so that we are sure tls->pCallCtrl is set.
if (!IsSTAThread() || !(_dwAptId == GetCurrentApartmentId() ||
CallableOnAnyApt()))
{
return RPC_E_WRONG_THREAD;
}
// dont allow the application to call out while handling an
// IMessageFilter call because it screws up the call sequencing.
CAptCallCtrl *pACC = tls->pCallCtrl;
if (pACC && pACC->InMsgFilter())
{
ComDebOut((DEB_ERROR, "Illegal callout from within IMessageFilter\n"));
return RPC_E_CANTCALLOUT_INEXTERNALCALL;
}
// if the call is async and remote, or async and to an MTA apartment,
// then change the category to sync, since we dont support async remotely
// or to MTA apartments locally. This must be done before calling
// CanMakeOutCall in order to avoid deadlocks. If the call is input sync
// and remote or to an MTA apartment, dissallow the call.
if (pMsg->rpcFlags & (RPCFLG_ASYNCHRONOUS | RPCFLG_INPUT_SYNCHRONOUS))
{
DWORD dwCtx;
CRpcChannelBuffer::GetDestCtx(&dwCtx, NULL);
if (dwCtx == MSHCTX_DIFFERENTMACHINE ||
(GetOXIDEntry()->dwFlags & OXIDF_MTASERVER))
{
if (pMsg->rpcFlags & RPCFLG_INPUT_SYNCHRONOUS)
return RPC_E_CANTCALLOUT_ININPUTSYNCCALL;
// turn off the async flag so that the call looks (and acts)
// like it is synchronous.
pMsg->rpcFlags &= ~RPCFLG_ASYNCHRONOUS;
}
}
// Make sure we are allowed to make this outgoing call. We do that here
// so that we dont marshal all the parameters only to discover that we
// cant call out and then have to free all the marshalled parameters
// (especially the ones where marshalling has side effects).
// figure out the call category of this call by looking at bit
// values in the rpc message flags.
DWORD dwCallCatOut = RpcFlagToCallCat(pMsg->rpcFlags);
// check other outgoing call restrictions common to multi and single
// threaded apartments
hr = CanMakeOutCall(dwCallCatOut, riid);
if (hr == S_OK)
{
// ask the real channel for a buffer.
hr = CRpcChannelBuffer::ClientGetBuffer(pMsg, riid);
}
return hr;
}
//+-------------------------------------------------------------------------
//
// Method: CAptRpcChnl::SendReceive
//
// Synopsis: instantiate a modal loop object and then transmit the call
//
// Parameters: [pMsg] - ptr to message structure
// [pulStatus] - place to return a status code
//
// History: 11-Nov-94 Rickhi Created
//
//--------------------------------------------------------------------------
STDMETHODIMP CAptRpcChnl::SendReceive(RPCOLEMESSAGE *pMsg, ULONG *pulStatus)
{
// Figure out the call category of this call by looking at the bit
// values in the rpc message flags.
DWORD dwCallCatOut = RpcFlagToCallCat(pMsg->rpcFlags);
DWORD dwMsgQInputFlag = gMsgQInputFlagTbl[dwCallCatOut];
// Now for a spectacular hack. IRemUnknown::Release had slightly
// different dwMsgQInputFlag semantic in the old code base, so we
// check for that one case here and set the flag accordingly. Not
// doing this would allow SYSCOMMAND calls in during Release which
// we throw away, thus preventing an app from shutting down correctly.
// SimpSvr.exe is a good example of this.
if ((pMsg->iMethod & ~RPC_FLAGS_VALID_BIT) == 5 &&
(IsEqualIID(IID_IRundown, *MSG_TO_IIDPTR(pMsg)) ||
IsEqualIID(IID_IRemUnknown, *MSG_TO_IIDPTR(pMsg))))
{
dwMsgQInputFlag = (QS_POSTMESSAGE | QS_SENDMESSAGE | QS_TRANSFER |
QS_ALLPOSTMESSAGE);
}
// Now construct a modal loop object for the call that is about to
// be made. It maintains the call state and exits when the call has
// been completed, cancelled, or rejected.
HRESULT hr;
CCliModalLoop CML(_dwTIDCallee, dwMsgQInputFlag);
do
{
hr = CML.SendReceive(pMsg, pulStatus, this);
if (hr == RPC_E_SERVERCALL_RETRYLATER)
{
// the call was rejected by the server and the client Msg Filter
// decided to retry the call. We have to make a copy of the
// message and re-send it.
hr = CopyMsgForRetry(pMsg);
}
else if (hr == RPC_E_CALL_REJECTED)
{
// the call was rejected by the server and the client Msg Filter
// decided NOT to retry the call. We have to free the buffer
// that was returned since the proxy is not expecting it.
FreeBuffer(pMsg);
}
} while (hr == RPC_E_SERVERCALL_RETRYLATER);
return hr;
}
//+-------------------------------------------------------------------------
//
// Method: CAptRpcChnl::CopyMsgForRetry
//
// Synopsis: Makes a copy of the message we sent. We have to ask Rpc
// for another buffer and then copy the original buffer into
// the new one so we can make another call.
//
// Parameters: [pMsg] - ptr to message structure to copy
//
// History: 11-Nov-94 Rickhi Created
//
//--------------------------------------------------------------------------
HRESULT CAptRpcChnl::CopyMsgForRetry(RPCOLEMESSAGE *pMsg)
{
ComDebOut((DEB_CALLCONT,"CAptRpcChnl::CopyMsgForRetry pMsg:%x\n", pMsg));
// CODEWORK: this is dumb, but the channel blows chunks in FreeBuffer
// if i dont do this double copy.
void *pTmpBuf = PrivMemAlloc(pMsg->cbBuffer);
if (pTmpBuf)
{
memcpy(pTmpBuf, pMsg->Buffer, pMsg->cbBuffer);
}
// save copy of the contents of the old message so we can free it later
HRESULT hr = E_OUTOFMEMORY;
RPCOLEMESSAGE MsgToFree = *pMsg;
FreeBuffer(&MsgToFree);
if (pTmpBuf)
{
// allocate a new message, dont have to worry about checking the
// CanMakeOutCall again, so we just ask the Rpc channel directly.
hr = CRpcChannelBuffer::GetBuffer(pMsg, *MSG_TO_IIDPTR(pMsg));
if (SUCCEEDED(hr))
{
// copy the temp buffer into the new buffer
memcpy(pMsg->Buffer, pTmpBuf, pMsg->cbBuffer);
hr = RPC_E_SERVERCALL_RETRYLATER;
}
PrivMemFree(pTmpBuf);
}
return hr;
}
//+-------------------------------------------------------------------------
//
// Member: CCliModalLoop::SendReceive
//
// Synopsis: called to transmit a call to the server and enter a modal
// loop.
//
// Arguments: [pMsg] - message to send
// [pulStatus] - place to return status code
// [pChnl] - IRpcChannelBuffer pointer
//
// Returns: result of the call. May return RETRYLATER if the call should
// be retransmitted.
//
// History: 11-Nov-94 Rickhi Created
//
//--------------------------------------------------------------------------
INTERNAL CCliModalLoop::SendReceive(RPCOLEMESSAGE *pMsg, ULONG *pulStatus,
IRpcChannelBuffer2 *pChnl)
{
// SendReceive is a blocking call. The channel will transmit the call
// asynchronously then call us back in BlockFn where we wait for an
// event such as the call completing, or a windows message arriving,
// or the user cancelling the call. Because of the callback, we need
// to set _hr before calling SR.
_hr = RPC_S_CALLPENDING;
_hr = pChnl->SendReceive2(pMsg, pulStatus);
// By this point the call has completed. Now check if it was rejected
// and if so, whether we need to retry immediately, later, or never.
// Handling of Rejected calls must occur here, not in the BlockFn, due
// to the fact that some calls and some protocols are synchronous, and
// other calls and protocols are asynchronous.
if (_hr == RPC_E_CALL_REJECTED || _hr == RPC_E_SERVERCALL_RETRYLATER)
{
// this function decides on 1 of 3 different courses of action
// 1. fail the call - sets the state to Call_Rejected
// 2. retry immediately - sets _hr to RETRYLATER, fall out
// 3. retry later - starts the timer, we block below
_hr = HandleRejectedCall(pChnl);
// if a timer was installed to retry the call later, then we have
// to go into modal loop until the timer expires. if the call is
// cancelled while in this loop, the loop will be exited.
while (!IsTimerAtZero())
{
BlockFn(NULL);
}
// Either it is time to retransmit the call, or the call was
// cancelled or rejected.
}
return _hr;
}
//+-------------------------------------------------------------------------
//
// Member: CCliModalLoop::HandleRejectedCall
//
// Synopsis: called when the response to a remote call is rejected or
// retry later.
//
// Arguments: [pChnl] - channel we are calling on.
//
// Returns: RPC_E_CALL_REJECTED - call is rejected
// RPC_E_SERVERCALL_RETRYLATER - the call should be retried
// (Timer is set if retry is to be delayed)
//
// Algorithm: Calls the app's message filter (if there is one) to
// determine whether the call should be failed, retried
// immediately, or retried at some later time. If there is
// no message filter, or the client is on a different machine,
// then the call is always rejected.
//
// History: 21-Dec-93 Johannp Created
// 30-Apr-95 Rickhi ReWrite
//
//--------------------------------------------------------------------------
INTERNAL CCliModalLoop::HandleRejectedCall(IRpcChannelBuffer2 *pChnl)
{
// default return value - rejected
DWORD dwRet = 0xffffffff;
DWORD dwDestCtx;
HRESULT hr = pChnl->GetDestCtx(&dwDestCtx, NULL);
if (SUCCEEDED(hr) && dwDestCtx != MSHCTX_DIFFERENTMACHINE)
{
// the call is local to this machine, ask the message filter
// what to do. For remote calls we never allow retry, since
// the parameters were not sent back to us in the packet.
IMessageFilter *pMF = _pACC->GetMsgFilter();
if (pMF)
{
ComDebOut((DEB_MFILTER,
"pMF->RetryRejectedCall(dwTIDCallee:%x ElapsedTime:%x Type:%x)\n",
_dwTIDCallee, GetElapsedTime(),
(_hr == RPC_E_CALL_REJECTED) ? SERVERCALL_REJECTED
: SERVERCALL_RETRYLATER));
dwRet = pMF->RetryRejectedCall((MF_HTASK)_dwTIDCallee, GetElapsedTime(),
(_hr == RPC_E_CALL_REJECTED) ? SERVERCALL_REJECTED
: SERVERCALL_RETRYLATER);
ComDebOut((DEB_MFILTER,"pMF->RetryRejected() dwRet:%x\n", dwRet));
_pACC->ReleaseMsgFilter();
}
}
if (dwRet == 0xffffffff)
{
// Really rejected. Mark it as such incase it was actually
// Call_RetryLater, also ensures that IsWaiting returns FALSE
return RPC_E_CALL_REJECTED;
}
else if (dwRet >= 100)
{
// Retry Later. Start the timer. This ensures that IsTimerAtZero
// returns FALSE and IsWaiting returns TRUE
return StartTimer(dwRet);
}
else
{
// Retry Immediately. The state is set so that IsTimerAtZero
// returns TRUE.
Win4Assert(IsTimerAtZero());
return RPC_E_SERVERCALL_RETRYLATER;
}
}
//+-------------------------------------------------------------------------
//
// Function: OleModalLoopBlockFn
//
// Synopsis: Called by the RpcChannel during an outgoing call while
// waiting for the reply message.
//
// Arguments: [pvWnd] - Window handle to expect the reply on
// [pvCtx] - Call Context (the CCliModalLoop)
// [hCallWaitEvent] - optional event to have CallControl wait on
//
// Returns: result of the call
//
// Algorithm: pvCtx is the topmost modal loop for the current apartment.
// Just call it's block function.
//
// History: Dec-93 JohannP Created
//
//--------------------------------------------------------------------------
RPC_STATUS OleModalLoopBlockFn(void *pvWnd, void *pvCtx, HANDLE hCallWaitEvent)
{
Win4Assert( pvCtx != NULL );
return ((CCliModalLoop *) pvCtx)->BlockFn(hCallWaitEvent);
}
//+-------------------------------------------------------------------------
//
// Member: CCliModalLoop::BlockFn (private)
//
// Synopsis: Implements the blocking part of the modal loop. This function
// blocks until an event of interest occurs, then it goes and
// processes that event and returns.
//
// Arguments: [hCallWaitEvent] - event to wait on (optional)
//
// Returns: RPC_S_CALLPENDING - the call is still pending a reply
// RPC_E_CALL_CANCELLED - the call was cancelled.
// RPC_E_SERVERCALL_RETRYLATER - the call should be retried later
//
// History: Dec-93 JohannP Created
// 30-Apr-95 Rickhi ReWrite
//
//--------------------------------------------------------------------------
HRESULT CCliModalLoop::BlockFn(HANDLE hEventCallComplete)
{
ComDebOut((DEB_CALLCONT,
"CCliModalLoop::BlockFn this:%x dwMsgQInputFlag:%x hEvent:%x\n",
this, _dwMsgQInputFlag, hEventCallComplete));
Win4Assert(IsWaiting() && "ModalLoop::BlockFn - not waiting on call");
// First, we wait for an event of interest to occur, either for the call
// to complete, or a new windows message to arrive on the queue.
DWORD dwWakeReason = WAIT_TIMEOUT;
HANDLE rgEvents[1] = {hEventCallComplete};
DWORD cEvents = 0;
if (hEventCallComplete != NULL)
{
// Check if the event is already signalled. This ensures that
// when we return from nested calls and the upper calls have already
// been acknowledged, that no windows messages can come in.
ComDebOut((DEB_CALLCONT, "WaitForSingleObject hEvent:%x\n", rgEvents[0]));
cEvents = 1;
dwWakeReason = WaitForSingleObject(rgEvents[0], 0);
}
if (dwWakeReason == WAIT_TIMEOUT)
{
DWORD dwWaitTime = TicksToWait();
// If we want to wake up for a posted message, we need to make
// sure that we haven't missed any because of the queue status
// being affected by prior PeekMessages. We don't worry about
// QS_SENDMESSAGE because if PeekMessage got called, the pending
// send got dispatched. Further, if we are in an input sync call,
// we don't want to start dispatching regular RPC calls here by
// accident.
if (_dwMsgQInputFlag & QS_POSTMESSAGE)
{
DWORD dwStatus = GetQueueStatus(_dwMsgQInputFlag);
// We care about any message on the queue not just new messages
// because PeekMessage affects the queue state. It resets the
// state so even if a message is not processed, the queue state
// represents this as an old message even though no one has
// ever looked at it. So even though the message queue tells us
// there are no new messages in the queue. A new message we are
// interested in could be in the queue.
WORD wNew = (WORD) dwStatus | HIWORD(dwStatus);
// Note that we look for send as well as post because our
// queue status could have reset the state of the send message
// bit and therefore, MsgWaitForMultipleObject below will not
// wake up to dispatch the send message.
if (wNew & (QS_POSTMESSAGE | QS_SENDMESSAGE))
{
// the acknowledge message might be already in the queue
if (PeekRPCAndDDEMessage())
{
// we know that *some* RPC message came in and was
// processed. It could have been the Reply we were waiting
// for OR some other incoming call. Since we cant tell
// which, we return to RPC land. If it was not our Reply
// then RPC will call our modal loop again.
return _hr;
}
}
#ifdef _CHICAGO_
//Note:POSTPPC
WORD wOld = HIWORD(dwStatus);
if (wOld & (QS_POSTMESSAGE))
{
ComDebOut((DEB_CALLCONT, "Set timeout time to 100\n"));
dwWaitTime = 100;
}
#endif //_CHICAGO_
}
ComDebOut((DEB_CALLCONT,
"Call MsgWaitForMultiple time:%ld, cEvents:%x hEvent:%x,\n",
dwWaitTime, cEvents, rgEvents[0] ));
dwWakeReason = MsgWaitForMultipleObjects(cEvents, rgEvents, FALSE,
dwWaitTime, _dwMsgQInputFlag);
ComDebOut((DEB_CALLCONT,
"MsgWaitForMultipleObjects hr:%ld\n", dwWakeReason));
}
// OK, we've done whatever blocking we were going to do and now we have
// been woken up, so figure out what event of interest occured to wake
// us up and go handle it.
if (dwWakeReason == (WAIT_OBJECT_0 + cEvents))
{
// Windows message came in - go process it
ComDebOut((DEB_CALLCONT, "BlockFn: Windows Message Arrived\n"));
HandleWakeForMsg();
}
else if (dwWakeReason == WAIT_TIMEOUT)
{
if (_hr == RPC_S_WAITONTIMER && IsTimerAtZero())
{
// The Retrytimer timed out - just exit and retransmit the call
ComDebOut((DEB_CALLCONT, "BlockFn: Timer at zero\n"));
_hr = RPC_E_SERVERCALL_RETRYLATER;
}
else
{
// we may have missed a message before we called MsgWaitForMult...
// so we go check now for any incoming messages.
ComDebOut((DEB_CALLCONT, "BlockFn: Timeout-Look for msgs\n"));
HandleWakeForMsg();
}
}
else
{
// CallComplete signalled - the call is done.
Win4Assert(rgEvents[dwWakeReason - WAIT_OBJECT_0] == hEventCallComplete);
ComDebOut((DEB_CALLCONT, "BlockFn: CallComplete Event Signaled\n"));
_hr = S_OK;
}
ComDebOut((DEB_CALLCONT, "CCliModalLoop::BlockFn this:%x returns:%x\n",
this, _hr));
return _hr;
}
//+-------------------------------------------------------------------------
//
// Member: CCliModalLoop::HandleWakeForMsg (private)
//
// Synopsis: Handle wake for the arrival of some kind of message
//
// Returns: nothing
// fClearedQueue flag set if appropriate
//
// Algorithm: If this is called to wake up for a posted message, we
// check the queue status. If the message queue status indicates
// that there is some kind of a modal loop going on, then we
// clear all the keyboard and mouse messages in our queue. Then
// if we wake up for all input, we check the message queue to
// see whether we need to notify the application that a message
// has arrived. Then, we dispatch any messages that have to do
// with the ORPC system. Finally we yield just in case we need
// to dispatch a send message in the VDM. For an input sync
// RPC, all we do is a call that will yield to get the pending
// send message dispatched.
//
// History: Dec-93 JohannP Created
// 13-Aug-94 Ricksa Created
//
//--------------------------------------------------------------------------
INTERNAL_(void) CCliModalLoop::HandleWakeForMsg()
{
MSG msg; // Used for various peeks.
// Is this an input sync call?
if (_dwMsgQInputFlag != QS_SENDMESSAGE)
{
// No, so we have to worry about the state of the message queue.
// We have to be careful that we aren't holding the input focus
// on an input synchronized queue.
// So what is the state of the queue? - note we or QS_TRANSFER because
// this an undocumented flag which tells us the the input focus has
// changed to us.
DWORD dwQueueFlags = GetQueueStatus(QS_ALLINPUT | QS_TRANSFER);
ComDebOut((DEB_CALLCONT, "Queue Status %lx\n", dwQueueFlags));
// Call through to the application if we are going to. We do this here
// so that the application gets a chance to process any
// messages that it wants to and also allows the call control to
// dispatch certain messages that it knows how to, thus making the
// queue more empty.
if (((_dwMsgQInputFlag & QS_ALLINPUT) == QS_ALLINPUT) &&
FindMessage(dwQueueFlags))
{
// pending message in the queue
HandlePendingMessage();
}
// Did the input focus change to us?
if ((LOWORD(dwQueueFlags) & QS_TRANSFER) || _dwFlags & CMLF_CLEAREDQUEUE)
{
ComDebOut((DEB_CALLCONT, "Message Queue is being cleared\n"));
_dwFlags |= CMLF_CLEAREDQUEUE;
// Try to clear the queue as best we can of any messages that
// might be holding off some other modal loop from executing.
// So we eat all mouse and key events.
if (HIWORD(dwQueueFlags) & QS_KEY)
{
while (MyPeekMessage(&msg, NULL, WM_KEYFIRST, WM_KEYLAST,
PM_REMOVE | PM_NOYIELD))
{
;
}
}
// Clear mouse releated messages if there are any
if (HIWORD(dwQueueFlags) & QS_MOUSE)
{
while (MyPeekMessage(&msg, NULL, WM_MOUSEFIRST, WM_MOUSELAST,
PM_REMOVE | PM_NOYIELD))
{
;
}
while (MyPeekMessage(&msg, NULL, WM_NCMOUSEFIRST,
WM_NCMOUSELAST, PM_REMOVE | PM_NOYIELD))
{
;
}
while (MyPeekMessage(&msg, NULL, WM_QUEUESYNC, WM_QUEUESYNC,
PM_REMOVE | PM_NOYIELD))
{
;
}
}
// Get rid of paint message if we can as well -- this makes
// the screen look so much better.
if (HIWORD(dwQueueFlags) & QS_PAINT)
{
if (MyPeekMessage(&msg, NULL, WM_PAINT, WM_PAINT, PM_REMOVE | PM_NOYIELD))
{
ComDebOut((DEB_CALLCONT, "Dispatch paint\n"));
DispatchMessage(&msg);
}
}
}
}
else if (!IsWOWThread() || !IsWOWThreadCallable())
{
// We need to give user control so that the send message
// can get dispatched. Thus the following is simply a no-op
// which gets into user to let it dispatch the message.
PeekMessage(&msg, 0, WM_NULL, WM_NULL, PM_NOREMOVE);
}
if (IsWOWThread() && IsWOWThreadCallable())
{
// In WOW, a genuine yield is the only thing to guarantee
// that SendMessage will get through
g_pOleThunkWOW->YieldTask16();
}
}
//+-------------------------------------------------------------------------
//
// Member: CCliModalLoop::PeekRPCAndDDEMessage
//
// Synopsis: Called when a windows message arrives to look for incoming
// Rpc messages which might be the reply to an outstanding call
// or may be new incoming request messages. Also looks for
// DDE messages.
//
// Returns: TRUE - found and processed an RPC message
// FALSE - did not find an RPC message
//
// History: 21-Dec-93 JohannP Created
// 30-Apr-95 Rickhi ReWrite
//
//--------------------------------------------------------------------------
BOOL CCliModalLoop::PeekRPCAndDDEMessage()
{
// loop over all windows looking for incoming Rpc messages. Note that
// it is possible for a dispatch here to cause one of the windows to
// be deregistered or another to be registered, so our loop has to account
// for that, hence the check for NULL hWnd.
BOOL fRet = FALSE;
MSG Msg;
for (UINT i = 0; i < 2; i++)
{
// get window info and peek on it if the hWnd is still OK
SWindowData *pWD = _pACC->GetWindowData(i);
if (pWD->hWnd != WD_EMPTY)
{
if (MyPeekMessage(&Msg, pWD->hWnd, pWD->wFirstMsg, pWD->wLastMsg,
PM_REMOVE | PM_NOYIELD))
{
Win4Assert(IsWaiting());
DispatchMessage(&Msg);
// exit on the first dispatched message. If the message was
// not the reply we were waiting for, then the channel will
// call us back again.
return TRUE;
}
}
}
return FALSE;
}
//+-------------------------------------------------------------------------
//
// Member: CCliModalLoop::FindMessage
//
// Synopsis: Called by HandleWakeForMsg when a message arrives on the
// windows msg queue. Determines if there is something of
// interest to us, and pulls timer msgs. Dispatches RPC, DDE,
// and RPC timer messages.
//
// Arguments: [dwStatus] - current Queue status (from GetQueueStatus)
//
// Returns: TRUE - there is a message to process
// FALSE - no messages to process
//
// Algorithm: Find the next message in the queue by using the following
// priority list:
//
// 1. RPC and DDE messages
// 2. mouse and keyboard messages
// 3. other messages
//
// History: 21-Dec-93 Johannp Created
//
//--------------------------------------------------------------------------
INTERNAL_(BOOL) CCliModalLoop::FindMessage(DWORD dwStatus)
{
WORD wOld = HIWORD(dwStatus);
WORD wNew = (WORD) dwStatus;
if (!wNew)
{
if (!(wOld & QS_POSTMESSAGE))
return FALSE; // no messages to take care of
else
wNew |= QS_POSTMESSAGE;
}
MSG Msg;
// Priority 1: look for RPC and DDE messages
if (wNew & (QS_POSTMESSAGE | QS_SENDMESSAGE | QS_TIMER))
{
if (PeekRPCAndDDEMessage())
{
// we know that *some* RPC message came in, might be our
// reply or may be some incoming call. In any case, return to
// the modal loop to guy so we can figure out if we need to
// keep going.
return FALSE;
}
}
if (wNew & QS_TIMER)
{
// throw the system timer messages away
while (MyPeekMessage(&Msg, 0, WM_SYSTIMER, WM_SYSTIMER, PM_REMOVE | PM_NOYIELD))
;
}
// Priority 2: messages from the hardware queue
if (wNew & (QS_KEY | QS_MOUSEMOVE | QS_MOUSEBUTTON))
{
return TRUE; // these messages are always removed
}
else if (wNew & QS_TIMER)
{
if (MyPeekMessage(&Msg, 0, WM_TIMER, WM_TIMER, PM_NOREMOVE | PM_NOYIELD) )
return TRUE;
}
else if (wNew & QS_PAINT)
{
return TRUE; // this message might not get removed
}
else if (wNew & (QS_POSTMESSAGE | QS_SENDMESSAGE))
{
if (MyPeekMessage(&Msg, 0, 0, 0, PM_NOREMOVE))
return TRUE; // Priority 3: all other messages
}
return FALSE;
}
//+-------------------------------------------------------------------------
//
// Member: CCliModalLoop::HandlePendingMessage
//
// Synopsis: this function is called for system messages and other
// pending messages
//
// Arguments: none
//
// Returns: nothing, _hr may be updated if call is cancelled.
//
// Algorithm:
//
// History: 21-Dec-93 Johannp Created
// 30-Apr-95 Rickhi ReWrite
//
//--------------------------------------------------------------------------
INTERNAL_(void) CCliModalLoop::HandlePendingMessage()
{
// get and call the message filter if there is one
IMessageFilter *pMF = _pACC->GetMsgFilter();
if (pMF)
{
ComDebOut((DEB_MFILTER,
"pMF->MessagePending(dwTIDCallee:%x ElapsedTime:%x Type:%x)\n",
_dwTIDCallee, GetElapsedTime(),
(_pPrev) ? PENDINGTYPE_NESTED : PENDINGTYPE_TOPLEVEL));
DWORD dwRet = pMF->MessagePending((MF_HTASK)_dwTIDCallee,
GetElapsedTime(),
(_pPrev) ? PENDINGTYPE_NESTED
: PENDINGTYPE_TOPLEVEL);
ComDebOut((DEB_MFILTER,"pMF->MessagePending() dwRet:%x\n", dwRet));
_pACC->ReleaseMsgFilter();
if (dwRet == PENDINGMSG_CANCELCALL)
{
_hr = RPC_E_CALL_CANCELED;
return;
}
Win4Assert((dwRet == PENDINGMSG_WAITDEFPROCESS ||
dwRet == PENDINGMSG_WAITNOPROCESS) &&
"Invalid return value from pMF->MessagePending");
}
// if we get here we are going to do the default message processing.
// Default Processing: Continue to wait for the call return and
// don't dispatch the new message. Perform default processing on
// special system messages.
MSG msg;
// we have to take out all syscommand messages
if (MyPeekMessage(&msg, 0, WM_SYSCOMMAND, WM_SYSCOMMAND, PM_REMOVE | PM_NOYIELD))
{
// only dispatch some syscommands
if (msg.wParam == SC_HOTKEY || msg.wParam == SC_TASKLIST)
{
ComDebOut((DEB_CALLCONT,">>>> Dispatching SYSCOMMAND message: %x; wParm: %x \r\n",msg.message, msg.wParam));
DispatchMessage(&msg);
}
else
{
ComDebOut((DEB_CALLCONT,">>>> Received/discarded SYSCOMMAND message: %x; wParm: %x \r\n",msg.message, msg.wParam));
MessageBeep(0);
}
}
else if (MyPeekMessage(&msg, 0, WM_SYSKEYDOWN, WM_SYSKEYDOWN, PM_NOREMOVE | PM_NOYIELD))
{
if (msg.message == WM_KEYDOWN)
{
if (msg.wParam != VK_CONTROL && msg.wParam != VK_SHIFT)
MessageBeep(0);
}
else if (msg.message == WM_SYSKEYDOWN && msg.lParam & SYS_ALTDOWN &&
(msg.wParam == VK_TAB || msg.wParam == VK_ESCAPE))
{
MyPeekMessage(&msg, 0, WM_SYSKEYDOWN, WM_SYSKEYDOWN, PM_REMOVE | PM_NOYIELD);
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
else if (MyPeekMessage(&msg, 0, WM_ACTIVATE, WM_ACTIVATE, PM_REMOVE | PM_NOYIELD)
|| MyPeekMessage(&msg, 0, WM_ACTIVATEAPP, WM_ACTIVATEAPP, PM_REMOVE | PM_NOYIELD)
|| MyPeekMessage(&msg, 0, WM_NCACTIVATE, WM_NCACTIVATE, PM_REMOVE | PM_NOYIELD) )
{
DispatchMessage(&msg);
}
}
//+-------------------------------------------------------------------------
//
// Member: CCliModalLoop::MyPeekMessage
//
// Synopsis: This function is called whenever we want to do a PeekMessage.
// It intercepts WM_QUIT messages and remembers them so that
// they can be reposted when the modal loop is exited.
//
// Arguments: [pMsg] - message structure
// [hWnd] - window to peek on
// [min/max] - min and max message numbers
// [wFlag] - peek flags
//
// Returns: TRUE - a message is available
// FALSE - no messages available
//
// History: 21-Dec-93 Johannp Created
//
//--------------------------------------------------------------------------
INTERNAL_(BOOL) CCliModalLoop::MyPeekMessage(MSG *pMsg, HWND hwnd,
UINT min, UINT max, WORD wFlag)
{
BOOL fRet = PeekMessage(pMsg, hwnd, min, max, wFlag);
while (fRet)
{
ComDebOut((DEB_CALLCONT, "MyPeekMessage: hwnd:%x msg:%d time:%ld\n",
pMsg->hwnd, pMsg->message, pMsg->time));
if (pMsg->message != WM_QUIT)
{
// it is not a QUIT message so exit the loop and return TRUE
break;
}
// just remember that we saw a QUIT message. we will ignore it for
// now and repost it after our call has completed.
ComDebOut((DEB_CALLCONT, "WM_QUIT received.\n"));
_wQuitCode = pMsg->wParam;
_dwFlags |= CMLF_QUITRECEIVED;
if (!(wFlag & PM_REMOVE)) // NOTE: dont use PM_NOREMOVE
{
// quit message is still on queue so pull it off
PeekMessage(pMsg, hwnd, WM_QUIT, WM_QUIT, PM_REMOVE | PM_NOYIELD);
}
// peek again to see if there is another message
fRet = PeekMessage(pMsg, hwnd, min, max, wFlag);
}
return fRet;
}
#if DBG==1
//+-------------------------------------------------------------------------
//
// Member: CCliModalLoop::DispatchMessage
//
// Synopsis: This function is called whenever we want to dispatch a
// message we have peeked. It is just a debug wrapper to provide
// debug out statements about dispatched messages.
//
// Arguments: [pMsg] - message structure
//
//--------------------------------------------------------------------------
INTERNAL_(void) CCliModalLoop::DispatchMessage(MSG *pMsg)
{
ComDebOut((DEB_CALLCONT, "Dispatching Message hWnd:%x msg:%d wParam:%x\n",
pMsg->hwnd, pMsg->message, pMsg->wParam));
::DispatchMessage(pMsg);
}
#endif
//+-------------------------------------------------------------------------
//
// Member: CCliModalLoop::GetElapsedTime
//
// Synopsis: Get the elapsed time for an RPC call
//
// Returns: Elapsed time of current call
//
// Algorithm: This checks whether we have the slow time factor. If not,
// and we are in WOW we read it from the registry. Otherwise,
// it is just set to one. Then we calculate the time of the
// RPC call and divide it by the slow time factor.
//
// History: 22-Jul-94 Ricksa Created
//
//--------------------------------------------------------------------------
INTERNAL_(DWORD) CCliModalLoop::GetElapsedTime()
{
// Define slow time factor to something invalid
static dwSlowTimeFactor = 0;
if (dwSlowTimeFactor == 0)
{
if (IsWOWProcess())
{
// Get time factor from registry otherwise set to the default
dwSlowTimeFactor = GetSlowTimeFactor();
}
else
{
// Time is unmodified for 32 bit apps
dwSlowTimeFactor = 1;
}
}
DWORD dwTickCount = GetTickCount();
DWORD dwElapsedTime = dwTickCount - _dwTimeOfCall;
if (dwTickCount < _dwTimeOfCall)
{
// the timer wrapped
dwElapsedTime = 0xffffffff - _dwTimeOfCall + dwTickCount;
}
return (dwElapsedTime / dwSlowTimeFactor);
}
//+-------------------------------------------------------------------------
//
// Member: CCliModalLoop::FindPrevCallOnLID [server side]
//
// Synopsis: When an incoming call arrives this is used to find any
// previous call for the same logical thread, ignoring
// INTERNAL calls. The result is used to determine if this
// is a nested call or not.
//
// Arguments: [lid] - logical threadid of incoming call
//
// Returns: pCML - if a previous CliModalLoop found for this lid
// NULL - otherwise
//
// Algorithm: just walk backwards on the _pPrev chain
//
// History: 17-Dec-93 JohannP Created
// 30-Apr-95 Rickhi ReWrite
//
//--------------------------------------------------------------------------
CCliModalLoop *CCliModalLoop::FindPrevCallOnLID(REFLID lid)
{
CCliModalLoop *pCML = this;
do
{
if (pCML->_lid == lid)
{
break; // found a match, return it
}
} while ((pCML = pCML->_pPrev) != NULL);
return pCML;
}
//+-------------------------------------------------------------------------
//
// Function: STAInvoke
//
// Synopsis: Called whenever an incoming call arrives in a single-threaded
// apartment. It asks the apps message filter (if there is one)
// whether it wants to handle the call or not, and dispatches
// the call if OK.
//
// Arguments: [pMsg] - Incoming Rpc message
// [pStub] - stub to call if MF says it is OK
// [pChnl] - channel ptr to give to stub
// [pv] - real interface being called
// [pdwFault] - where to store fault code if there is a fault
//
// Returns: result for MF or from call to stub
//
// History: 21-Dec-93 Johannp Original Version
// 22-Jul-94 Rickhi ReWrite
//
//--------------------------------------------------------------------------
INTERNAL STAInvoke(RPCOLEMESSAGE *pMsg, DWORD CallCatIn, IRpcStubBuffer *pStub,
IRpcChannelBuffer *pChnl, void *pv, DWORD *pdwFault)
{
ComDebOut((DEB_CALLCONT,
"STAInvoke pMsg:%x CallCatIn:%x pStub:%x pChnl:%x\n",
pMsg, CallCatIn, pStub, pChnl));
HRESULT hr = HandleIncomingCall(*MSG_TO_IIDPTR(pMsg),
(WORD)pMsg->iMethod,
CallCatIn, pv);
if (hr == S_OK)
{
// the message filter says its OK to invoke the call.
// construct a server call state. This puts the current incoming
// call's CallCat in Tls so we can check it if the server tries to
// make an outgoing call while handling this call. See CanMakeOutCall.
CSrvCallState SCS(CallCatIn);
// invoke the call
hr = MTAInvoke(pMsg, CallCatIn, pStub, pChnl, pdwFault);
}
else if (hr == RPC_E_CALL_REJECTED || hr == RPC_E_SERVERCALL_RETRYLATER)
{
// server is rejecting the call, try to copy the incomming buffer so
// that the client has the option of retrying the call.
hr = CopyMsgForRetry(pMsg, pChnl, hr);
}
ComDebOut((DEB_CALLCONT,"STAInvoke returns:%x\n",hr));
return hr;
}
//+-------------------------------------------------------------------------
//
// Function: HandleIncomingCall, internal
//
// Synopsis: Called whenever an incoming call arrives in a single-threaded
// apartment. It asks the app's message filter (if there is one)
// whether it wants to handle the call or not
//
// Arguments: [piid] - ptr to interface the call is being made on
// [iMethod] - method number being called
// [CallCatIn] - category of incoming call
// [pv] - real interface being called
//
// Returns: result from MF
//
// History: 11-Oct-96 Rickhi Separated from STAInvoke
//
//--------------------------------------------------------------------------
INTERNAL HandleIncomingCall(REFIID riid, WORD iMethod, DWORD CallCatIn, void *pv)
{
ComDebOut((DEB_CALLCONT,
"HandleIncomingCall iid:%I iMethod:%x CallCatIn:%x pv:%x:%x\n",
&riid, iMethod, CallCatIn, pv));
COleTls tls;
if (!(tls->dwFlags & OLETLS_APARTMENTTHREADED))
{
// free-threaded apartments don't have a message filter
return S_OK;
}
HRESULT hr = S_OK;
CAptCallCtrl *pACC = tls->pCallCtrl;
// We dont call the message filter for IUnknown since older versions
// of OLE did not, and doing so (unfortunately) breaks compatibility.
// Also check for IRundown since local clients call on it instead of
// IRemUnknown.
IMessageFilter *pMF = (riid == IID_IRundown || riid == IID_IRemUnknown)
? NULL : pACC->GetMsgFilter();
if (pMF)
{
// the app has installed a message filter, call it.
INTERFACEINFO IfInfo;
IfInfo.pUnk = (IUnknown *)pv;
IfInfo.iid = riid;
IfInfo.wMethod = iMethod;
ComDebOut((DEB_CALLCONT, "Calling iMethod:%x riid:%I\n",
IfInfo.wMethod, &IfInfo.iid));
CCliModalLoop *pCML = NULL;
REFLID lid = tls->LogicalThreadId;
DWORD TIDCaller = tls->dwTIDCaller;
DWORD dwCallType = pACC->GetCallTypeForInCall(&pCML, lid, CallCatIn);
DWORD dwElapsedTime = (pCML) ? pCML->GetElapsedTime() : 0;
// The DDE layer doesn't provide any interface information. This
// was true on the 16-bit implementation, and has also been
// brought forward into this implementation to insure
// compatibility. However, the CallCat of the IfInfo is still
// provided.
//
// Therefore, if pIfInfo has its pUnk member set to NULL, then
// we are going to send a NULL pIfInfo to the message filter.
ComDebOut((DEB_MFILTER,
"pMF->HandleIncomingCall(dwCallType:%x TIDCaller:%x dwElapsedTime:%x IfInfo:%x)\n",
dwCallType, TIDCaller, dwElapsedTime, (IfInfo.pUnk) ? &IfInfo : NULL));
DWORD dwRet = pMF->HandleInComingCall(dwCallType,
(MF_HTASK)TIDCaller,
dwElapsedTime,
IfInfo.pUnk ? &IfInfo : NULL);
ComDebOut((DEB_MFILTER,"pMF->HandleIncomingCall() dwRet:%x\n", dwRet));
pACC->ReleaseMsgFilter();
// strict checking of app return code for win32
Win4Assert(dwRet == SERVERCALL_ISHANDLED ||
dwRet == SERVERCALL_REJECTED ||
dwRet == SERVERCALL_RETRYLATER ||
IsWOWThread() && "Invalid Return code from App IMessageFilter");
if (dwRet != SERVERCALL_ISHANDLED)
{
if (CallCatIn == CALLCAT_ASYNC || CallCatIn == CALLCAT_INPUTSYNC)
{
// Note: input-sync and async calls can not be rejected
// Even though they can not be rejected, we still have to
// call the MF above to maintain 16bit compatability.
hr = S_OK;
}
else if (dwRet == SERVERCALL_REJECTED)
{
hr = RPC_E_CALL_REJECTED;
}
else if (dwRet == SERVERCALL_RETRYLATER)
{
hr = RPC_E_SERVERCALL_RETRYLATER;
}
else
{
// 16bit OLE let bogus return codes go through and of course
// apps rely on that behaviour so we let them through too, but
// we are more strict on 32bit.
hr = (IsWOWThread()) ? S_OK : RPC_E_UNEXPECTED;
}
}
}
ComDebOut((DEB_CALLCONT, "HandleIncomingCall hr:%x\n", hr));
return hr;
}
//+-------------------------------------------------------------------------
//
// Function: MTAInvoke
//
// Synopsis: Multi-Threaded Apartment Invoke. Called whenever an incoming
// call arrives in the MTA apartment (or as a subroutine to
// STAInvoke). It just dispatches to a common sub-routine.
//
// Arguments: [pMsg] - Incoming Rpc message
// [pStub] - stub to call if MF says it is OK
// [pChnl] - channel ptr to give to stub
// [pdwFault] - where to store fault code if there is a fault
//
// Returns: result from calling the stub
//
// History: 03-Oct-95 Rickhi Made into subroutine from STAInvoke
//
//--------------------------------------------------------------------------
INTERNAL MTAInvoke(RPCOLEMESSAGE *pMsg, DWORD CallCatIn, IRpcStubBuffer *pStub,
IRpcChannelBuffer *pChnl, DWORD *pdwFault)
{
#if DBG==1
ComDebOut((DEB_CALLCONT,
"MTAInvoke pMsg:%x CallCatIn:%x pStub:%x pChnl:%x\n",
pMsg, CallCatIn, pStub, pChnl));
IID *piid = MSG_TO_IIDPTR(pMsg);
DebugPrintORPCCall(ORPC_INVOKE_BEGIN, *piid, pMsg->iMethod, CallCatIn);
RpcSpy((CALLIN_BEGIN, NULL, *piid, pMsg->iMethod, 0));
#endif
// call a common subroutine to do the dispatch. The subroutine also
// catches exceptions and provides some debug help.
HRESULT hr = StubInvoke(pMsg, pStub, pChnl, pdwFault);
#if DBG==1
RpcSpy((CALLIN_END, NULL, *piid, pMsg->iMethod, hr));
DebugPrintORPCCall(ORPC_INVOKE_END, *piid, pMsg->iMethod, CallCatIn);
ComDebOut((DEB_CALLCONT,"MTAInvoke returns:%x\n",hr));
#endif
return hr;
}
//+-------------------------------------------------------------------------
//
// Function: CopyMsgForRetry
//
// Synopsis: Makes a copy of the server-side message buffer to return to
// the client so that the client can retry the call later.
// Returns an error if the client is on a different machine.
//
// Parameters: [pMsg] - ptr to message to copy
// [pChnl] - ptr to channel call is being made on
// [hr] - result code
//
// History: 30-05-95 Rickhi Created
//
//+-------------------------------------------------------------------------
HRESULT CopyMsgForRetry(RPCOLEMESSAGE *pMsg, IRpcChannelBuffer *pChnl, HRESULT hrIn)
{
ComDebOut((DEB_CALLCONT,"CopyMsgForRetry pMsg:%x pChnl:%x pBuffer:%x\n",
pMsg, pChnl, pMsg->Buffer));
DWORD dwDestCtx;
HRESULT hr = pChnl->GetDestCtx(&dwDestCtx, NULL);
if (SUCCEEDED(hr) && dwDestCtx != MSHCTX_DIFFERENTMACHINE &&
!IsEqualGUID(IID_IObjServer, *MSG_TO_IIDPTR(pMsg)))
{
// client on same machine as server.
void *pSavedBuffer = pMsg->Buffer;
hr = pChnl->GetBuffer(pMsg, *MSG_TO_IIDPTR(pMsg));
if (SUCCEEDED(hr))
{
// copy original buffer to the new buffer
memcpy(pMsg->Buffer, pSavedBuffer, pMsg->cbBuffer);
hr = hrIn;
}
}
else
{
// client on different machine than server, or the call was on
// the activation interface, fail the call and dont send back
// a copy of the parameter packet.
hr = RPC_E_CALL_REJECTED;
}
ComDebOut((DEB_CALLCONT,"CopyMsgForRetry pBuffer:%x hr:%x\n",
pMsg->Buffer, hr));
return hr;
}
//+-------------------------------------------------------------------------
//
// Method: CAptCallCtrl::GetCallTypeForInCall
//
// Synopsis: called when an incoming call arrives in order to determine
// what CALLTYPE to pass to the applications message filter.
//
// Arguments: [ppCML] - Client Modal Loop of prev call on same lid (if any)
// [lid] - logical thread id of this call
// [dwCallCat] - call category of incoming call
//
// Returns: the CALLTYPE to give to the message filter
//
// History: 21-Dec-93 Johannp Created
// 30-Apr-95 Rickhi ReWrite
//
// Notes:
//
// 1 = CALLTYPE_TOPLEVEL // sync or inputsync call - no outgoing call
// 2 = CALLTYPE_NESTED // callback on behalf of previous outgoing call
// 3 = CALLTYPE_ASYNC // asynchronous call - no outstanding call
// 4 = CALLTYPE_TOPLEVEL_CALLPENDING // call with new LID - outstand call
// 5 = CALLTYPE_ASYNC_CALLPENDING // async call - outstanding call
//
//--------------------------------------------------------------------------
DWORD CAptCallCtrl::GetCallTypeForInCall(CCliModalLoop **ppCML,
REFLID lid, DWORD dwCallCatIn)
{
DWORD CallType;
CCliModalLoop *pCML = GetTopCML();
if (dwCallCatIn == CALLCAT_ASYNC) // asynchronous call has arrived
{
if (pCML == NULL)
CallType = CALLTYPE_ASYNC; // no outstanding calls
else
CallType = CALLTYPE_ASYNC_CALLPENDING; // outstanding call
}
else // non-async call has arrived
{
if (pCML == NULL)
CallType = CALLTYPE_TOPLEVEL; // no outstanding call
else if ((*ppCML = pCML->FindPrevCallOnLID(lid)) != NULL)
CallType = CALLTYPE_NESTED; // outstanding call on same lid
else
CallType = CALLTYPE_TOPLEVEL_CALLPENDING; // different lid
}
ComDebOut((DEB_CALLCONT,"GetCallTypeForInCall return:%x\n", CallType));
return CallType;
}