NT4/private/ole32/com/dcomrem/callctrl.hxx
2020-09-30 17:12:29 +02:00

526 lines
17 KiB
C++

//+-------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1992 - 1993.
//
// File: callctrl.hxx
//
// Contents: OLE Call Control
//
// Functions:
//
// History: 21-Dec-93 Johannp Original Version
// 04-Nov-94 Rickhi ReWrite
//
//--------------------------------------------------------------------------
#ifndef __CALLCTRL_HXX__
#define __CALLCTRL_HXX__
#include <channelb.hxx> // CRpcChannelBuffer
#undef RPC_S_CALLPENDING
#undef RPC_S_WAITONTIMER
#define RPC_S_CALLPENDING 21 // BUGBUG
#define RPC_S_WAITONTIMER 22 // BUGBUG
// Max time we wait for MsgWaitForMultiple before waking up and
// checking the queue. This is needed because the API is broken
// (ie it does not wake up on messages posted before it is called).
#define MAX_TICKS_TO_WAIT 1000
// Private definition for change of input focus
// BUGBUG: fix for CHICAGO when chicago's USER supports QS_TRANSFER
#ifdef _CHICAGO_
#define QS_TRANSFER 0x0000
#else
#define QS_TRANSFER 0x4000
#endif
typedef IID LID; // logical thread id
typedef REFIID REFLID; // ref to logical thread id
#define MF_HTASK struct HTASK__ *
// 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.
extern DWORD gMsgQInputFlagTbl[4]; // see callctrl.cxx
// the following table is used to map bit flags in the Rpc Message to
// the equivalent OLE CALLCATEGORY.
extern DWORD gRpcFlagToCallCatMap[3]; // see callctrl.cxx
// the following inline funtion is used to compute the CALLCATEGORY from
// the RpcFlags field in the RPC message
inline DWORD RpcFlagToCallCat(DWORD RpcFlags)
{
return gRpcFlagToCallCatMap[(RpcFlags & 0x60000000) >> 29];
}
// convenient mapping from RPCOLEMESSAGE to IID in the message
#define MSG_TO_IIDPTR(pMsg) \
&((RPC_SERVER_INTERFACE *)((RPC_MESSAGE *)pMsg)->RpcInterfaceInformation)->InterfaceId.SyntaxGUID
// private structure used to hold the window handles and message ranges
// to peek to see if there is more work to do when in the modal loop.
typedef struct tagSWindowData
{
HWND hWnd; // window handle to peek on
UINT wFirstMsg; // first msg in range to peek
UINT wLastMsg; // Last msg in range to peek
} SWindowData;
// function prototypes. This function is called by the channel during
// transmission in the apartment model.
RPC_STATUS OleModalLoopBlockFn(void *, void *, HANDLE hEventComplete);
// function called by the channel during dispatch in the apartment model.
// STAInvoke is used for single-threaded apartments, MTAInvoke is used
// for Multi-threaded apartments.
INTERNAL STAInvoke(RPCOLEMESSAGE *pMsg, DWORD dwCallCat, IRpcStubBuffer *pStub,
IRpcChannelBuffer *pChnl, void *pv, DWORD *pdwFault);
INTERNAL MTAInvoke(RPCOLEMESSAGE *pMsg, DWORD dwCallCat, IRpcStubBuffer *pStub,
IRpcChannelBuffer *pChnl, DWORD *pdwFault);
INTERNAL CanMakeOutCall(DWORD dwCallCatOut, REFIID riid);
class CAptCallCtrl;
//+-------------------------------------------------------------------------
//
// Function: GetAptCallCtrl
//
// Synopsis: Gets the current apartment's call control ptr from TLS.
//
//+-------------------------------------------------------------------------
inline CAptCallCtrl *GetAptCallCtrl(void)
{
COleTls tls;
return tls->pCallCtrl;
}
//+-------------------------------------------------------------------------
//
// Class: CAptRpcChnl
//
// Synopsis: Client side Apartment model Rpc Channel.
//
// History: 11-Nov-94 Rickhi Created
//
// Notes: This object inherits the Rpc channel and adds some
// functionality to it that is needed by the apartment model,
// (eg deadlock prevention, call retry, nested call support).
// For each outgoing call, it verifies the app is allowed to
// make the call, and instantiates a modal loop for it, thereby
// allowing callbacks and new calls to be handled by the app
// thread.
//
// Important: Since no mutual exclusion primitives are used in this code,
// the derived class CAptRpcChnl must be stateless, as some
// proxies are freethreaded even in the apartment model, in
// particular, IRemUnknown and the SCM activation interface. All
// relevant state is maintained in the CCliModalLoop object which
// is constructed on the stack on a per call basis. Note that the
// _dwTIDCallee state is safe because it is set in the ctor and
// never changes. The base class, CRpcChannelBuffer *is* thread
// safe since it is used in the freethreaded model also.
//
//--------------------------------------------------------------------------
class CAptRpcChnl : public CRpcChannelBuffer
{
public:
CAptRpcChnl(CStdIdentity *pStdId, OXIDEntry *pOXIDEntry, DWORD eState);
// CRpcChannelBuffer methods that we override
STDMETHOD (GetBuffer) (RPCOLEMESSAGE *pMsg, REFIID riid);
STDMETHOD (SendReceive) (RPCOLEMESSAGE *pMsg, ULONG *pulStatus);
private:
~CAptRpcChnl(); // can only be called from Release
HRESULT CopyMsgForRetry(RPCOLEMESSAGE *pMsg);
DWORD _dwTIDCallee; // TID of thread server lives on
DWORD _dwAptId; // Apartment ID proxy lives in.
};
//+-------------------------------------------------------------------------
//
// Class: CMTARpcChnl
//
// Synopsis: Client side Multi-Threaded Apartment Rpc Channel.
//
// History: 11-Nov-94 Rickhi Created
//
// Notes: This object inherits the Rpc channel and adds some
// functionality to it that is needed by Multi-threaded apartment.
// For each outgoing call, it verifies the app is allowed to
// make the call.
//
// Important: Since no mutual exclusion primitives are used in this code,
// the derived class CMTARpcChnl must be stateless. Note that the
// _dwTIDCallee state is safe because it is set in the ctor and
// never changes. The base class, CRpcChannelBuffer *is* thread
// safe.
//
//--------------------------------------------------------------------------
class CMTARpcChnl : public CRpcChannelBuffer
{
public:
CMTARpcChnl(CStdIdentity *pStdId, OXIDEntry *pOXIDEntry, DWORD eState);
// CRpcChannelBuffer methods that we override
STDMETHOD (GetBuffer) (RPCOLEMESSAGE *pMsg, REFIID riid);
private:
~CMTARpcChnl(); // can only be called from Release
DWORD _dwTIDCallee; // TID of thread server lives on
DWORD _dwAptId; // Apartment ID proxy lives in.
};
//+-------------------------------------------------------------------------
//
// Class: CCliModalLoop
//
// Synopsis: Each outgoing client call enters a modal loop. This object
// maintains the state of the modal loop for one outgoing
// call.
//
// History: 11-Nov-94 Rickhi Created
//
// Notes: This object is constructed on the stack on a per call basis
// and needs no mutual exclusion mechanisms. A pointer to this
// state is stored in TLS (actually in CAptCallCtrl in TLS) and
// later referenced when OleModalLoopBlockFn is called from deep
// within the bowls of SendReceive in the channel (or Rpc Runtime
// if the MSWMSG transport is used).
//
//--------------------------------------------------------------------------
class CCliModalLoop
{
public:
CCliModalLoop(DWORD TIDCallee, DWORD CallCatOut);
~CCliModalLoop();
CCliModalLoop *FindPrevCallOnLID(REFLID lid);
HRESULT SendReceive(RPCOLEMESSAGE *pMsg, ULONG *pulStatus,
IRpcChannelBuffer2 *pChnl);
HRESULT BlockFn(HANDLE hEventCallComplete);
BOOL IsWaiting(void)
{
return (_hr == RPC_S_CALLPENDING || _hr == RPC_S_WAITONTIMER);
}
INTERNAL_(BOOL) MyPeekMessage(MSG *pMsg, HWND hwnd, UINT min, UINT max, WORD wFlag);
INTERNAL_(DWORD) GetElapsedTime();
private:
// message processing in modal loop
INTERNAL_(void) HandleWakeForMsg(void);
INTERNAL_(BOOL) FindMessage(DWORD dwStatus);
INTERNAL_(void) HandlePendingMessage(void);
INTERNAL_(BOOL) PeekRPCAndDDEMessage(void);
#if DBG==1
INTERNAL_(void) DispatchMessage(MSG *pMsg);
#endif
// rejected call processing
INTERNAL HandleRejectedCall(IRpcChannelBuffer2 *pChnl);
INTERNAL StartTimer(DWORD dwMilliSecToWait);
INTERNAL_(BOOL) IsTimerAtZero();
INTERNAL_(DWORD) TicksToWait();
HRESULT _hr; // the return value of this call
CCliModalLoop *_pPrev; // Previous CCliModalLoop for this apartment
DWORD _dwTIDCallee; // TID of thread we are calling
DWORD _dwMsgQInputFlag; // message queue input flag
LID _lid; // logical threadid of call
DWORD _dwFlags; // internal flags (see CMLFLAGS)
UINT _wQuitCode; // quit code if WM_QUIT received
DWORD _dwTimeOfCall; // time when call was made
DWORD _dwWakeup; // absolute time to wake up
DWORD _dwMillSecToWait; // relative time
CAptCallCtrl *_pACC; // apartment call control object
};
// bit values for the CliModalLoop _dwFlags field
typedef enum tagCMLFLAGS
{
CMLF_QUITRECEIVED = 1, // WM_QUIT was received
CMLF_CLEAREDQUEUE = 2 // the msg queue has been cleared
} CMLFLAGS;
//+-------------------------------------------------------------------------
//
// Class: CSrvCallState
//
// Synopsis: Each incoming server call generates one of these objects.
// It maintains the state of the incoming call.
//
// History: 11-Nov-94 Rickhi Created
//
//--------------------------------------------------------------------------
class CSrvCallState
{
public:
CSrvCallState(DWORD CallCatIn);
~CSrvCallState();
DWORD GetCallCatIn(void) { return _dwCallCatIn; }
private:
DWORD _dwCallCatIn; // category of this incoming call
CSrvCallState *_pPrev; // previous CSrvCallState on the stack
};
//+-------------------------------------------------------------------------
//
// Class: CAptCallCtrl
//
// Synopsis: Represents per apartment Call Control state that is shared
// between both the client side and server side call control
// objects.
//
// History: 11-Nov-94 Rickhi Created
//
// Notes: Two LIFO stacks are maintained, one for client call modal
// loops, and one for incoming server calls. The incoming
// server calls are used in both Single-Threaded apartments
// and multi-threaded apartments, and so are stored in TLS
// directly, rather than chained off this object.
//
//--------------------------------------------------------------------------
class CAptCallCtrl
{
public:
CAptCallCtrl();
~CAptCallCtrl();
// message filter handling methods
IMessageFilter *InstallMsgFilter(IMessageFilter *pMF);
IMessageFilter *GetMsgFilter();
void ReleaseMsgFilter() { _fInMsgFilter = FALSE; }
BOOL InMsgFilter() { return _fInMsgFilter; }
// client side LIFO modal loop queue
void SetTopCML(CCliModalLoop *pCML) { _pTopCML = pCML; }
CCliModalLoop *GetTopCML(void) { return _pTopCML; }
// modal loop helper functions
SWindowData *GetWindowData(UINT i) { return &_WD[i]; }
DWORD GetCallTypeForInCall(CCliModalLoop **ppCML,
REFLID lid, DWORD dwCallCatIn);
// window registration/revocation methods (used by channel & dde)
void Register(HWND hWnd, UINT wFirstMsg, UINT wLastMsg);
void Revoke(HWND hWnd);
private:
IMessageFilter *_pMF; // app supplied Msg Filter
BOOL _fInMsgFilter; // TRUE when calling the Apps MF
CCliModalLoop *_pTopCML; // topmost Client Modal Loop
SWindowData _WD[2]; // RPC and DDE Window Data
};
//+-------------------------------------------------------------------------
//
// Method: CCliModalLoop::CCliModalLoop
//
// Synopsis: constructor for the client side modal loop
//
//+-------------------------------------------------------------------------
inline CCliModalLoop::CCliModalLoop(DWORD dwTIDCallee, DWORD dwMsgQInputFlag) :
_dwTIDCallee(dwTIDCallee),
_dwMsgQInputFlag(dwMsgQInputFlag),
_dwFlags(0) // all flags start FALSE
{
COleTls tls;
_lid = tls->LogicalThreadId;
// push self on top of the per apartment modal loop stack
_pACC = tls->pCallCtrl;
_pPrev = _pACC->GetTopCML();
_pACC->SetTopCML(this);
_dwTimeOfCall = GetTickCount(); // record start time of the call
// the rest of the fields are initialized when first used
ComDebOut((DEB_CALLCONT, "CCliModalLoop::CCliModalLoop at:%x\n", this));
}
//+-------------------------------------------------------------------------
//
// Method: CCliModalLoop::~CCliModalLoop
//
// Synopsis: destructor for the client side modal loop
//
//+-------------------------------------------------------------------------
inline CCliModalLoop::~CCliModalLoop()
{
// pop self off the per apartment modal loop stack by resetting the
// top of stack to the previous value.
_pACC->SetTopCML(_pPrev);
// repost any WM_QUIT message we intercepted during the call
if (_dwFlags & CMLF_QUITRECEIVED)
{
ComDebOut((DEB_CALLCONT, "posting WM_QUIT\n"));
PostQuitMessage(_wQuitCode);
}
ComDebOut((DEB_CALLCONT, "CCliModalLoop::~CCliModalLoop at:%x\n", this));
}
//+-------------------------------------------------------------------------
//
// Method: CCliModalLoop::StartTimer
//
// Synopsis: starts a timer when a call is rejected and the client
// wants to retry it later.
//
//+-------------------------------------------------------------------------
inline HRESULT CCliModalLoop::StartTimer(DWORD dwMilliSecToWait)
{
// Set time when we should awake and retry the call. Note that
// if the GetTickCount + dwMilliSecToWait wraps the timer, then
// we may wakeup earlier than expected, but at least we wont
// deadlock.
ComDebOut((DEB_CALLCONT,
"Timer installed for %lu msec.\n", dwMilliSecToWait));
_dwMillSecToWait = dwMilliSecToWait;
_dwWakeup = GetTickCount() + dwMilliSecToWait;
// caller should place the return value in _hr
return RPC_S_WAITONTIMER;
}
//+-------------------------------------------------------------------------
//
// Method: CCliModalLoop::IsTimerAtZero
//
// Synopsis: returns TRUE if the timer is not started or the timer has
// run down.
//
//+-------------------------------------------------------------------------
inline BOOL CCliModalLoop::IsTimerAtZero()
{
// if no timer installed, return TRUE
if (_hr != RPC_S_WAITONTIMER)
return TRUE;
DWORD dwTickCount = GetTickCount();
// the second test is in case GetTickCount wrapped during
// the call. see also the comment in StartTimer.
if (dwTickCount > _dwWakeup ||
dwTickCount < _dwWakeup - _dwMillSecToWait)
{
// this _hr will tell SendReceive to retransmit the call
_hr = RPC_E_SERVERCALL_RETRYLATER;
return TRUE;
}
return FALSE;
}
//+-------------------------------------------------------------------------
//
// Method: CCliModalLoop::TicksToWait
//
// Synopsis: returns the amount of time to wait for a message to arrive.
//
//+-------------------------------------------------------------------------
inline DWORD CCliModalLoop::TicksToWait()
{
if (_hr != RPC_S_WAITONTIMER)
return MAX_TICKS_TO_WAIT;
// waiting to retry a rejected call
DWORD dwTick = GetTickCount();
return (_dwWakeup < dwTick) ? 0 : _dwWakeup - dwTick;
}
//+-------------------------------------------------------------------------
//
// Method: CSrvCallState::CSrvCallState
//
// Synopsis: constructor for server side call state. Pushes the call
// state on the call control stack.
//
//+-------------------------------------------------------------------------
inline CSrvCallState::CSrvCallState(DWORD dwCallCatIn) :
_dwCallCatIn(dwCallCatIn)
{
// push self on top of the per apartment server call state stack
COleTls tls;
_pPrev = tls->pTopSCS;
tls->pTopSCS = this;
}
//+-------------------------------------------------------------------------
//
// Method: CSrvCallState::~CSrvCallState
//
// Synopsis: destructor for server side call state. Pops the call
// state off the call control stack.
//
//+-------------------------------------------------------------------------
inline CSrvCallState::~CSrvCallState()
{
// pop self on top of the per apartment server call state stack
COleTls tls;
tls->pTopSCS = _pPrev;
}
//+-------------------------------------------------------------------------
//
// Method: CAptCallCtrl::GetMsgFilter
//
// Synopsis: returns the IMessageFilter and set the flag indicating we
// are currently calling the IMF, so that apps are prevented
// from making outgoing calls while inside their IMF.
//
//+-------------------------------------------------------------------------
inline IMessageFilter *CAptCallCtrl::GetMsgFilter()
{
if (_pMF)
{
_fInMsgFilter = TRUE;
}
return _pMF;
}
#endif // __CALLCTRL_HXX__