NT4/private/ole32/com/remote/channelb.cxx
2020-09-30 17:12:29 +02:00

2709 lines
80 KiB
C++

//+---------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1993 - 1994.
//
// File: d:\nt\private\cairole\com\remote\channelb.cxx
//
// Contents: This module contains thunking classes that allow proxies
// and stubs to use a buffer interface on top of RPC for Cairo
//
// Classes: CHeaderBrain
// CChannelList
// CRpcChannelBuffer
//
// Functions: CHeaderBrain::GetInboundData
// CHeaderBrain::GetOutboundData
// CHeaderBrain::SetOutboundBase
// CHeaderBrain::SetInboundData
// CHeaderBrain::SetOutboundData
// CChannelList::Add
// CChannelList::Grow
// CChannelList::Cleanup
// CChannelList::DisconnectHdlr
// CChannelList::Lookup
// CChannelList::LookupControl
// CChannelList::DisconnectServiceHere
// CChannelList::DisconnectService
// ChannelInitialize
// ChannelRegisterProtseq
// ChannelUninitialize
// CRpcChannelBuffer::AddRef
// CRpcChannelBuffer::AppInvoke
// CRpcChannelBuffer::ComInvoke
// CRpcChannelBuffer::CRpcChannelBuffer
// CRpcChannelBuffer::FreeBuffer
// CRpcChannelBuffer::GetBuffer
// CRpcChannelBuffer::QueryInterface
// CRpcChannelBuffer::Release
// CRpcChannelBuffer::GetCallCategory
// CRpcChannelBuffer::SendReceive
// DebugCoSetRpcFault
// DllDebugObjectRPCHook
// ThreadInvoke
// ThreadSendReceive
//
// History: 22 Jun 93 AlexMi Created
// 31 Dec 93 ErikGav Chicago port
// 15 Mar 94 JohannP Added call category support.
// 09 Jun 94 BruceMa Get call category from RPC message
// 19 Jul 94 CraigWi Added support for ASYNC calls
// 01-Aug-94 BruceMa Memory sift fix
//
//----------------------------------------------------------------------
#include <ole2int.h>
#include <channelb.hxx>
#ifdef _CHICAGO_
#include <islocalp.hxx>
#endif // _CHICAGO_
extern "C"
{
#include "orpc_dbg.h"
}
#include "rpcdcep.h"
//+---------------------------------------------------------------------------
//
// Function: AppInvokeExceptionFilter
//
// Synopsis: Determine if the application as thrown an exception we want
// to report. If it has, then print out enough information for
// the 'user' to debug the problem
//
// Arguments: [lpep] -- Exception context records
//
// History: 6-20-95 kevinro Created
//
// Notes:
//
// At the moment, I was unable to get this to work for Win95, so I have
// commented out the code.
//
//----------------------------------------------------------------------------
#ifdef _CHICAGO_
//
// Win95 doesn't appear to support this functionality by default.
//
inline LONG
AppInvokeExceptionFilter(
LPEXCEPTION_POINTERS lpep
)
{
return(EXCEPTION_EXECUTE_HANDLER);
}
#else
#include <imagehlp.h>
#define SYM_HANDLE GetCurrentProcess()
#if defined(_M_IX86)
#define MACHINE_TYPE IMAGE_FILE_MACHINE_I386
#elif defined(_M_MRX000)
#define MACHINE_TYPE IMAGE_FILE_MACHINE_R4000
#elif defined(_M_ALPHA)
#define MACHINE_TYPE IMAGE_FILE_MACHINE_ALPHA
#elif defined(_M_PPC)
#define MACHINE_TYPE IMAGE_FILE_MACHINE_POWERPC
#else
#error( "unknown target machine" );
#endif
LONG
AppInvokeExceptionFilter(
LPEXCEPTION_POINTERS lpep
)
{
#if DBG == 1
BOOL rVal;
STACKFRAME StackFrame;
CONTEXT Context;
SymSetOptions( SYMOPT_UNDNAME );
SymInitialize( SYM_HANDLE, NULL, TRUE );
ZeroMemory( &StackFrame, sizeof(StackFrame) );
Context = *lpep->ContextRecord;
#if defined(_M_IX86)
StackFrame.AddrPC.Offset = Context.Eip;
StackFrame.AddrPC.Mode = AddrModeFlat;
StackFrame.AddrFrame.Offset = Context.Ebp;
StackFrame.AddrFrame.Mode = AddrModeFlat;
StackFrame.AddrStack.Offset = Context.Esp;
StackFrame.AddrStack.Mode = AddrModeFlat;
#endif
CairoleDebugOut((DEB_FORCE,"An Exception occurred while calling into app\n"));
CairoleDebugOut((DEB_FORCE,
"Exception address = 0x%x Exception number 0x%x\n",
lpep->ExceptionRecord->ExceptionAddress,
lpep->ExceptionRecord->ExceptionCode ));
CairoleDebugOut((DEB_FORCE,"The following stack trace is where the exception occured\n"));
CairoleDebugOut((DEB_FORCE,"Frame RetAddr mod!symbol\n"));
do
{
rVal = StackWalk(MACHINE_TYPE,SYM_HANDLE,0,&StackFrame,&Context,ReadProcessMemory,
SymFunctionTableAccess,SymGetModuleBase,NULL);
if (rVal)
{
ULONG Displacement;
PIMAGEHLP_SYMBOL sym;
IMAGEHLP_MODULE ModuleInfo;
LPSTR pModuleName = "???";
sym = SymGetSymFromAddr(SYM_HANDLE,StackFrame.AddrPC.Offset,&Displacement);
//
// If there is module name information available, then grab it.
//
if(SymGetModuleInfo(SYM_HANDLE,StackFrame.AddrPC.Offset,&ModuleInfo))
{
pModuleName = ModuleInfo.ModuleName;
}
if (sym != NULL)
{
CairoleDebugOut((DEB_FORCE,
"%08x %08x %s!%s + %x\n",
StackFrame.AddrFrame.Offset,
StackFrame.AddrReturn.Offset,
pModuleName,
sym->Name,
Displacement));
}
else
{
CairoleDebugOut((DEB_FORCE,
"%08x %08x %s!%08x\n",
StackFrame.AddrFrame.Offset,
StackFrame.AddrReturn.Offset,
pModuleName,
StackFrame.AddrPC.Offset));
}
}
} while( rVal );
SymCleanup( SYM_HANDLE );
#endif
return EXCEPTION_EXECUTE_HANDLER;
}
#endif // _CHICAGO_
#pragma code_seg(".orpc")
/***************************************************************************/
/* Defines. */
#define MAKE_WIN32( status ) \
MAKE_SCODE( SEVERITY_ERROR, FACILITY_WIN32, (status) )
// This should just return a status to runtime, but runtime does not
// support both comm and fault status yet.
#ifdef _CHICAGO_
#define RETURN_COMM_STATUS( status ) return (status)
#else
#define RETURN_COMM_STATUS( status ) RpcRaiseException( status )
#endif
/***************************************************************************/
/* Typedefs. */
// The size of this structure must be a multiple of 8.
typedef struct Inbound_Header
{
DWORD channel_id;
UUID logical_thread;
IID iid;
unsigned int proc_num;
DWORD callcat;
// Warning - This field must be last, see CHeaderBrain.
unsigned long debug_data_size;
} Inbound_Header;
// The size of this structure must be a multiple of 8.
typedef struct Outbound_Header
{
// This field has one of the following values: RPC_E_SERVERCALL_REJECTED,
// RPC_E_SERVERCALL_RETRYLATER, or S_OK.
HRESULT rejected;
// Warning - This field must be last, see CHeaderBrain.
unsigned long debug_data_size;
} Outbound_Header;
struct com_call : STHREADCALLINFO
{
com_call(TRANSMIT_FN fn,CALLCATEGORY callcat,DWORD tid=0)
: STHREADCALLINFO(fn, callcat, tid) { }
com_call(DISPATCH_FN fn,CALLCATEGORY callcat,REFLID lid)
: STHREADCALLINFO(fn, callcat, lid) { }
virtual ~com_call();
virtual STHREADCALLINFO *MakeAsyncCopy(STHREADCALLINFO *);
virtual BOOL FormulateAsyncReply();
// this ctor is used in conjunction with MakeAsyncCopy and sets up the vtable
// pointer; MakeAsyncCopy actually initializes the instance. A normal copy
// constructor isn't used because they aren't virtual.
com_call(INIT_VTABLE i) : STHREADCALLINFO(i) { }
RPCOLEMESSAGE *pmessage;
DWORD server_fault;
// The client and server specific parts cannot be a union because they
// both exists in the local case.
// Client side only.
BOOL first_try;
DWORD channel_id;
CRpcService *service; // (NULL on server side)
// Server side only (only used within CRpcChannelBuffer::ComInvoke/AppInvoke)
IRpcStubBuffer *stub;
CRpcChannelBuffer *channel;
};
struct disconnect_service_data : STHREADCALLINFO
{
disconnect_service_data (DISPATCH_FN fn,CALLCATEGORY callcat,REFLID lid)
: STHREADCALLINFO(fn, callcat, lid) { }
virtual ~disconnect_service_data()
{ if (service != NULL) service->Release(); }
CRpcService *service; // NULL or valid
};
// This structure contains a copy of all the information needed to make a
// call. It is copied so it can be canceled without stray pointer references.
struct working_call : com_call
{
// we only construct this on the transmitting side.
working_call(TRANSMIT_FN fn,CALLCATEGORY callcat,DWORD tid=0)
: com_call(fn, callcat, tid) { }
// this ctor is used in conjunction with MakeAsyncCopy and sets up the vtable
// pointer; MakeAsyncCopy actually initializes the instance. A normal copy
// constructor isn't used because they aren't virtual.
working_call(INIT_VTABLE i) : com_call(i) { }
RPCOLEMESSAGE message;
};
//-------------------------------------------------------------------------
//
// Function: GetInterfaceName
//
// synopsis: Gets the human readable name of an Interface given it's IID.
//
// History: 12-Jun-95 Rickhi Created
//
//-------------------------------------------------------------------------
LONG GetInterfaceName(REFIID riid, WCHAR *pwszName)
{
// Read the registry entry for the interface to get the interface name
LONG ulcb=256;
WCHAR szKey[80];
lstrcpyW(szKey, L"Interface\\");
StringFromGUID2(riid,&szKey[10],80);
LONG result = RegQueryValue(
HKEY_CLASSES_ROOT,
szKey,
pwszName,
&ulcb);
return result;
}
/***************************************************************************/
/* Classes. */
/* This class manages the various headers in a COM packet. For inbound packets
the Inbound_Header is followed by the optional debug data which is followed
by the users data. For outbound packets, the Outbound_Header is followed by
the optional debug data which is followed by the users data.
The space for the debug data is reserved in the following cases:
client get buf to send receive
server ThreadInvoke to stub invoke
server get buffer to the return of ThreadInvoke
client send receive to client free buffer
The debug data is only valid, however, from the SendReceive in the client
to the beginning of AppInvoke in the server and from the end of AppInvoke
on the server back to SendReceive on the client. All cases that write
a debug size on to the tail end of the debug information (and thus corrupt
it) are outside these times of validity.
The following terms are used to create the function names:
Get Get some data about the headers.
Set Set some data about the headers.
Inbound A packet going from client to server (request).
Outbound A packet going from server to client (reply).
Base Pointer to the Channel header (Inbound_Header or Outbound_Header).
Debug Pointer to the debug data.
Data Pointer to the user data.
Warning: the methods of this class are full of side effects (data destroying
side effects). The methods must be called in a particular order.
Client:
GetBuffer
SetDebugSize
SetBase
GetInboundData
SendReceive
SetInboundData
GetBase or GetDebugSize
GetInboundDebug
I_RpcSendReceive
SetOutboundBase
GetDebugSize
GetOutboundDebug
GetOutboundData
FreeBuffer
SetOutboundData
GetBase or GetDebugSize
Server:
AppInvoke:
SetBase
SetDebugSize
GetDebugSize
GetInboundDebug
GetInboundData
stub
SetOutboundData
GetBase
GetDebugSize
GetOutboundDebug
GetBuffer
SetInboundBase
GetBase
SetDebugSize
GetDebugSize
I_RpcGetBuffer
SetBase
GetOutboundData
GetDebugSize
GetBase or GetDebugSize
*/
class CHeaderBrain
{
public:
CHeaderBrain() { debug_size = 0; };
RPCOLEMESSAGE *GetBase() { return (RPCOLEMESSAGE *) base; };
inline void *GetInboundData();
inline void *GetOutboundData();
void *GetInboundDebug() { return base+sizeof(Inbound_Header); };
void *GetOutboundDebug() { return base+sizeof(Outbound_Header); };
ULONG GetDebugSize() { return debug_size; };
inline void SetBase( void *param ) { base = (char *) param; };
inline void SetInboundData( void * );
inline void SetOutboundBase( void *param );
inline void SetOutboundData( void * );
inline void SetDebugSize( ULONG param ) { debug_size = param; };
private:
char *base;
ULONG debug_size;
};
/*
Get the Inbound user data pointer. At this time the debug header size is
stashed away just before the user data. Saving the debug size corrupts the
debug data if present. If not present, it ends up being written back to
the debug size field in the Inbound_Header.
*/
inline void *CHeaderBrain::GetInboundData()
{
ULONG *tmp = (ULONG *) (base + sizeof(Inbound_Header) + debug_size - sizeof(ULONG));
*tmp = debug_size;
return tmp+1;
}
/*
Get the Outbound user data pointer. At this time the debug header size is
stashed away just before the user data. Saving the debug size corrupts the
debug data if present. If not present, it ends up being written back to
the debug size field in the Outbound_Header.
*/
inline void *CHeaderBrain::GetOutboundData()
{
ULONG *tmp = (ULONG *) (base + sizeof(Outbound_Header) + debug_size - sizeof(ULONG));
*tmp = debug_size;
return tmp+1;
}
/*
Initialize a CHeaderBrain given a pointer to the Outbound_Header.
*/
inline void CHeaderBrain::SetOutboundBase( void *param )
{
base = (char *) param;
debug_size = ((Outbound_Header *) base)->debug_data_size;
};
/*
Initialize a CHeaderBrain given a pointer to the user data in an
inbound packet. Note that this is only called after GetInboundData
so the debug data size is stashed just before the user data.
Read the debug size and compute the real base.
*/
inline void CHeaderBrain::SetInboundData( void *param )
{
ULONG *tmp = ((ULONG *) param) - 1;
debug_size = *tmp;
base = ((char *) (tmp+1)) - debug_size - sizeof(Inbound_Header);
((Inbound_Header *) base)->debug_data_size = debug_size;
}
/*
Initialize a CHeaderBrain given a pointer to the user data in an
outbound packet. Note that this is only called after GetOutboundData
so the debug data size is stashed just before the user data.
Read the debug size and compute the real base.
*/
inline void CHeaderBrain::SetOutboundData( void *param )
{
ULONG *tmp = ((ULONG *) param) - 1;
debug_size = *tmp;
base = ((char *) (tmp+1)) - debug_size - sizeof(Outbound_Header);
((Outbound_Header *) base)->debug_data_size = debug_size;
}
/***************************************************************************/
//+-------------------------------------------------------------------
//
// Class: CCallCache
//
// Purpose: This class caches allocations on the working_call structure.
// The class keeps an array of up to CALLCACHE_SIZE elements.
// If there is an element in the array on Get, it is returned.
// Otherwise on if allocated. If there is space in the array
// on Free, the block is cached. Otherwise it is freed.
//
// Note that this class must use PrivMemAlloc because the
// caller is allowed to free them using PrivMemFree rather
// then calling Free. The channel controller frees the
// structures when calls are canceled.
//
// NOTE: this class is identical to the event cache except that the
// resource allocation and free is different. This suggests makeing this
// a virtual base class with virtual methods that do that allocation
// and free, and virtual methods that just do casting to the correct type
// for get and free.
//
// NOTE: this class *must* be allocated statically, as it holds a
// COleStaticMutexSem. We take advantage of the platform initializing static
// memory to 0, and so don't use a constructor for this class. If the
// Initialize member is modified to set something to other than 0, then this
// needs to be reworked.
//
// Interface:
//
// History: 28 June 94 AlexMit Created
//
//--------------------------------------------------------------------
const DWORD CALLCACHE_SIZE = 8;
class CCallCache
{
public:
void Cleanup();
void Free( working_call * );
working_call *Get();
void Initialize();
private:
working_call *list[CALLCACHE_SIZE];
DWORD next;
COleStaticMutexSem lock;
};
/***************************************************************************/
/* Prototypes. */
void ThreadInvoke ( RPC_MESSAGE *message );
HRESULT ThreadSendReceive ( STHREADCALLINFO * );
/***************************************************************************/
/* Globals. */
COleStaticMutexSem ChannelLock;
RPC_DISPATCH_FUNCTION vector =
(void (__stdcall *) (struct ::_RPC_MESSAGE *)) ThreadInvoke;
RPC_DISPATCH_TABLE the_dispatch_table =
{ 1, &vector, 0 };
RPC_SERVER_INTERFACE the_server_if =
{
sizeof(RPC_SERVER_INTERFACE),
{0x69C09EA0, 0x4A09, 0x101B, 0xAE, 0x4B, 0x08, 0x00, 0x2B, 0x34, 0x9A, 0x02,
{0, 0}},
{0x8A885D04, 0x1CEB, 0x11C9, 0x9F, 0xE8, 0x08, 0x00, 0x2B, 0x10, 0x48, 0x60,
{2, 0}},
&the_dispatch_table, 0, 0, 0
};
RPC_CLIENT_INTERFACE the_client_if =
{
sizeof(RPC_CLIENT_INTERFACE),
{0x69C09EA0, 0x4A09, 0x101B, 0xAE, 0x4B, 0x08, 0x00, 0x2B, 0x34, 0x9A, 0x02,
{0, 0}},
{0x8A885D04, 0x1CEB, 0x11C9, 0x9F, 0xE8, 0x08, 0x00, 0x2B, 0x10, 0x48, 0x60,
{2, 0}},
0, 0, 0, 0
};
// Has the app made remote calls?
BOOL MadeCalls = FALSE;
// Should the debugger hooks be called?
BOOL DoDebuggerHooks = FALSE;
LPORPC_INIT_ARGS DebuggerArg = NULL;
// List of channels.
CChannelList ChannelList;
CRpcChannelBuffer **CChannelList::_pList = NULL;
DWORD CChannelList::_ulLength = 0;
DWORD CChannelList::_dwSequence;
DWORD CChannelList::_dwFree = BAD_CHANNEL_ID;
COleStaticMutexSem CChannelList::_lock;
// Cache of working_call structures.
CCallCache CallCache;
/***************************************************************************/
/*
Insert a channel into the list of channels. The list is used to look up
server side channels during dispatch. The channel stays in the list until
it is disconnected, runs down, or is released (when the channel is released
the lookup function is called with the remove parameter true). In the
normal case, the client side channel holds the server side channel till
ReleaseChannel is called.
The channel list holds an addref for each pointer in the list. Thus,
the removal of an entry also releases it.
*/
DWORD CChannelList::Add(CRpcChannelBuffer *pChannel)
{
// single thread access
COleStaticLock lck(_lock);
// grab first entry on the free list
DWORD id = _dwFree;
if (id == BAD_CHANNEL_ID)
{
// there is no free space in the list, grow the list.
Win4Assert(((_ulLength + CLIST_EXPAND_SIZE) & CLIST_SEQUENCE_MASK) == 0);
id = Grow(_ulLength + CLIST_EXPAND_SIZE);
if (id == BAD_CHANNEL_ID)
{
// couldn't grow the list
return id;
}
}
// adjust the free list to next entry
_dwFree = (DWORD) _pList[id];
_pList[id] = pChannel;
id |= _dwSequence;
_dwSequence += CLIST_SEQUENCE_INC;
pChannel->AddRef();
pChannel->SetID( id );
return id;
}
/***************************************************************************/
DWORD CChannelList::Grow(ULONG ulNewSize)
{
CRpcChannelBuffer **pNewList = (CRpcChannelBuffer **)
PrivMemAlloc (ulNewSize * sizeof(CRpcChannelBuffer **));
if (pNewList)
{
if (_pList)
{
// there was an existing list, copy it over to the new list
// and free the old one.
memcpy(pNewList, _pList, _ulLength * sizeof(CRpcChannelBuffer **));
PrivMemFree(_pList);
}
// build the new free list
ULONG i;
for (i = _ulLength; i < ulNewSize-1; i++)
{
pNewList[i] = (CRpcChannelBuffer *) (i+1);
}
// set the last one to BAD so we know when to grow again.
pNewList[i] = (CRpcChannelBuffer *) BAD_CHANNEL_ID;
_dwFree = _ulLength;
_ulLength = ulNewSize;
_pList = pNewList;
}
// if the allocation failed, free_list is still BAD, if it succeeded,
// then free_list is the first free spot in the list.
return _dwFree;
}
/***************************************************************************/
void CChannelList::Cleanup()
{
_lock.Request();
#if DBG==1
// Make sure there are no channels left in the list.
ULONG i;
for (i = 0; i < _ulLength; i++)
if ((ULONG) _pList[i] > _ulLength &&
(ULONG) _pList[i] != BAD_CHANNEL_ID)
CairoleDebugOut((DEB_ERROR, "ChannelList is not empty on cleanup, channel = %x\n",
_pList[i]));
#endif
PrivMemFree( _pList );
_pList = NULL;
_ulLength = 0;
_dwFree = BAD_CHANNEL_ID;
_lock.Release();
}
/***************************************************************************/
/*
Note that this must be called on the thread the object lives on.
*/
void CChannelList::DisconnectHdlr( IRemoteHdlr *remote_handler )
{
CairoleDebugOut((DEB_CHANNEL, "DisconnectHdlr pRH:%x\n", remote_handler));
CRpcChannelBuffer *pChannel = NULL;
_lock.Request();
for (ULONG i = 0; i < _ulLength; i++)
{
if ((ULONG) _pList[i] > _ulLength &&
(ULONG) _pList[i] != BAD_CHANNEL_ID &&
(pChannel = _pList[i])->GetRH() == remote_handler)
{
// Remove the channel from the list and release it.
_pList[i] = (CRpcChannelBuffer *) _dwFree;
_dwFree = i;
pChannel->SetID(BAD_CHANNEL_ID);
_lock.Release();
pChannel->DisconnectObject(0);
pChannel->Release();
_lock.Request();
}
}
_lock.Release();
}
/***************************************************************************/
CRpcChannelBuffer *CChannelList::Lookup(DWORD id, BOOL fRemove, BOOL fAddref)
{
// don't allow removing and not addrefing at the same time
// (since the channel pointer returned would likely be of no use).
Win4Assert(!fRemove || fAddref);
COleStaticLock lck(_lock);
if ((id & CLIST_ID_MASK) >= _ulLength )
return NULL;
CRpcChannelBuffer *pChannel = _pList[id & CLIST_ID_MASK];
if ((DWORD) pChannel < _ulLength ||
(DWORD) pChannel == BAD_CHANNEL_ID ||
pChannel->GetID() != id)
{
pChannel = NULL;
}
else
{
if (fAddref)
{
pChannel->AddRef();
pChannel->AssertValid(FALSE, FALSE); // not in assert, can do assert
}
if (fRemove)
{
_pList[id & CLIST_ID_MASK] = (CRpcChannelBuffer *) _dwFree;
_dwFree = id & CLIST_ID_MASK;
pChannel->SetID(BAD_CHANNEL_ID);
pChannel->Release();
}
}
return pChannel;
}
/***************************************************************************/
CChannelControl *CChannelList::LookupControl( DWORD id )
{
CChannelControl *controller = NULL;
Win4Assert( (id & CLIST_ID_MASK) < _ulLength );
COleStaticLock lck(_lock);
if ((id & CLIST_ID_MASK) >= _ulLength )
return NULL;
CRpcChannelBuffer *pChannel = _pList[id & CLIST_ID_MASK];
if ((DWORD) pChannel > _ulLength &&
(DWORD) pChannel != BAD_CHANNEL_ID &&
pChannel->GetID() == id)
{
controller = pChannel->GetControl();
controller->AddRef();
}
return controller;
}
/***************************************************************************/
DWORD CChannelList::LookupIdByOid( OID object, CRpcService *service, DWORD tid )
{
CRpcChannelBuffer *channel = NULL;
DWORD thread = GetCurrentThreadId();
DWORD id = BAD_CHANNEL_ID;
OID channel_oid;
_lock.Request();
for (ULONG i = 0; i < _ulLength; i++)
{
channel = _pList[i];
if ((ULONG) channel > _ulLength &&
(ULONG) channel != BAD_CHANNEL_ID &&
channel->my_thread == thread &&
channel->_dwClientTID == tid &&
channel->_pService == service)
{
channel->_pRH->GetObjectID( &channel_oid );
if (channel_oid == object)
{
id = channel->_ChannelID;
break;
}
}
}
_lock.Release();
return id;
}
/***************************************************************************/
/* static */
/*
This function disconnects all the channels on the current thread that
use the specified service object. Since this is private, it is obviously
only used by this class. I'll give you one guess which function calls
this one. If you can't figure it out, you should retire.
*/
/* static */
HRESULT CChannelList::DisconnectServiceHere( STHREADCALLINFO *callinfo )
{
CairoleDebugOut((DEB_CHANNEL, "DisconnectServiceHere\n"));
CRpcChannelBuffer *pChannel;
disconnect_service_data *msg = (disconnect_service_data *) callinfo;
DWORD thread = GetCurrentThreadId();
_lock.Request();
for (ULONG i = 0; i < _ulLength; i++)
{
if ((ULONG) _pList[i] > _ulLength &&
(ULONG) _pList[i] != BAD_CHANNEL_ID)
{
pChannel = _pList[i];
if (pChannel->GetServerApt() == thread)
if (msg->service == NULL ||
pChannel->GetService() == msg->service)
{
// Remove the channel from the list and release it.
_pList[i] = (CRpcChannelBuffer *) _dwFree;
_dwFree = i;
pChannel->SetID(BAD_CHANNEL_ID);
_lock.Release();
pChannel->DisconnectObject(0);
pChannel->Release();
_lock.Request();
}
}
}
_lock.Release();
if (msg->service != NULL)
{
msg->service->Release();
msg->service = NULL; // NULL out so dtor doesn't release again
}
return S_OK;
}
/***************************************************************************/
/*
This function disconnects all server channels that use a service object.
The function may be called on any thread and causes DisconnectObject
to be called on each relevent channel on the correct thread.
This function is called in two cases: rundown and process uninitialization.
On rundown, we want to notify all threads that use the
service object. On process uninitialize there are no other threads so
we can call DisconnectServiceHere for this thread.
NOTE: There is a special requirement that process uninitialize be
synchronous.
NOTE: This routine should be optimized to only post one message per
thread.
service == NULL means to disconnect all channel objects.
*/
void CChannelList::DisconnectService( CRpcService *pService )
{
CRpcChannelBuffer *pChannel;
CChannelControl *controller;
HRESULT result;
// If process uninitialize, just clean up this thread, it is the only
// one left.
if (pService == NULL)
{
disconnect_service_data msg(DisconnectServiceHere,
CALLCAT_INTERNALSYNC, GUID_NULL);
msg.service = pService;
DisconnectServiceHere( &msg );
}
// Clean up all threads.
else
{
// we don't care if this fails (which is unlikely, anyway) because
// we would rather cleanup on a bogus lid than not clean up.
LID lid;
(void)CoCreateAlmostGuid(&lid);
_lock.Request();
for (ULONG i = 0; i < _ulLength; i++)
{
if ((ULONG) _pList[i] > _ulLength &&
(ULONG) _pList[i] != BAD_CHANNEL_ID)
{
pChannel = _pList[i];
if (pChannel->GetService() == pService)
{
controller = pChannel->GetControl();
controller->AddRef();
_lock.Release();
// must construct a new msg for each call.
disconnect_service_data msg(DisconnectServiceHere,
CALLCAT_INTERNALSYNC, lid);
// NOTE - This can be optimized to avoid sending multiple
// requests to the same channel.
msg.service = pService;
pService->AddRef(); // released by DisconnectServiceHere or dtor
result = controller->GetToCOMThread( &msg );
controller->Release();
_lock.Request();
}
}
}
_lock.Release();
}
}
/***************************************************************************/
STDAPI ChannelProcessInitialize()
{
TRACECALL(TRACE_RPC, "ChannelInitialize");
Win4Assert( (sizeof(Inbound_Header) & 7) == 0 );
Win4Assert( (sizeof(Outbound_Header) & 7) == 0 );
// Initialize the channel list
ChannelList.AdjustSequence( GetCurrentProcessId() );
CallCache.Initialize();
#ifdef _CHICAGO_
// For chicago set up the global list of local services
lslLocalServices.Init();
#endif // _CHICAGO_
#ifndef _CHICAGO_
// create the service object for the LOCAL process and store
// it in the global service list.
SetLocalService( FindSRVFromEP(NULL, TRUE) );
if (LocalService())
{
LocalService()->AssertValid();
}
else
{
return E_OUTOFMEMORY;
}
#endif
return ChannelControlProcessInitialize();
}
//--------------------------------------------------------------------
//
// Function: ChannelRegisterProtseq
//
// synopsis:
//
//
// Algorithm:
//
//
// History: 23-Nov-92 Rickhi Created
//
//--------------------------------------------------------------------
STDAPI ChannelRegisterProtseq(WCHAR *pwszProtseq)
{
return LocalService()->RegisterProtseq(pwszProtseq);
}
//+-------------------------------------------------------------------
//
// Function: ChannelThreadUninitialize, public
//
// Synopsis: Uninitializes the channel subsystem per thread data.
//
// History: 23-Nov-93 Rickhi Created
//
// Notes: This is called at thread uninitialize, not process
// uninitialize.
//
//--------------------------------------------------------------------
STDAPI_(void) ChannelThreadUninitialize(void)
{
TRACECALL(TRACE_RPC, "ChannelThreadUninitialize");
// On Chicago, the local service object and service list are kept
// per apartment, whereas on Daytona they are per process. We clean
// up the per apartment stuff here and the per process stuff in
// ChannelProcessUninitialize.
Win4Assert(!FreeThreading
&& "ChannelThreadUninitialize called and Free Threading");
#ifdef _CHICAGO_
CRpcService *pSrv = LocalService();
if (pSrv)
{
// disconnect all client channels from the local service object
ChannelList.DisconnectService(pSrv);
}
#endif
// cleanup the channel controller, disallowing any more incoming
// calls to this apartment.
ChannelControlThreadUninitialize();
#ifdef _CHICAGO_
// release the local service object
if (pSrv != NULL)
{
pSrv->Release();
SetLocalService(NULL);
}
// cleanup the list of service objects
CSrvList *list = (CSrvList *) TLSGetServiceList();
if (list != NULL)
{
list->Cleanup();
TLSSetServiceList(NULL);
delete list;
}
#endif
}
//+-------------------------------------------------------------------
//
// Function: ChannelProcessUninitialize, public
//
// Synopsis: Uninitializes the channel subsystem global data.
//
// History: 23-Nov-93 Rickhi Created
//
// Notes: This is called at process uninitialize, not thread
// uninitialize.
//
//--------------------------------------------------------------------
STDAPI_(void) ChannelProcessUninitialize(void)
{
TRACECALL(TRACE_RPC, "ChannelProcessUninitialize");
// cleanup the channel controller process wide state
ChannelControlProcessUninitialize();
ChannelList.Cleanup();
#ifdef _CHICAGO_
// For chicago clean up the global list of local services
lslLocalServices.Uninit();
#else
// On Chicago, the local service object and service list are kept
// per apartment, wheras on Daytona they are per process. We clean
// up the per process stuff here and the per apartment stuff in
// ChannelThreadUninitialize.
// release the global local service object
CRpcService *pSrv = LocalService();
if (pSrv != NULL)
{
// BUGBUG: must release the local service before doing SetLocalService
// to NULL because CRpcService::AssertValid makes use of LocalService
// to determine if this is a client service object or the local service
// object. This info should be intrinsic to the object itself.
pSrv->Release();
SetLocalService(NULL);
}
// cleanup the list of service objects
sg_SrvList.Cleanup();
#endif // _CHICAGO_
CallCache.Cleanup();
return;
}
/***************************************************************************/
/* This is the destructor for a com_call.
*/
com_call::~com_call()
{
// Release the reply buffer.
if (pmessage != NULL)
DeallocateBuffer(pmessage);
// the message pointer is either NULL or points immediately after us
// and thus there is no need to free it separately.
Win4Assert(pmessage == NULL || (RPCOLEMESSAGE *)(this + 1) == pmessage);
// Release the service object.
if (service != NULL)
service->Release();
// NOTE: the stub and channel members are set and reset synchronously
// within ComInvoke and thus need no attention here.
}
/***************************************************************************/
/* Makes an copy of the message (which is returned); the original is untouched.
The copy has a new lid, separate event, addref'd pointers, etc.
*/
STHREADCALLINFO *com_call::MakeAsyncCopy(STHREADCALLINFO *thread)
{
Win4Assert(thread == NULL && "should be top-level async copy");
working_call *pwcall = CallCache.Get();
if (pwcall == NULL)
return NULL;
// we don't want to call a regular constructor here because that would
// do the initialization twice, but we do want to call some ctor to
// initialize the vtable pointers.
pwcall->working_call::working_call(init_vtable);
void *pBuffer = PrivMemAlloc8(pmessage->cbBuffer);
Win4Assert(((ULONG)pBuffer & 0x7) == 0 && "Buffer not aligned properly");
if (pBuffer != NULL && STHREADCALLINFO::MakeAsyncCopy(pwcall) != NULL)
{
// finish constructing the instance of working_call
// NULL this out so the dtor can tell that this is the server
// side and this value need not be released.
pwcall->service = NULL;
pwcall->pmessage = &pwcall->message;
memcpy(&pwcall->message, pmessage, sizeof(RPCOLEMESSAGE));
pwcall->message.Buffer = pBuffer;
memcpy(pBuffer, pmessage->Buffer, pmessage->cbBuffer);
// pretend local so we don't touch rpc for more buffers, etc.
pwcall->message.rpcFlags |= RPCFLG_LOCAL_CALL;
}
else
{
// could not allocate all pieces; nothing constructed so just free
PrivMemFree(pBuffer);
CallCache.Free(pwcall);
pwcall = NULL;
}
// Free the input buffer for local calls.
if (pmessage->rpcFlags & RPCFLG_LOCAL_CALL)
DeallocateBuffer( pmessage );
return pwcall;
}
// called to convert an incoming message into a successful async reply.
// returns TRUE if successful; FALSE if out of memory.
BOOL com_call::FormulateAsyncReply()
{
STHREADCALLINFO::FormulateAsyncReply();
// setup reply to original; royal kludge: this is what all async
// calls expect; only a return value.
pmessage->cbBuffer = sizeof(Outbound_Header) + 4;
if (pmessage->rpcFlags & RPCFLG_LOCAL_CALL)
{
pmessage->Buffer = PrivMemAlloc8(pmessage->cbBuffer);
if (pmessage->Buffer == NULL)
return FALSE;
}
else
{
if (I_RpcGetBuffer((RPC_MESSAGE *) pmessage) != RPC_S_OK)
return FALSE;
}
// NOTE: the debugger will follow the thread of execution into the
// async routine. The reply is made to the caller without debug
// information. This is simplest for now and may need to be changed.
// simulate success in method call
((Outbound_Header*)pmessage->Buffer)->rejected = SERVERCALL_ISHANDLED;
((Outbound_Header*)pmessage->Buffer)->debug_data_size = 0;
*(SCODE *)((Outbound_Header *)pmessage->Buffer + 1) =S_OK;
return TRUE;
}
/***************************************************************************/
STDMETHODIMP_(ULONG) CRpcChannelBuffer::AddRef( THIS )
{
// can't call AssertValid(FALSE) since it is used in asserts
InterlockedIncrement( (long *) &ref_count );
return ref_count;
}
/***************************************************************************/
// Static, but the compiler doesn't like to see that here.
#if DBG == 1
DWORD AppInvoke_break = 0;
DWORD AppInvoke_count = 0;
#endif
HRESULT CRpcChannelBuffer::AppInvoke( STHREADCALLINFO *thread )
{
com_call *call = (com_call *) thread;
RPCOLEMESSAGE *message = (RPCOLEMESSAGE *) call->pmessage;
CHeaderBrain fixup;
void *orig_buffer = message->Buffer;
ULONG orig_buffer_size = message->cbBuffer;
void *orig_stub_buffer;
HRESULT result;
#if DBG == 1
IID iidBeingCalled = ((Inbound_Header *) orig_buffer)->iid;
DWORD dwMethod = ((Inbound_Header *) orig_buffer)->proc_num;
#endif
// If the debugger is active or if there is debug data in the packet,
// notify the debugger.
fixup.SetBase( message->Buffer );
fixup.SetDebugSize( ((Inbound_Header *) message->Buffer)->debug_data_size );
message->iMethod = ((Inbound_Header *) message->Buffer)->proc_num;
if (!IsWOWThread() && (fixup.GetDebugSize() != 0 || DoDebuggerHooks))
{
void *iface;
if (!SUCCEEDED(call->stub->DebugServerQueryInterface( &iface )))
iface = NULL;
DebugORPCServerNotify( message,
((Inbound_Header *)orig_buffer)->iid,
call->channel,
iface,
NULL,
fixup.GetInboundDebug(),
fixup.GetDebugSize(),
DebuggerArg,
DoDebuggerHooks );
if (iface != NULL)
call->stub->DebugServerRelease( iface );
}
// Adjust the buffer.
message->Buffer = fixup.GetInboundData();
orig_stub_buffer = message->Buffer;
message->cbBuffer -= sizeof(Inbound_Header) +
fixup.GetDebugSize();
// Call the stub.
_try
{
TRACECALL(TRACE_RPC, "CRpcChannelBuffer::StubInvoke");
#if DBG == 1
//
// On a debug build, we are able to break on a call by serial number.
// This isn't really 100% thread safe, but is still extremely useful
// when debugging a problem.
//
DWORD dwBreakCount = ++AppInvoke_count;
CairoleDebugOut((DEB_CHANNEL, "AppInvoke(0x%x) calling method 0x%x iid %I\n",
dwBreakCount,dwMethod, &iidBeingCalled));
if(AppInvoke_break == dwBreakCount)
{
DebugBreak();
}
#endif
result = call->stub->Invoke( message, call->channel );
}
_except(AppInvokeExceptionFilter( GetExceptionInformation()))
{
result = RPC_E_SERVERFAULT;
call->server_fault = GetExceptionCode();
#if DBG == 1
//
// OLE catches exceptions when the server generates them. This is so we can
// cleanup properly, and allow the client to continue.
//
if (call->server_fault == STATUS_ACCESS_VIOLATION ||
call->server_fault == 0xC0000194 /*STATUS_POSSIBLE_DEADLOCK*/ ||
call->server_fault == 0xC00000AA /*STATUS_INSTRUCTION_MISALIGNMENT*/ ||
call->server_fault == 0x80000002 /*STATUS_DATATYPE_MISALIGNMENT*/ )
{
WCHAR iidName[256];
iidName[0] = 0;
char achProgname[256];
achProgname[0] = 0;
GetModuleFileNameA(NULL,achProgname,sizeof(achProgname));
GetInterfaceName(iidBeingCalled,iidName);
CairoleDebugOut((DEB_FORCE,
"OLE has caught a fault 0x%08x on behalf of the server %s\n",
call->server_fault,
achProgname));
CairoleDebugOut((DEB_FORCE,
"The fault occured when OLE called the interface %I (%ws) method 0x%x\n",
&iidBeingCalled,iidName,dwMethod));
Win4Assert(!"The server application has faulted processing an inbound RPC request. Check the kernel debugger for useful output. OLE can continue but you probably want to stop and debug the application.");
}
#endif
}
// For local calls, just free the in buffer.
if (call->pmessage->rpcFlags & RPCFLG_LOCAL_CALL)
PrivMemFree( orig_buffer );
// If an exception occurred before a new buffer was allocated,
// set the Buffer field to point to the original buffer.
if (message->Buffer == orig_stub_buffer)
{
// The buffer pointer in the message must be correct so RPC can free it.
if (call->pmessage->rpcFlags & RPCFLG_LOCAL_CALL)
message->Buffer = NULL;
else
message->Buffer = orig_buffer;
}
// An out buffer exists, get the pointer to the channel header.
else
{
Win4Assert( message->Buffer );
Win4Assert( (ULONG) orig_buffer > (ULONG) message->Buffer ||
(ULONG) message->Buffer > (ULONG) orig_buffer + orig_buffer_size );
fixup.SetOutboundData( message->Buffer );
message->Buffer = fixup.GetBase();
message->cbBuffer += sizeof(Outbound_Header) + fixup.GetDebugSize();
// If the call failed and the call is local, free the out buffer.
if (result != S_OK &&
call->pmessage->rpcFlags & RPCFLG_LOCAL_CALL)
{
PrivMemFree( message->Buffer );
message->Buffer = NULL;
}
}
// If successful, adjust the buffer.
if (result == S_OK)
{
((Outbound_Header *) message->Buffer)->rejected = S_OK;
// If the packet contains space for debugger data, let the debugger
// fill it.
if (fixup.GetDebugSize() != 0)
{
void *iface;
if (!SUCCEEDED(call->stub->DebugServerQueryInterface( &iface )))
iface = NULL;
DebugORPCServerFillBuffer(
message,
((Inbound_Header *)orig_buffer)->iid,
call->channel,
iface,
NULL,
fixup.GetOutboundDebug(),
fixup.GetDebugSize(),
DebuggerArg,
DoDebuggerHooks );
if (iface != NULL)
call->stub->DebugServerRelease( iface );
}
}
return result;
}
/***************************************************************************/
// Static, but the compiler doesn't like to see that here.
HRESULT CRpcChannelBuffer::ComInvoke( STHREADCALLINFO *thread )
{
TRACECALL(TRACE_RPC, "CRpcChannelBuffer::ComInvoke");
com_call *call = (com_call *) thread;
RPCOLEMESSAGE *message = (RPCOLEMESSAGE *) call->pmessage;
Inbound_Header *header = (Inbound_Header *) message->Buffer;
INTERFACEINFO32 iface; // holds addref'd pUnkServer
void *saved_buffer;
RPC_STATUS status;
DISPATCHDATA mf_data;
HRESULT result;
CairoleDebugOut((DEB_CHANNEL, "CRpcChannelBuffer::ComInvoke callinfo:%x header:%x buffer:%x ChannelId:%x\n",
thread, header, message->Buffer, header->channel_id));
// NOTE: return only by going to or falling through exit !!!!
// Unmarshal the channel id and get a channel pointer.
call->stub = NULL; // NULL out for error case
iface.pUnk = NULL; // NULL out for error case
call->channel = ChannelList.Lookup( header->channel_id, FALSE, TRUE );
if (call->channel == NULL || call->channel->IsConnected() != S_OK)
{
// channel gone or not connected
result = RPC_E_DISCONNECTED;
goto exit;
}
// Set the caller TID. This is needed by some interop code in order
// to do focus management via tying queues together. We first save the
// current one so we can restore later to deal with nested calls
// correctly.
DWORD TIDCallerSaved;
BOOL fLocalSaved;
TLSSetCallerTID(call->channel->GetClientTID(), call->channel->local,
&TIDCallerSaved, &fLocalSaved);
// Get the stub pointer from the channel. This holds the real server alive
// (by not allowing the RH to release its pointers) and is similar to what
// we did in 16bit OLE to stablize the app object during incoming calls.
// This call must be balanced with a call to IRH::FinishCall.
MadeCalls = TRUE;
call->stub = call->channel->GetRH()->LookupStub(
header->iid,
&iface.pUnk,
&result);
// the stub is held alive by the RH which is held alive by the channel
// which is held alive because we addref'd it (in Lookup above).
if (call->stub == NULL)
{
Win4Assert(result != S_OK);
Win4Assert(iface.pUnk == NULL);
goto exit;
}
// In multithreaded mode, call the app directly
if (FreeThreading)
result = AppInvoke( call );
// In singlethreaded mode, go through the call controller.
else
{
iface.iid = header->iid;
iface.wMethod = header->proc_num;
iface.callcat = (CALLCATEGORY) header->callcat;
mf_data.pData = call;
// change the dispatch fn to AppInvoke and save the old one.
DISPATCH_FN fnPrev = call->ResetDispatchFn(AppInvoke);
result = call->channel->GetControl()
->GetCallControl()->HandleDispatchCall(
call->channel->GetClientTID(),
call->lid(),
&iface,
&mf_data );
// If the call was rejected, send the request back.
if (result == RPC_E_SERVERCALL_REJECTED ||
result == RPC_E_SERVERCALL_RETRYLATER)
{
// restore the original dispatch fn because we might call back in
// the reject or retry later case, and the STHREADCALLINFO is shared
// when SwitchCOMThread is used.
call->ResetDispatchFn(fnPrev);
// Note - Rather then copy the entire message twice, the inbound
// header and all the data is sent back. ThreadSendReceive and
// CRpcChannelBuffer::SendReceive detect this condition.
// Reuse the same buffer for local calls, copy it for remote calls.
if ((call->pmessage->rpcFlags & RPCFLG_LOCAL_CALL) == 0)
{
// Note that runtime does not free the original buffer until this
// function returns.
saved_buffer = message->Buffer;
// Note - We don't need to set reserved1 on the server side (unlike
// we do on the client side where we need to restore it before we
// reuse the buffer).
status = I_RpcGetBuffer( (RPC_MESSAGE *) message );
if (status != RPC_S_OK)
{
result = MAKE_WIN32( status );
goto exit;
}
memcpy( message->Buffer, saved_buffer, message->cbBuffer );
((Outbound_Header *) message->Buffer)->rejected = result;
}
}
}
exit:
// restore the caller TID. The latter two params are just
// dummy placeholders.
TLSSetCallerTID(TIDCallerSaved, fLocalSaved,
&TIDCallerSaved, &fLocalSaved);
if (call->channel != NULL)
{
call->channel->GetRH()->FinishCall(call->stub, iface.pUnk);
call->channel->Release();
}
return result;
}
/***************************************************************************/
CRpcChannelBuffer::CRpcChannelBuffer( IRemoteHdlr *rh,
CRpcService *service,
DWORD dwClientTID,
CChannelControl *chc,
EChannelState eState )
{
// Note that server_apt and controller are set correctly on the client
// side by UnmarshalInterface.
ref_count = 1;
_pRH = rh;
if (eState == server_cs)
{
// on server, we hold the rh alive so stub stay alive; released in dtor
rh->AddRef();
}
state = eState;
_pService = service;
_ChannelID = BAD_CHANNEL_ID;
_ulMarshalCnt = 0;
_fStrongConn = TRUE;
_dwClientTID = dwClientTID;
my_thread = GetCurrentThreadId();
server_apt = my_thread;
controller = chc;
connect_sync = NULL;
// The local flag is not valid on the client side till UnmarshalInterface
// is called. Chicago does not use local calls, but it still uses the
// local flag in the message structure for async calls.
local = service == LocalService();
if (controller != NULL)
controller->AddRef();
if (service != NULL)
service->AddRef();
}
/***************************************************************************/
CRpcChannelBuffer::~CRpcChannelBuffer()
{
DisconnectObject(0); // this call releases the controller and service objects
if (state == server_cs)
{
// on server we hold the rh alive through here because it simlifies error
// handling by making the server channel case look like other cases
// (which always have a valid _pRH).
_pRH->Release();
}
}
/***************************************************************************/
STDMETHODIMP CRpcChannelBuffer::FreeBuffer( RPCOLEMESSAGE *pMessage )
{
TRACECALL(TRACE_RPC, "CRpcChannelBuffer::FreeBuffer");
CHeaderBrain fixup;
AssertValid(FALSE, TRUE);
Win4Assert(state != disconnected_cs);
if (pMessage->Buffer == NULL)
return S_OK;
// If SendReceive has not been called, remove the inbound header.
if (pMessage->reserved2[1] != NULL)
{
fixup.SetInboundData( pMessage->Buffer );
pMessage->Buffer = fixup.GetBase();
DeallocateBuffer(pMessage);
}
// Remove the outbound header.
else
{
fixup.SetOutboundData( pMessage->Buffer );
pMessage->Buffer = fixup.GetBase();
pMessage->reserved2[1] = &the_client_if;
DeallocateBuffer(pMessage);
// Release the AddRef we did earlier. Note that we cant do this until
// after DeallocateBuffer since it may release a binding handle that
// I_RpcFreeBuffer needs.
_pRH->UnLockClient();
}
pMessage->Buffer = NULL;
return S_OK;
}
/***************************************************************************/
/*
This routine allocates buffers for proxies and stubs. It is called on
both the client and server side. It is called for both process local and
process remote calls. By contract with RPC, if it fails it should not change
the buffer pointer in the message.
*/
STDMETHODIMP CRpcChannelBuffer::GetBuffer( RPCOLEMESSAGE *pMessage,
REFIID riid )
{
TRACECALL(TRACE_RPC, "CRpcChannelBuffer::GetBuffer");
RPC_STATUS status;
CHeaderBrain fixup;
ULONG method;
CALLCATEGORY callCat = CALLCAT_SYNCHRONOUS;
void *buffer;
Win4Assert(state != disconnected_cs);
// Check the thread.
if (!FreeThreading && GetCurrentThreadId() != my_thread)
return RPC_E_WRONG_THREAD;
AssertValid(FALSE, TRUE);
// On the client side, compute the call category and add space for the
// Inbound_Header.
if (state == client_cs)
{
// Fetch the call category from the RPC message structure
if (pMessage->rpcFlags & RPCFLG_ASYNCHRONOUS)
{
// only allow async for these two interfaces for now
if (riid != IID_IAdviseSink && riid != IID_IAdviseSink2)
return E_UNEXPECTED;
callCat = CALLCAT_ASYNC;
}
else if (pMessage->rpcFlags & RPCFLG_INPUT_SYNCHRONOUS)
{
callCat = CALLCAT_INPUTSYNC;
}
// Note - RPC requires that the 16th bit of the proc num be set because
// we use the rpcFlags field of the RPC_MESSAGE struct.
method = pMessage->iMethod & ~RPC_FLAGS_VALID_BIT;
pMessage->iMethod = RPC_FLAGS_VALID_BIT;
// if service object of destination is in same process, definitely local
// calls; async calls are also forced to be local.
if (local)
pMessage->rpcFlags |= RPCFLG_LOCAL_CALL;
// Find out if we need debug data.
if (!IsWOWThread() && DoDebuggerHooks)
fixup.SetDebugSize(
DebugORPCClientGetBufferSize( pMessage, riid, NULL, NULL,
DebuggerArg, DoDebuggerHooks ) );
// Adjust the rest of the message.
if ((pMessage->rpcFlags & RPCFLG_LOCAL_CALL) == 0)
pMessage->reserved1 = _pService->GetRpcHdl();
/*
else
Why allocate the RPC resources for the local case?
*/
pMessage->cbBuffer += sizeof(Inbound_Header) +
fixup.GetDebugSize();
pMessage->reserved2[1] = &the_client_if;
}
// On the server side add space for the Outbound_Header.
else
{
// Find out if we need debug data.
fixup.SetInboundData( pMessage->Buffer );
if (!IsWOWThread() && DoDebuggerHooks)
{
HRESULT result;
void *iface;
Inbound_Header *header;
header = (Inbound_Header *) fixup.GetBase();
// NOTE: don't get pUnkServer because we don't need it and we don't
// want any server code to execute yet
IRpcStubBuffer *stub = _pRH->LookupStub(
header->iid,
NULL,
&result);
if (SUCCEEDED(result))
result = stub->DebugServerQueryInterface( &iface );
if (!SUCCEEDED(result))
iface = NULL;
fixup.SetDebugSize(
DebugORPCServerGetBufferSize( pMessage, header->iid, this, iface,
NULL, DebuggerArg, DoDebuggerHooks ) );
if (iface != NULL)
stub->DebugServerRelease( iface );
}
else
fixup.SetDebugSize( 0 );
// Adjust the buffer size.
pMessage->cbBuffer += sizeof(Outbound_Header) + fixup.GetDebugSize();
}
// Get a buffer.
if (pMessage->rpcFlags & RPCFLG_LOCAL_CALL)
{
// NDR_DREP_ASCII | NDR_DREP_LITTLE_ENDIAN | NDR_DREP_IEEE
pMessage->dataRepresentation = 0x00 | 0x10 | 0x0000;
buffer = PrivMemAlloc8( pMessage->cbBuffer );
if (buffer == NULL)
status = RPC_S_OUT_OF_MEMORY;
else
{
status = RPC_S_OK;
pMessage->Buffer = buffer;
}
}
else
{
TRACECALL(TRACE_RPC, "I_RpcGetBuffer");
status = I_RpcGetBuffer( (RPC_MESSAGE *) pMessage );
}
if (status != RPC_S_OK)
{
pMessage->cbBuffer = 0;
TLSSetFault( MAKE_WIN32( status ) );
return MAKE_WIN32( status );
}
// On the client side, stick the channel id in the buffer and adjust
// the buffer pointer past the Inbound_Header.
fixup.SetBase( pMessage->Buffer );
if (state == client_cs)
{
((Inbound_Header *) pMessage->Buffer)->channel_id = _ChannelID;
// NOTE: logical thread is set the first time the message is sent
((Inbound_Header *) pMessage->Buffer)->iid = riid;
((Inbound_Header *) pMessage->Buffer)->proc_num = method;
((Inbound_Header *) pMessage->Buffer)->callcat = callCat;
// BUGBUG: for some reason, the (WORD) cast did not strip high word in all
// cases.
// Note - RPC requires that the 17th bit of the proc num be set because
// we use the rpcFlags field of the RPC_MESSAGE struct.
pMessage->Buffer = (char *) fixup.GetInboundData();
pMessage->cbBuffer -= sizeof(Inbound_Header) - fixup.GetDebugSize();
}
// On the server side, adjust the pointer past the Outbound_Header.
else
{
pMessage->Buffer = fixup.GetOutboundData();
pMessage->cbBuffer -= sizeof(Outbound_Header) - fixup.GetDebugSize();
}
return S_OK;
}
/***************************************************************************/
STDMETHODIMP CRpcChannelBuffer::GetDestCtx( DWORD FAR* lpdwDestCtx,
LPVOID FAR* lplpvDestCtx )
{
TRACECALL(TRACE_RPC, "CRpcChannelBuffer::GetDestCtx");
AssertValid(FALSE, FALSE);
Win4Assert(state != disconnected_cs);
if (_pService)
{
_pService->GetDestCtx(lpdwDestCtx, lplpvDestCtx);
}
else
{
#ifdef _CAIRO_
// CODEWORK: must determine proper destination context
*lpdwDestCtx = MSHCTX_NOSHAREDMEM;
#else
*lpdwDestCtx = MSHCTX_LOCAL;
#endif
if (lplpvDestCtx != NULL)
*lplpvDestCtx = NULL;
}
return S_OK;
}
/***************************************************************************/
STDMETHODIMP CRpcChannelBuffer::IsConnected( THIS )
{
// must be on right thread because because of a possible disconnect
// (this routine is primarily called on the client side anyway).
AssertValid(FALSE, TRUE);
Win4Assert(state != disconnected_cs); // don't allow here
if (_pService != NULL && _pService->IsConnected())
{
return S_OK;
}
else if (state == client_cs)
{
// known not connected, ensure cleaned up by disconnecting
// BUGBUG - Why is DisconnectObject called here?
_pRH->Disconnect();
return S_FALSE;
}
// BUGBUG - If you were to call DisconnectObject on a server channel,
// you would have to remove it from the channel list.
else
return S_FALSE;
}
/***************************************************************************/
STDMETHODIMP CRpcChannelBuffer::QueryInterface( THIS_ REFIID riid, LPVOID FAR* ppvObj)
{
AssertValid(FALSE, FALSE);
// IMarshal is queried more frequently than any other interface, so
// check for that first.
if (IsEqualIID(riid, IID_IMarshal))
{
*ppvObj = (IMarshal *) this;
}
else if (IsEqualIID(riid, IID_IUnknown) ||
IsEqualIID(riid, IID_IRpcChannelBuffer))
{
*ppvObj = (IRpcChannelBuffer *) this;
}
else
{
*ppvObj = NULL;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
/***************************************************************************/
STDMETHODIMP_(ULONG) CRpcChannelBuffer::Release( THIS )
{
// can't call AssertValid(FALSE) since it is used in asserts
ULONG retval = ref_count - 1;
if (InterlockedDecrement( (long*) &ref_count ) == 0)
{
delete this;
return 0;
}
else
{
return retval;
}
}
/***************************************************************************/
// BUGBUG: needs to be removed for the real product
//
//IOleWindow
#define GetWindow 3
//IOleInplaceActiveobject
#define iTranslateAccelerator 5
#define OnFrameWindowActivate 6
#define OnDocWindowActivate 7
#define ResizeBorder 8
// IOleInplaceUIWindow
#define GetBorder 5
#define RequestBorderSpace 6
#define SetBorderSpace 7
// IOleInplaceFrame
#define SetMenu 10
#define SetStatusText 12
// IOleInplacObject
#define SetObjectsRect 7
CALLCATEGORY CRpcChannelBuffer::GetCallCategory(REFIID refiid, WORD wMethod)
{
// instead of doing 6 GUID compares, we take advantage of the
// similarity of the GUID values for the interfaces we are interested
// in, and switch on the part that is unique.
//
// IID_IAdviseSink, 0000010f-0000-0000-C000-000000000046
// IID_IOleInplaceObject, 00000113-0000-0000-C000-000000000046
// IID_IOleWindow, 00000114-0000-0000-C000-000000000046
// IID_IOleInPlaceUIWindow, 00000115-0000-0000-C000-000000000046
// IID_IOleInPlaceFrame, 00000116-0000-0000-C000-000000000046
// IID_IOleInPlaceActiveObject 00000117-0000-0000-C000-000000000046
DWORD *ptr = (DWORD *) &refiid;
Win4Assert(wMethod > 2 && "Incorrect remoting of IUnknown");
// the last 3 dwords are the same for all the interfaces
if (*(ptr+1) == 0x00000000 &&
*(ptr+2) == 0x000000C0 &&
*(ptr+3) == 0x46000000)
{
switch (*ptr)
{
case 0x0000010f: // IID_IAdviseSink
Win4Assert(IsEqualIID(refiid, IID_IAdviseSink));
Win4Assert(wMethod < 8);
// all async calls
return CALLCAT_ASYNC;
case 0x00000113: // IID_IOleInPlaceObject
Win4Assert(IsEqualIID(refiid, IID_IOleInPlaceObject));
Win4Assert(wMethod < 9);
if (wMethod == SetObjectsRect ||
wMethod == GetWindow)
{
return CALLCAT_INPUTSYNC;
}
break;
case 0x00000114: // IID_IOleWindow
Win4Assert(IsEqualIID(refiid,IID_IOleWindow));
Win4Assert(wMethod < 5);
if (wMethod == GetWindow)
{
return CALLCAT_INPUTSYNC;
}
break;
case 0x00000115: // IID_IOleInPlaceUIWindow
Win4Assert(IsEqualIID(refiid, IID_IOleInPlaceUIWindow));
Win4Assert(wMethod < 9);
if (wMethod == GetBorder ||
wMethod == RequestBorderSpace ||
wMethod == SetBorderSpace ||
wMethod == GetWindow)
{
return CALLCAT_INPUTSYNC;
}
break;
case 0x00000116: // IID_IOleInPlaceFrame
Win4Assert(IsEqualIID(refiid, IID_IOleInPlaceFrame));
Win4Assert(wMethod < 15);
// IOleInPlaceFrame inherits from IOleInplaceUIWindow
// which in turn inherits from IOleWindow
if (wMethod == SetMenu ||
wMethod == SetStatusText ||
wMethod == GetBorder ||
wMethod == RequestBorderSpace ||
wMethod == SetBorderSpace ||
wMethod == GetWindow)
{
return CALLCAT_INPUTSYNC;
}
break;
case 0x00000117: // IID_IOleInplaceActiveObject
Win4Assert(IsEqualIID(refiid, IID_IOleInPlaceActiveObject));
Win4Assert(wMethod < 10);
if (wMethod == OnFrameWindowActivate ||
wMethod == iTranslateAccelerator ||
wMethod == OnDocWindowActivate ||
wMethod == ResizeBorder ||
wMethod == GetWindow)
{
return CALLCAT_INPUTSYNC;
}
break;
case 0x00000119: // IID_IOleInPlaceSite
Win4Assert(IsEqualIID(refiid, IID_IOleInPlaceSite));
Win4Assert(wMethod < 15);
// IID_IOleInplaceSite inherits from IOleWindow so it has
// one input sync call.
if (wMethod == GetWindow)
{
return CALLCAT_INPUTSYNC;
}
break;
default:
break;
}
}
return CALLCAT_SYNCHRONOUS;
}
/***************************************************************************/
STDMETHODIMP CRpcChannelBuffer::SendReceive( THIS_ RPCOLEMESSAGE *pMessage,
ULONG *status )
{
TRACECALL(TRACE_RPC, "CRpcChannelBuffer::SendReceive");
AssertValid(FALSE, TRUE);
Win4Assert( state == client_cs );
HRESULT result;
CHeaderBrain fixup;
BOOL sent_debug_data = FALSE;
working_call *call;
IID iid;
Inbound_Header *header;
// Allocate a call record.
call = CallCache.Get();
if (call == NULL)
{
FreeBuffer(pMessage);
*status = (ULONG) RPC_E_OUT_OF_RESOURCES;
return RPC_E_FAULT;
}
// we must ensure that we dont go away during this call. we will Release
// ourselves in the FreeBuffer call, or in the error handling at the
// end of this function.
_pRH->LockClient();
// Adjust the buffer pointer before the Inbound_Header and send it.
fixup.SetInboundData( pMessage->Buffer );
pMessage->Buffer = fixup.GetBase();
pMessage->cbBuffer += sizeof(Inbound_Header) + fixup.GetDebugSize();
header = (Inbound_Header *) pMessage->Buffer;
iid = header->iid;
// DWORD *tmp = (DWORD *) &iid;
// CairoleDebugOut((DEB_CHANNEL, "SendReceive calling method 0x%x iid %08x%08x%08x%08x\n",
// header->proc_num, tmp[0], tmp[1], tmp[2], tmp[3] ));
// If the debugger wants to put in data, call the debugger back.
if (fixup.GetDebugSize() != 0)
{
sent_debug_data = TRUE;
DebugORPCClientFillBuffer(
pMessage,
iid,
NULL,
NULL,
fixup.GetInboundDebug(),
fixup.GetDebugSize(),
DebuggerArg,
DoDebuggerHooks );
}
// Fill in the call information. Common case is ThreadSendReceive.
call->working_call::working_call(ThreadSendReceive,
(CALLCATEGORY)header->callcat,
server_apt);
call->SetCalledIID(header->iid);
call->message = *pMessage;
call->pmessage = &call->message;
call->service = _pService;
call->first_try = TRUE;
call->channel_id = _ChannelID;
_pService->AddRef();
// In the local case, switch directly to the server thread. Otherwise
// get to a RPC thread and call I_RpcSendReceive
if (pMessage->rpcFlags & RPCFLG_LOCAL_CALL)
{
Win4Assert(controller != NULL);
call->pmessage->reserved2[3] = NULL;
call->ResetDispatchFn(ComInvoke);
result = controller->SwitchCOMThread( (STHREADCALLINFO **) &call );
}
else
{
Win4Assert(controller == NULL);
result = CChannelControl::GetOffCOMThread( (STHREADCALLINFO **) &call );
}
// Call succeeded.
if (result == S_OK)
{
// The first assignment is a sneaky flag to let FreeBuffer know that
// the message now contains an out buffer rather then an in buffer.
pMessage->reserved2[1] = NULL;
pMessage->reserved1 = call->message.reserved1;
pMessage->Buffer = call->message.Buffer;
pMessage->cbBuffer = call->message.cbBuffer;
#ifdef _CHICAGO_
// No buffer was returned for async calls.
if (pMessage->rpcFlags & RPCFLG_ASYNCHRONOUS)
{
// since no buffer was returned, FreeBuffer wont be called, and
// hence we need to unlock the client here.
_pRH->UnLockClient();
}
else
#endif
{
// If the debugger is active or if the packet contains debug data,
// call the debugger.
fixup.SetOutboundBase( pMessage->Buffer );
if (!IsWOWThread() && (fixup.GetDebugSize() != 0 || DoDebuggerHooks))
{
DebugORPCClientNotify(
pMessage,
iid,
NULL,
NULL,
S_OK,
fixup.GetOutboundDebug(),
fixup.GetDebugSize(),
DebuggerArg,
DoDebuggerHooks );
}
// Adjust the buffer.
pMessage->Buffer = fixup.GetOutboundData();
pMessage->cbBuffer -= sizeof(Outbound_Header) - fixup.GetDebugSize();
}
call->pmessage = NULL; // don't free the message in the dtor
*status = S_OK;
}
else
{
// CODEWORK: may need to free the marshaled data here (say in the REJECTED
// case) if the server never unmarshaled it.
// If the result is server fault, get the exception code from the com_call.
if (result == RPC_E_SERVERFAULT)
{
*status = call->server_fault;
// Buffer already freed or will be freed by RPC;prevent dtor from doing so
call->pmessage = NULL;
}
// Everything else is a comm fault.
else
{
*status = result;
// If the call was rejected or the server requested a retry, free the
// return buffer in dtor. Note that CallRunModalLoop and
// HandleDispatchCall return different values to indicate reject.
if (result == RPC_E_CALL_REJECTED || result == RPC_E_RETRY)
{
// leave call->pmessage alone to get buffer freed
}
else if (result == RPC_E_CALL_CANCELED)
{
// if canceled, buffer (and whole call packet) already freed
}
else
{
// Only return server fault, comm fault, retried, rejected, or canceled.
result = RPC_E_FAULT;
call->pmessage = NULL;
}
}
if (!IsWOWThread() &&
(fixup.GetDebugSize() != 0 || DoDebuggerHooks || sent_debug_data))
{
DebugORPCClientNotify(
pMessage,
iid,
NULL,
NULL,
result,
NULL,
0,
DebuggerArg,
DoDebuggerHooks );
}
TLSSetFault( *status );
_pRH->UnLockClient();
// since result is almost always mapped to RPC_E_FAULT, display the
// real error here to assist debugging.
CairoleDebugOut((DEB_CHANNEL, "ORPC call failed. status = %x\n", *status));
}
// If the call was not canceled, free the working_call structure.
if (*status != RPC_E_CALL_CANCELED)
{
// rather than destroying the packet only to allocate a new one later,
// we call the destructor and save the packet. The destructor will
// free the message buffer if the pointer is not NULL'd above.
call->working_call::~working_call();
CallCache.Free( call );
}
return result;
}
#if DBG == 1
//+-------------------------------------------------------------------
//
// Member: CRpcChannelBuffer::AssertValid
//
// Synopsis: Validates that the state of the object is consistent.
//
// History: 25-Jan-94 CraigWi Created.
//
//--------------------------------------------------------------------
void CRpcChannelBuffer::AssertValid(BOOL fKnownDisconnected,
BOOL fMustBeOnCOMThread)
{
if (fKnownDisconnected)
{
// for better asserts when shutting down the RH
Win4Assert(state == client_cs || state == disconnected_cs || state == server_cs);
Win4Assert(_pService == NULL);
Win4Assert(_ChannelID == BAD_CHANNEL_ID);
}
if (fMustBeOnCOMThread && !FreeThreading)
Win4Assert(FreeThreading || my_thread == GetCurrentThreadId());
if (state == client_cs)
{
if (_pService == NULL)
{
// client, disconnected
if (ref_count >=1)
{
Win4Assert(_pRH->GetChannel(FALSE) == this);
}
Win4Assert(_ChannelID == BAD_CHANNEL_ID);
Win4Assert(_ulMarshalCnt == 0);
Win4Assert(_fStrongConn);
Win4Assert(connect_sync == 0);
Win4Assert(controller == NULL);
}
else
{
// client, connected
// _pService is not strictly an interface, but close enough
Win4Assert(ref_count >= 1);
_pService->AssertValid();
Win4Assert(_pRH->GetChannel(FALSE) == this);
Win4Assert(_ChannelID != BAD_CHANNEL_ID);
// can't validate _ChannelID since the value is only valid
// in the server process.
Win4Assert(_ulMarshalCnt > 0);
Win4Assert(_fStrongConn);
Win4Assert(connect_sync == 0);
if (local)
{
Win4Assert(controller != NULL);
}
else
{
Win4Assert(controller == NULL);
}
}
}
else if (state == connecting_cs)
{
// CODEWORK: fill in
Win4Assert(_pRH->GetChannel(FALSE) == this);
}
else if (state == disconnected_cs)
{
Win4Assert(_pService == NULL);
Win4Assert(_pRH->GetChannel(FALSE) == this);
Win4Assert(_ChannelID == BAD_CHANNEL_ID);
Win4Assert(_ulMarshalCnt == 0);
Win4Assert(_fStrongConn);
Win4Assert(connect_sync == 0);
Win4Assert(controller == NULL);
}
else if (state == server_cs)
{
// ref count can be 0 in various stages of connection and disconnection
Win4Assert(ref_count < 0x7fff && "Channel ref count unreasonably high");
if (_pService == NULL)
{
// server side, disconnected (connection in the midst of closing)
// Marshal count on other threads can change during this call.
if (fMustBeOnCOMThread)
{
Win4Assert(_ulMarshalCnt == 0);
Win4Assert(_ChannelID == BAD_CHANNEL_ID);
}
// CODEWORK: could assert that the channel is not in the channel list
}
else
{
// server side, connected
// Marshal count on other threads can change during this call.
if (fMustBeOnCOMThread)
{
Win4Assert(_ulMarshalCnt > 0);
_pService->AssertValid();
Win4Assert(controller != NULL);
}
}
// the _pRH pointer can not be NULL
Win4Assert(IsValidInterface(_pRH));
Win4Assert(_pRH->GetChannel(FALSE) != this);
Win4Assert(_fStrongConn == TRUE || _fStrongConn == FALSE);
Win4Assert(connect_sync == 0);
}
else
{
Win4Assert(!"Channel Object with invalid state");
}
}
#endif // DBG == 1
/***************************************************************************/
STDAPI_(ULONG) DebugCoGetRpcFault()
{
return TLSGetFault();
}
/***************************************************************************/
STDAPI_(void) DebugCoSetRpcFault( ULONG fault )
{
TLSSetFault( fault );
}
/***************************************************************************/
extern "C"
BOOL _stdcall DllDebugObjectRPCHook( BOOL trace, LPORPC_INIT_ARGS pass_through )
{
if (!IsWOWThread())
{
DoDebuggerHooks = trace;
DebuggerArg = pass_through;
return TRUE;
}
else
return FALSE;
}
/***************************************************************************/
/* This routine returns both comm status and server faults to the runtime
by raising exceptions. Some time in the future, the macro
RETURN_COMM_STATUS can be changed so this function returns comm status
values as its result. If FreeThreading is true, ComInvoke will throw
exceptions to indicate server faults. These will not be caught and will
propogate directly to the runtime. If FreeThreading is false, this routine
will communicate to the main thread using a thread_switch_data record which
will contain both the comm status and server fault indications.
NOTE:
This function switches to the 32 bit stack under WIN95.
An exception has to be caught while switched to the 32 bit stack.
The exceptions has to be pass as a value and rethrown again on the
16 bit stack (see SSInvoke in stkswtch.cxx)
*/
#ifdef _CHICAGO_
DWORD
#else
void
#endif
SSAPI(ThreadInvoke)(RPC_MESSAGE *message )
{
TRACECALL(TRACE_RPC, "ThreadInvoke");
BOOL success;
Inbound_Header *header;
HRESULT result;
// RPC currently does not always set the flags on the server side.
message->RpcFlags = 0;
// If thread aware, just call ComInvoke. ComInvoke will not catch
// exceptions so they will fall straight through to the runtime.
header = (Inbound_Header *) message->Buffer;
// NOTE: we throw exceptions and routines we call can throw them so beware!
com_call call(
CRpcChannelBuffer::ComInvoke,
(CALLCATEGORY) header->callcat,
header->logical_thread);
call.pmessage = (RPCOLEMESSAGE *) message;
call.service = NULL;
if (FreeThreading)
{
success = TLSSetLogicalThread( ((Inbound_Header *) message->Buffer)->logical_thread );
if (!success)
result = RPC_E_SYS_CALL_FAILED;
else
result = CRpcChannelBuffer::ComInvoke( &call );
// CODEWORK: the above routine can throw exceptions; we probably want to
// catch them here, cleanup and re-throw them.
}
// Pass the message to the app thread.
else
{
CChannelControl *controller;
controller = ChannelList.LookupControl( header->channel_id );
if (controller != NULL)
{
result = controller->GetToCOMThread( &call );
controller->Release();
}
else
{
result = RPC_E_SERVER_DIED_DNE;
}
}
call.pmessage = NULL; // that was not our message; must not free
// NOTE: these exceptions will avoid the com_call destructor, so we call it
// For comm and server faults, generate an exception. Otherwise the buffer
// is set up correctly. Sometimes GetToCOMThread will rethrow
// server faults.
if (result == RPC_E_SERVERFAULT)
{
#if !defined(_CHICAGO_)
call.com_call::~com_call();
#endif
RETURN_COMM_STATUS( (ULONG)RPC_E_SERVERFAULT );
}
else if (result != RPC_E_SERVERCALL_REJECTED &&
result != RPC_E_SERVERCALL_RETRYLATER &&
result != S_OK)
{
#if !defined(_CHICAGO_)
call.com_call::~com_call();
#endif
RETURN_COMM_STATUS( result );
}
// else dtor called normally
#ifdef _CHICAGO_
return 0;
#endif //_CHICAGO_
}
/***************************************************************************/
HRESULT ThreadSendReceive( STHREADCALLINFO *thread )
{
TRACECALL(TRACE_RPC, "ThreadSendReceive");
working_call *call = (working_call *) thread;
HRESULT result;
RPCOLEMESSAGE *message = call->pmessage;
RPC_MESSAGE free_me;
RPC_STATUS status;
// The first time this is transmitted, fill in the logical thread id.
if (call->first_try)
{
// Save the logical thread. Also, set the first_try field so the next
// time this routine is called with this call data it will retransmit
// the data.
call->first_try = FALSE;
((Inbound_Header *) message->Buffer)->logical_thread = call->lid();
}
// Get a new buffer. Copy the reply. Free the reply.
else
{
// Note: this reply was generated in CRpcChannelBuffer::ComInvoke.
// The Inbound header is still present, except for the first field as noted.
free_me = *((RPC_MESSAGE *) message);
message->reserved1 = call->service->GetRpcHdl();
status = I_RpcGetBuffer( (RPC_MESSAGE *) message );
if (status != RPC_S_OK)
{
I_RpcFreeBuffer( (RPC_MESSAGE *) &free_me );
return MAKE_WIN32( status );
}
memcpy( message->Buffer, free_me.Buffer, message->cbBuffer );
// Note: On reject/retry the first field is overwritten with the return
// code (see CRpcChannelBuffer::ComInvoke). Here we restore it.
((Inbound_Header *) message->Buffer)->channel_id = call->channel_id;
I_RpcFreeBuffer( (RPC_MESSAGE *) &free_me );
}
// Call the runtime. In the future, detect server faults and
// change the value of result to RPC_E_SERVERFAULT.
{
#ifdef _CHICAGO_
TRACECALL(TRACE_RPC, "I_RpcAsyncSendReceive");
result = I_RpcAsyncSendReceive( (RPC_MESSAGE *) message, thread );
#else
TRACECALL(TRACE_RPC, "I_RpcSendReceive");
result = I_RpcSendReceive( (RPC_MESSAGE *) message );
#endif
}
// If the result is small, it is probably a Win32 code.
if (result != 0)
{
if ((ULONG) result > 0xfffffff7 || (ULONG) result < 0x2000)
result = MAKE_WIN32( result );
if (result != MAKE_WIN32( RPC_S_CALL_CANCELLED ))
message->Buffer = NULL;
// Tell the call destructor not to free the message if it was canceled.
else
{
call->pmessage = NULL;
result = RPC_E_CALL_CANCELED;
}
}
// Check to see if the packet was rejected by the message filter.
else
{
#ifdef _CHICAGO_
// No buffer is returned for asynchronous calls.
if ((message->rpcFlags & RPCFLG_ASYNCHRONOUS) == 0)
#endif
result = ((Outbound_Header *) message->Buffer)->rejected;
}
return result;
}
/***************************************************************************/
void CCallCache::Cleanup()
{
DWORD i;
// Release everything.
lock.Request();
if (next <= CALLCACHE_SIZE)
{
for (i = 0; i < next; i++)
if (list[i] != NULL)
{
// use (void *) to avoid cast to real CPrivAlloc
PrivMemFree( (void *)list[i] );
list[i] = NULL;
}
// Make sure no late frees get cached.
next = CALLCACHE_SIZE + 1;
}
lock.Release();
}
/***************************************************************************/
// given a destroyed working_call, put it on the free list or just free it.
void CCallCache::Free( working_call *call )
{
// Add the structure to the list if the list is not full.
lock.Request();
if (next < CALLCACHE_SIZE)
{
list[next] = call;
next += 1;
}
// Otherwise just free it.
else
{
// use (void *) to avoid cast to real CPrivAlloc
PrivMemFree( (void *)call );
}
lock.Release();
}
/***************************************************************************/
// returns unconstructed working_call
working_call *CCallCache::Get()
{
working_call *call;
// Get the last entry from the cache.
lock.Request();
if (next > 0 && next < CALLCACHE_SIZE+1)
{
next -= 1;
call = list[next];
list[next] = NULL;
}
// If there are none, allocate a new one.
else
call = (working_call *) PrivMemAlloc(sizeof(*call));
lock.Release();
return call;
}
/***************************************************************************/
void CCallCache::Initialize()
{
// Zero everything.
lock.Request();
next = 0;
memset( list, 0, sizeof(list) );
lock.Release();
}