//+-------------------------------------------------------------------
//
//  File:       remhdlr.cxx
//
//  Contents:   remote object handler implementation
//
//  Classes:    CRemoteHdlr - remote object handler
//              CPSIX       - proxy/stub interface wrapper
//
//  History:    23-Nov-92   Rickhi      Created
//              05-Jul-94   BruceMa     Check for end of stream
//
//--------------------------------------------------------------------

#include <ole2int.h>

#include    <remhdlr.hxx>       //  CRemoteHdlr

CMutexSem   CRemoteHdlr::_mxs;  //  global mutext semaphore


#pragma warning(disable:4355)   // 'this' used in base member init list
//+-------------------------------------------------------------------
//
//  Member:     CRemoteHdlr::CRemoteHdlr, public
//
//  Synopsis:   constructor for the remote handler object
//
//  History:	23-Nov-93   Rickhi	Created
//		01-Aug-94   Alexgo	Allocate the ChannelBuffer and
//					return failure if E_OUTOFMEMORY
//
//--------------------------------------------------------------------

CRemoteHdlr::CRemoteHdlr(IUnknown      *punkObj,    // object ptr
			 IWeakRef      *pWR,	    // IWR* of object if one
			 IStdIdentity  *pStdID,	    // identity
			 DWORD		dwFlags,    // flags
			 HRESULT	&hr)	    // return code
    : _punkObj(punkObj),
      _pWR(pWR),
      _pstdID(pStdID),
      _dwFlags(dwFlags),
      _cReferences(1),
      _cCallsInProgress(0),
      _pChannel(NULL)
{
    Win4Assert(IsValidInterface(_punkObj));

    // create an Rpc Channel for this object. we need to tell it if this is
    // for a local object, or a remote object, so that the channel can point
    // to the correct service object.

    _pChannel = new CRpcChannelBuffer(this, NULL, 0, NULL,
				   IS_LOCAL_RH ? disconnected_cs : client_cs);
    if (_pChannel == NULL)
    {
	hr = E_OUTOFMEMORY;
	return;
    }

    if (IS_LOCAL_RH)
    {
       // we (now) addref this as long as we hold its value; disconnect
       // is the only place that releases it and set it to NULL; many places
       // check for NULL.
       _punkObj->AddRef();

       if (_pWR != NULL)
       {
	   _pWR->AddRef();
	   _pWR->ChangeWeakCount(2);// for _punkObj and _pWR
       }
    }
    else
    {
	Win4Assert(pWR == NULL);
    }

    hr = S_OK;
    AssertValid();

    CairoleDebugOut((DEB_MARSHAL,
             "New CRemoteHdlr pRH:%x pChannel:%x\n",
             this, _pChannel));
}
#pragma warning(default:4355)  // 'this' used in base member init list



//+-------------------------------------------------------------------
//
//  Member:     CRemoteHdlr::QueryInterface, public
//
//  Synopsis:   returns internally supported interfaces
//
//  Exceptions: CException if input parameters are bad or
//              cant create the interface proxy.
//
//  History:    23-Nov-93   Rickhi       Created
//
//  Notes:      IUnknown and IMarshal are always private interfaces when
//      this object is aggregated, hence QI, AddRef, Release do not
//      get forwarded to the controlling unknown.
//
//--------------------------------------------------------------------

STDMETHODIMP CRemoteHdlr::QueryInterface(REFIID riid, void **ppv)
{
    HRESULT sc = S_OK;

    AssertValid();

    if (IsEqualIID(riid, IID_IMarshal) ||  //   more common than IUnknown
        IsEqualIID(riid, IID_IUnknown))
    {
        *ppv = (IMarshal *) this;
        AddRef();
    }
    else if (IsEqualIID(riid, IID_IRemoteHdlr))
    {
        *ppv = (IRemoteHdlr *) this;
        AddRef();
    }
    else
    {
	// don't expect this on the local side.
	Assert(!IS_LOCAL_RH);

	//  find or create the interface we are looking for, and AddRef it

        // BUGBUG: we probably want to stablize this QI here per new rules
        // i.e., _pUnkObj->AddRef(), Release() around the FindIX call.

	// call sets ppv and sc per normal rules (e.g., *ppv == NULL on error).
	(void)FindIX(riid, ppv, FLG_QUERYINTERFACE, &sc);
    }

    return sc;
}


//+-------------------------------------------------------------------
//
//  Member:     CRemoteHdlr::AddRef, public
//
//  Synopsis:   increments the reference count on the object.
//
//  History:    23-Nov-93   Rickhi       Created
//
//--------------------------------------------------------------------

STDMETHODIMP_(ULONG) CRemoteHdlr::AddRef(void)
{
    AssertValid();

    return InterlockedAddRef(&_cReferences);
}


//+-------------------------------------------------------------------
//
//  Member:     CRemoteHdlr::Release, public
//
//  Synopsis:   decrements the reference count and deletes the object if
//              it reaches zero.
//
//  History:    23-Nov-93   Rickhi       Created
//
//--------------------------------------------------------------------

STDMETHODIMP_(ULONG) CRemoteHdlr::Release(void)
{
    DWORD refs;
    AssertValid();

    if ((refs = InterlockedRelease(&_cReferences)) == 0)
    {
       delete this;
       return 0;
    }

    return refs;
}


//+-------------------------------------------------------------------
//
//  Member:     CRemoteHdlr::GetChannel, public
//
//  Synopsis:   Returns the channel; DBG only.
//
//  History:    06-Jan-94   CraigWi     Created.
//
//--------------------------------------------------------------------

STDMETHODIMP_(IRpcChannelBuffer *) CRemoteHdlr::GetChannel(BOOL fAddRef)
{
#if DBG == 1
    // can't call AssertValid() since it is used by the service object asserts

    // don't support fAddRef yet because the channel object is part of the RH
    Assert(!fAddRef);

    return _pChannel;
#else // !DBG
    return 0;
#endif
}


//+-------------------------------------------------------------------
//
//  Member:     CRemoteHdlr::LockClient, public
//
//  Synopsis:   prevents the client RH, and hence all attached proxies and
//      channels, from vanishing during an Rpc call.  We hold
//      pUnkOuter of the aggregate and the whole aggregate must
//      persist if any piece of it does.
//
//      NOTE: nothing is done on the server side; the server side
//      channel addref's the RH for the duration of the connection.
//      We can't do that on the client side since the pointer
//      from the channel to the RH is a back pointer (which are
//      not normally addref'd and further more the channel is embedded
//      in the RH and thus its ref count is not too meaningful).
//
//  CODEWORK: since this is really only interesting on the client side,
//  we can make this much simpler if the channel was an independent object
//  (by just addref'ing the channel object only).
//
//  History:    23-Nov-93   Rickhi       Created
//      21-Dec-93   CraigWi      Distinguish between local and remote
//       7-Apr-94   CraigWi      Only works remotely now
//
//--------------------------------------------------------------------

STDMETHODIMP_(ULONG) CRemoteHdlr::LockClient(void)
{
    AssertValid();

    if (!IS_LOCAL_RH && _punkObj != NULL)
       return _punkObj->AddRef();
    else
       return 0;
}


//+-------------------------------------------------------------------
//
//  Member:     CRemoteHdlr::UnLockClient, public
//
//  Synopsis:   See LockClient() above.
//
//  History:    23-Nov-93   Rickhi       Created
//      21-Dec-93   CraigWi      Distinguish between local and remote
//       7-Apr-94   CraigWi      Only works remotely now
//
//  Note:       Special magic that was required in the past is not longer
//      necessary since the RH is never has non-addref'd pointers
//      to it.  This complexity is now in the identity object.
//
//--------------------------------------------------------------------

STDMETHODIMP_(ULONG) CRemoteHdlr::UnLockClient(void)
{
    AssertValid();

    if (!IS_LOCAL_RH && _punkObj != NULL)
       return _punkObj->Release();
    else
       return 0;
}


//+-------------------------------------------------------------------
//
//  Member:     CRemoteHdlr::ClearIdentity, public
//
//  Synopsis:   Clears the _pstdID during identity destruction.
//
//  History:    21-Dec-93   CraigWi      Created
//
//--------------------------------------------------------------------

STDMETHODIMP_(void) CRemoteHdlr::ClearIdentity(void)
{
    AssertValid();

    // don't clear this on the client side since we may need it again
    if (IS_LOCAL_RH)
       _pstdID = NULL;
}


//+-------------------------------------------------------------------
//
//  Member:     CRemoteHdlr::DoesSupportIID, public
//
//  Synopsis:   returns TRUE if the interface is supported; works
//      on both client and server sides.
//
//  Arguments:  [riid]  -- The iid in question
//
//  History:    13-Dec-93   CraigWi     Created.
//
//--------------------------------------------------------------------

STDMETHODIMP_(BOOL) CRemoteHdlr::DoesSupportIID(REFIID riid)
{
    HRESULT hr;
    AssertValid();

    return FindIX(riid, NULL, IS_LOCAL_RH ? FLG_MARSHAL : FLG_QUERYINTERFACE, &hr) != NULL;
}


//+-------------------------------------------------------------------
//
//  Member:     CRemoteHdlr::AddConnection, private
//
//  Synopsis:   forwards call to identity object
//
//  History:    23-Nov-93   Rickhi       Created
//      21-Dec-93   CraigWi      Changed to handle weak as well
//      20-Apr-94   CraigWi      Change to forward to id object
//
//--------------------------------------------------------------------

STDMETHODIMP CRemoteHdlr::AddConnection(DWORD extconn, DWORD reserved)
{
    AssertValid();

    if (_pstdID != NULL)
       return _pstdID->AddConnection(extconn, reserved);
    else
       return CO_E_OBJNOTCONNECTED;
}


//+-------------------------------------------------------------------
//
//  Member:     CRemoteHdlr::ReleaseConnection, private
//
//  Synopsis:   records the removal of a connection (weak or strong)
//
//  History:    23-Nov-93   Rickhi       Created
//      21-Dec-93   CraigWi      Changed to handle weak as well
//      20-Apr-94   CraigWi      Change to forward to id object
//
//--------------------------------------------------------------------

STDMETHODIMP CRemoteHdlr::ReleaseConnection(DWORD extconn,
       DWORD reserved, BOOL fLastReleaseCloses)
{
    AssertValid();

    if (_pstdID != NULL)
       return _pstdID->ReleaseConnection(extconn, reserved,fLastReleaseCloses);
    else
       return CO_E_OBJNOTCONNECTED;
}


//+-------------------------------------------------------------------
//
//  Member:     CRemoteHdlr::IsConnected, private
//
//  Synopsis:   Returns TRUE if most likely connected; FALSE if definitely
//		not connected.  If returns FALSE, cleans up.
//
//  History:    14-Dec-93   CraigWi     Created.
//
//--------------------------------------------------------------------

STDMETHODIMP_(BOOL) CRemoteHdlr::IsConnected(void)
{
    AssertValid();
    Assert(!IS_LOCAL_RH);

    if (_pChannel->IsConnected() == S_OK)
    {
	return TRUE;
    }
    else
    {
	// clean up our data (channel already did)
	_IXList.DisconnectProxies();

	// BUGBUG: not sure about the fMustBeOnCOMThread setting
	_pChannel->AssertValid(TRUE, FALSE); // known disconnected
	return FALSE;
    }
}


//+-------------------------------------------------------------------
//
//  Member:     CRemoteHdlr::Disconnect, private
//
//  Synopsis:   same as IMarshal::Disconnect(0); this method is
//      present for the convenience of the identity object.
//
//  History:    14-Dec-93   CraigWi     Created.
//
//--------------------------------------------------------------------

STDMETHODIMP_(void) CRemoteHdlr::Disconnect(void)
{
    AssertValid();
    Assert(!IS_LOCAL_RH);

    DisconnectObject(0);
}

//+-------------------------------------------------------------------
//
//  Member:     CRemoteHdlr::LockConnection, private
//
//  Synopsis:   pass through to channel
//
//  History:    14-Dec-93   CraigWi     Created.
//
//--------------------------------------------------------------------

STDMETHODIMP CRemoteHdlr::LockConnection(BOOL fLock, BOOL fLastUnlockReleases)
{
    AssertValid();
    Assert(!IS_LOCAL_RH);

    return _pChannel->LockObjectConnection(fLock, fLastUnlockReleases);
}


//+-------------------------------------------------------------------
//
//  Member:     CRemoteHdlr::GetUnmarshalClass, public
//
//  Synopsis:   returns the class of the code used to unmarshal this
//              object
//
//  History:    23-Nov-93   Rickhi       Created
//
//--------------------------------------------------------------------

STDMETHODIMP CRemoteHdlr::GetUnmarshalClass(REFIID riid, void *pv,
                DWORD dwDestCtx, void *pvDestCtx,
                        DWORD mshlflags, LPCLSID pClassid)
{
    AssertValid();

    *pClassid = CLSID_StdMarshal;
    return S_OK;
}


//+-------------------------------------------------------------------
//
//  Member:     CRemoteHdlr::GetMarshalSizeMax, public
//
//  Synopsis:   returns the maximum sized buffer needed to marshal this
//              object
//
//  History:    23-Nov-93   Rickhi       Created
//
//--------------------------------------------------------------------

STDMETHODIMP CRemoteHdlr::GetMarshalSizeMax(REFIID riid, void *pv,
                DWORD dwDestCtx, void *pvDestCtx,
                        DWORD mshlflags, DWORD *pSize)
{
    AssertValid();

    // get the size that the channel needs
    HRESULT sc = _pChannel->GetMarshalSizeMax(riid, pv, dwDestCtx,
                              pvDestCtx, mshlflags, pSize);

    // add in the size the handler needs (assume clsid Channel)
#ifdef CODEWORK
    *pSize += sizeof(SHandlerDataHdr) + sizeof(CLSID);
#else
    *pSize += sizeof(SHandlerDataHdr);
#endif
    return sc;
}


//+-------------------------------------------------------------------
//
//  Member:     CRemoteHdlr::MarshalInterface, public
//
//  Synopsis:   marshalls the specified interface into the given stream
//
//  History:    23-Nov-93   Rickhi       Created
//
//  Note:       The format of the data is {SHandlerDataHdr,<channel Data>}
//
//--------------------------------------------------------------------

STDMETHODIMP CRemoteHdlr::MarshalInterface(IStream *pStm, REFIID riid, void *pv,
                        DWORD dwDestCtx, void *pvDestCtx, DWORD mshlflags)

{
    TRACECALL(TRACE_MARSHAL, "CRemoteHdlr::MarshalInterface");

    AssertValid();

    SHandlerDataHdr hdh;
    HRESULT sc = E_NOINTERFACE;

    // make sure we have a stub setup for this interface. we check for
    // IUnknown specially because there is no proxy/stub for it, it is
    // handled by us directly.

    // BUGBUG: This call to FindIX is kind of strange for the client side;
    // the riid should already exist; if it doesn't (e.g., we weren't pass
    // such a pointer), we will assert in CreateInterfaceStub and then
    // proceed to create a stub on the remote side!!

    if (IsEqualIID(IID_IUnknown, riid) || FindIX(riid, NULL, FLG_MARSHAL, &sc))
    {
#ifdef CODEWORK
       //  get the channel classid
       CLSID clsidChannel;

       sc = _pChannel->GetUnmarshalClass(riid, pv, dwDestCtx, pvDestCtx,
                                  mshlflags, &clsidChannel);
       if (FAILED(sc))
           return sc;

       if (clsidChannel == CLSID_StdChannel)
           set bit in hdh.dwflags;
#else
       // since we are single channel, we always have the same clsid; verify
#if DBG == 1
       CLSID clsidChannel;
       sc = _pChannel->GetUnmarshalClass(riid, pv, dwDestCtx, pvDestCtx,
                                  mshlflags, &clsidChannel);
       Assert(sc == NOERROR && IsEqualGUID(clsidChannel, CLSID_RpcChannelBuffer));
#endif
#endif

       //  store the marshal flags. these are needed for unmarshal and
       //  release data in order to adjust the reference counts
       //  appropriately.

       hdh.dwflags = (mshlflags & HDLRFLAGS_TABLE) | HDLRFLAGS_STDRPCCHNL;

       //  store the interface iid in the data
       memcpy(&hdh.iid, &riid, sizeof(hdh.iid));

       //  write the interface header into the stream
       sc = pStm->Write(&hdh, sizeof(hdh), NULL);

       if (SUCCEEDED(sc))
       {
           //   now marshal the channel data
           sc = _pChannel->MarshalInterface(pStm, riid, pv, dwDestCtx,
                                     pvDestCtx, mshlflags);
       }
    }

    return sc;
}


//+-------------------------------------------------------------------
//
//  Member:     CRemoteHdlr::UnmarshalInterface, public
//
//  Synopsis:   reads the SHandlerDataHdr from the stream, locates or
//      creates the appropriate handler for this object, and
//      passes the work off to that handler.
//
//  History:    23-Nov-93   Rickhi       Created
//
//--------------------------------------------------------------------

STDMETHODIMP CRemoteHdlr::UnmarshalInterface(
    IStream *pStm,
    REFIID riid,
    void **ppv)
{
    TRACECALL(TRACE_MARSHAL, "CRemoteHdlr::UnmarshalInterface");

    AssertValid();

    // read the object data from the stream, and ensure
    // that this object has not already been released.

    SHandlerDataHdr hdh;
    CLSID clsidChannel;
    HRESULT sc = StRead(pStm, &hdh, sizeof(hdh));

    // deal with channel clsid (probably pass to Unmarshal below)
    if (SUCCEEDED(sc) && (hdh.dwflags & HDLRFLAGS_STDRPCCHNL) == 0)
       sc = StRead(pStm, &clsidChannel, sizeof(clsidChannel));

#ifdef CODEWORK
    must use clsidChannel
#else
    // single channel; just verify clsid
    Assert((hdh.dwflags & HDLRFLAGS_STDRPCCHNL) != 0 ||
           IsEqualGUID(clsidChannel, CLSID_RpcChannelBuffer));
#endif

    // deal with extension
    if (hdh.dwflags & HDLRFLAGS_EXTENSION)
       SkipMarshalExtension(pStm);

    if (SUCCEEDED(sc))
    {
       //  ask helper routine to do the rest of the work
       sc = Unmarshal(pStm, riid, hdh, ppv);
    }

    return  sc;
}



//+-------------------------------------------------------------------
//
//  Member:     CRemoteHdlr::Unmarshal, private
//
//  Parameters: [pStm]    - stream to unmarshal data from
//
//  Synopsis:   does the unmarshalling for a particular handler. asks
//      the channel to unmarshal his part, finds or creates
//      the appropriate interfaces, and tweaks the reference
//      counts.
//
//  History:    23-Nov-93   Rickhi       Created
//
//  CODEWORK:   merge into ::UnmarshalInterface above.  The reasons for
//      this routine being separate have gone.
//
//--------------------------------------------------------------------

STDMETHODIMP CRemoteHdlr::Unmarshal(
    IStream *pStm,
    REFIID riid,
    SHandlerDataHdr &hdh,
    void **ppv)
{
    // CODEWORK: fix for multiple channels
    Assert(hdh.dwflags & HDLRFLAGS_STDRPCCHNL);

    // unmarshal the channel data; KLUDGE: don't care about iid/ppv;
    // we certainly don't want to pass the riid/ppv from above.
    // CODEWORK: will probably change this when we lookup channel based on
    // destination context.
    HRESULT sc = _pChannel->UnmarshalInterface(pStm, IID_NULL, NULL);

    if (SUCCEEDED(sc))
    {
       if (sc == CHAN_S_RECONNECT)
       {
           // connect proxies
           _IXList.ConnectProxies(_pChannel);
       }

       sc = S_OK;               // don't want to propagate random success codes

       //  look for the interface that was actually marshalled by the
       //  other side. if not present, create an interface proxy for it.

       CPSIX *pIX = NULL;
       if (!IsEqualIID(IID_IUnknown, hdh.iid))
       {
           pIX = FindIX(hdh.iid, NULL, FLG_UNMARSHAL, &sc);
           // ignore these errors; a subsequent QI will detect the problem;
           // the main point about passing the iid back and forth is
           // to tell the client that a certain interface is support
           // and avoid an RPC call to the server process.
       }


       // since the RH is just a middle man in the unmarshaling process,
       // we don't allow any interface to be returned; another way
       // to look at this is the RH must be precreated in the right place.

       if (!IsEqualIID(riid, IID_NULL) || ppv != NULL)
           sc = E_UNEXPECTED;
    }

    return sc;
}



//+-------------------------------------------------------------------
//
//  Member:     CRemoteHdlr::ReleaseMarshalData, public
//
//  Synopsis:   finds the handler for this object and calls it to release
//      its internal state.
//
//  History:    23-Nov-93   Rickhi       Created
//
//  Invocation: This is called by CoUnmarshalInterface when mshlflags
//              are NORMAL, and by application code when mshlflags are
//              TABLESTRONG or TABLEWEAK.
//
//  Notes:      So we dont ever unmarshal this data again, we set the
//      HDLRFLAGS_RELEASED bit in the SHandlerDataHdr. Unmarshal
//              ensures this is not set, and errors if so.
//
//--------------------------------------------------------------------

STDMETHODIMP CRemoteHdlr::ReleaseMarshalData(IStream *pStm)
{
    TRACECALL(TRACE_MARSHAL, "CRemoteHdlr::ReleaseMarshalData");

    AssertValid();

    SHandlerDataHdr hdh;
    CLSID clsidChannel;

    // read the marshal data from the stream
    HRESULT sc = StRead(pStm, &hdh, sizeof(hdh));
    if (FAILED(sc))
       return sc;

    // deal with channel clsid (probably pass to Unmarshal below)
    if ((hdh.dwflags & HDLRFLAGS_STDRPCCHNL) == 0)
    {
       sc = StRead(pStm, &clsidChannel, sizeof(clsidChannel));
       if (FAILED(sc))
           return sc;
    }

#ifdef CODEWORK
    must use clsidChannel
#else
    // single channel; just verify clsid
    Assert((hdh.dwflags & HDLRFLAGS_STDRPCCHNL) != 0 ||
       IsEqualGUID(clsidChannel, CLSID_RpcChannelBuffer));
#endif

    // deal with extension
    if (hdh.dwflags & HDLRFLAGS_EXTENSION)
    {
       sc = SkipMarshalExtension(pStm);
       if (FAILED(sc))
           return sc;
    }

    // we are the remote handler for the object to be released
    sc = ReleaseData(pStm, hdh);

    return  sc;
}


//+-------------------------------------------------------------------
//
//  Member:     CRemoteHdlr::ReleaseData, private
//
//  Synopsis:   Releases any internal state kept around for marshalled
//      interfaces on this particular handler.
//
//  History:    23-Nov-93   Rickhi       Created
//
//  CODEWORK:   merge into ::UnmarshalInterface above.  The reasons for
//      this routine being separate have gone.
//
//+-------------------------------------------------------------------

STDMETHODIMP CRemoteHdlr::ReleaseData(IStream *pStm, SHandlerDataHdr &hdh)
{
    CairoleDebugOut((DEB_MARSHAL, "CRemoteHdlr::ReleasMarshalData %x\n", this));

    // CODEWORK: fix for multiple channels
    Assert(hdh.dwflags & HDLRFLAGS_STDRPCCHNL);

    // call ReleaseMarshalData on the channel
    return _pChannel->ReleaseMarshalData(pStm);
}



//+-------------------------------------------------------------------
//
//  Member:     CRemoteHdlr::DisconnectObject, public
//
//  Synopsis:
//
//  History:    23-Nov-93   Rickhi       Created
//
//--------------------------------------------------------------------

STDMETHODIMP CRemoteHdlr::DisconnectObject(DWORD dwReserved)
{
    AssertValid();

    // CODEWORK: thread safety: need to block multiple calls here and
    // block out other code which tries to use the data changed here (_punkObj)

    // if calls in progress, setup pending disconnect
    if (_cCallsInProgress != 0)
    {
	Win4Assert(IS_LOCAL_RH);

	if (_dwFlags & RHFLAGS_PENDINGDISCONNECT)
	    // either recursive call or we already did the work.
	    return NOERROR;

	// mark that we did the work (channel list disconnect)
	_dwFlags |= RHFLAGS_PENDINGDISCONNECT;

        ChannelList.DisconnectHdlr(this);

	// NOTE: the main point about delaying the rest of the disconnect
	// is to keep the addref's on _punkObj and _pWR (to hold the server
	// alive in general) and the stubs (to hold alive the stub we are
	// calling on and the interface pointer used by that stub).
	return S_OK;
    }

    if (IS_LOCAL_RH)
    {
	// server side:
	//  walk the list of clients, killing off their channel ids.
	//  when they make a subsequent call, they will get an Rpc error.

	ChannelList.DisconnectHdlr(this);

	// CODEWORK: not thread safe (two disconnects at the same time)

	// release our main hold on the object; use WR if supported
	if (_punkObj != NULL)
	{
	    if (_pWR != NULL)
	    {
	        _pWR->ChangeWeakCount(-(2 + (LONG)_IXList.CountStubRefs()));

		// the above ChangeWeakCount turns the _punkObj, _pWR
		// pointers into strong pointers in addition to the stub
		// pointers and thus hold the object alive for the duration
		// of the stub releases and the release of _punkObj.
		_IXList.DisconnectStubs();

		SafeReleaseAndNULL(&_punkObj);
		_pWR->ReleaseKeepAlive(_pWR, 0);
		_pWR = NULL;
	    }
	    else
	    {
		_IXList.DisconnectStubs();
		SafeReleaseAndNULL(&_punkObj);
		// _pWR already NULL
	    }
	}
	else
	{
	    // both NULL
	    Win4Assert(_pWR == NULL);
	}

	Win4Assert(_IXList.CountStubRefs() == 0);
    }
    else
    {
	_IXList.DisconnectProxies();

       // client side: disconnect this one channel from the server
       _pChannel->DisconnectObject(dwReserved);

       // NOTE: we don't AddRef the _punkObj in this case and thus no Release
       // (because _punkObj is our pUnkOuter, which might just be the id obj).
       // Also, _punkObj is always valid as long as we are alive.
    }

    _pChannel->AssertValid(TRUE, TRUE); // known disconnected

    return S_OK;
}


//+-------------------------------------------------------------------
//
//  Member:     CRemoteHdlr::FindIX, private
//
//  Synopsis:   finds or creates an interface object for the given
//      interface.
//
//  Returns:    Pointer to CPSIX for requested interface; NULL if error
//		ppv place to put proxy QI result; must be NULL on server
//		side; may be NULL for client side.
//	        *phr always holds result of call.
//
//
//  History:    23-Nov-93   Rickhi       Created
//		7-May-94   CraigWi       Added HRESULT out param
//
//  Notes:      this code is thread safe
//
//--------------------------------------------------------------------

CPSIX * CRemoteHdlr::FindIX(REFIID riid, void **ppv, DWORD dwFlag, HRESULT *phr)
{
    TRACECALL(TRACE_MARSHAL, "CRemoteHdlr::FindIX");

    AssertValid();

    // validate input parms. there are no proxies/stubs for these
    // interfaces...
    Win4Assert(! (IsEqualIID(riid, IID_NULL) ||
          IsEqualIID(riid, IID_IUnknown) ||
          IsEqualIID(riid, IID_IMarshal)));

    // check for disconnected server on local side
    if (_punkObj == NULL || _dwFlags & RHFLAGS_PENDINGDISCONNECT)
    {
       *phr = CO_E_OBJNOTCONNECTED;
       return NULL;
    }

    // single thread access to this routine
    _mxs.Request();

    // find the IX with the matching IID
    CPSIX* pIX;
    IUnknown *pUnkProxy = NULL;

    if (IS_LOCAL_RH)
    {
	pIX = _IXList.FindStubIX(riid);
    }
    else
    {
	pIX = _IXList.FindProxyIX(riid, (void **)&pUnkProxy);
    }
    *phr = S_OK;

    if (pIX == NULL)
    {
	//  this will take a while. instead of blocking all RHs in the
	//  process, we will mark this RH as busy and release the mutex

	_dwFlags |= RHFLAGS_GETTINGIX;
	_mxs.Release();

	//  no IX in the list. figure out what we should do based on the
	//  flags that were passed in.

	switch (dwFlag)
	{
	case FLG_QUERYINTERFACE:
	    //	 called by QueryInterface. we must call the remote object to
	    //	 tell it to instantiate a stub for the requested interface.

	    if ((*phr = _pChannel->QueryObjectInterface(riid)) != NOERROR)
	    {
		break;
	    }

	    //	 fallthru into UNMARSHAL case

	case FLG_UNMARSHAL:
	    //	 called by the unmarshalling code. the server side is known to
	    //	 have instantiated an interface stub already, so we just create
	    //	 an interface proxy here.

	    pIX = CreateInterfaceProxy(riid, (void **)&pUnkProxy, phr);
	    break;

	case FLG_MARSHAL:
	    //	 called by marshalling code. we create an interface stub for
	    //	 the interface.

	    pIX = CreateInterfaceStub(riid, phr);
	    break;
	}

	//  need to take the mutex again to protect the list.
	_mxs.Request();
	_dwFlags &= ~RHFLAGS_GETTINGIX;

	//  add the interface to the list
	if (pIX)
	{
           _IXList.AddToList(pIX);
	}
    }

    // release the mutex
    _mxs.Release();

    // if pUnkProxy and asked for one, return it; else release.
    if (pUnkProxy != NULL)
    {
	Win4Assert(pIX != NULL);	    // only makes sense if we succeed
	if (ppv != NULL)
	    *ppv = (void **)pUnkProxy;	    // transfer addref
	else
	    pUnkProxy->Release();	    // not needed
    }

    return pIX;
}



//+-------------------------------------------------------------------
//
//  Function:   CreateInterfaceProxy, public
//
//  Synopsis:   creates an interface proxy and wraps it in a CPSIX
//
//  Exceptions: none
//
//  History:    23-Nov-93   Rickhi       Created
//       7-May-94   CraigWi      Added HRESULT out param
//
//--------------------------------------------------------------------

CPSIX * CRemoteHdlr::CreateInterfaceProxy(REFIID riid, void **ppv, HRESULT *phr)
{
    TRACECALL(TRACE_MARSHAL, "CreateInterfaceProxy");

    AssertValid();
    Assert((_dwFlags & RHFLAGS_LOCAL) == 0);
    Win4Assert(_punkObj != NULL);
    Win4Assert(ppv != NULL);

    *ppv = NULL;

    IPSFactoryBuffer *pIPSF = NULL;
    IRpcProxyBuffer  *pIProxy = NULL;
    CPSIX            *pIX = NULL;
    CLSID            clsid;


    // map iid to classid
    HRESULT sc = CoGetPSClsid(riid, &clsid);

    if (SUCCEEDED(sc))
    {
        DWORD dwContext = IsRequestedByWOW(riid) ?
                            CLSCTX_INPROC_SERVER16 : CLSCTX_INPROC_SERVER;

	//  load the dll and get the PS class object
	sc = CoGetClassObject(clsid, dwContext, NULL, IID_IPSFactoryBuffer,
			      (void **)&pIPSF);
	AssertOutPtrIface(sc, pIPSF);

	if (SUCCEEDED(sc))
	{
	    sc = pIPSF->CreateProxy(_punkObj, riid, &pIProxy, ppv);
	    AssertOutPtrIface(sc, pIProxy);
	    AssertOutPtrIface(sc, *ppv);
	    pIPSF->Release();
	}
    }

    if (SUCCEEDED(sc))
    {
	//  create an IX wrapper for this interface proxy

	pIX = new CPSIX(riid, pIProxy);
	if (pIX == NULL)
	{
	    // release below will release proxy (and disconnect)
	    sc = E_OUTOFMEMORY;

	    ((IUnknown *)*ppv)->Release();
	    *ppv = NULL;
	}

	//  connect the proxy to the channel
	pIProxy->Connect(_pChannel);
    }

    if (pIProxy)
	pIProxy->Release();

#if DBG == 1
    if (!pIX)
    {
	WCHAR	 wszGuid[50];
	StringFromGUID2(riid, wszGuid, sizeof(wszGuid)/sizeof(WCHAR));
	CairoleDebugOut((DEB_ERROR, "No Proxy for interface %ws; error = %lx\n" , wszGuid, sc));
    }
#endif

    Win4Assert((sc == S_OK) == (pIX != NULL));
    Win4Assert((sc == S_OK) == (*ppv != NULL));
    *phr = sc;
    return  pIX;
}



//+-------------------------------------------------------------------
//
//  Function:   CreateInterfaceStub, public
//
//  Synopsis:   creates an interface stub and wraps it in a CPSIX
//
//  Exceptions: none
//
//  History:	23-Nov-93   Rickhi	Created
//		 7-May-94   CraigWi	Added HRESULT out param
//
//--------------------------------------------------------------------
CPSIX * CRemoteHdlr::CreateInterfaceStub(REFIID riid, HRESULT *phr)
{
    TRACECALL(TRACE_MARSHAL, "CreateInterfaceStub");

    AssertValid();
    Assert(_dwFlags & RHFLAGS_LOCAL);
    Win4Assert(_punkObj != NULL);

#if DBG == 1
    // convert riid to string for debug messages
    WCHAR   wszGuid[50];
    StringFromGUID2(riid, wszGuid, sizeof(wszGuid)/sizeof(WCHAR));
#endif

    // first, make sure the object supports the given interface, so we
    // dont waste a bunch of effort creating a stub if the interface is
    // not supported.

    IUnknown *punkIf = NULL;
    HRESULT sc = _punkObj->QueryInterface(riid, (void **)&punkIf);

    if (FAILED(sc))
    {
	// the server does not support the requested interface.
	CairoleDebugOut((DEB_MARSHAL,
                 "Server Object does not support Interface:%ws\n", wszGuid));
	*phr = sc;
	return NULL;
    }

    // Determine whether the thing we're getting back is a custom WOW proxy
    // or not so we know whether to load 16-bit custom proxy code if necessary
    BOOL fWowCustom = FALSE;
    IThunkManager *pthkmgr;
    
    if (InWow() &&
        g_pOleThunkWow->GetThunkManager(&pthkmgr) == NOERROR)
    {
        fWowCustom = pthkmgr->IsCustom3216Proxy(punkIf, riid);
        pthkmgr->Release();
    }
    
    punkIf->Release();


    IPSFactoryBuffer    *pIPSF = NULL;
    IRpcStubBuffer      *pIStub = NULL;
    CPSIX               *pIX = NULL;
    CLSID                clsid;

    //  map iid to classid
    sc = CoGetPSClsid(riid, &clsid);

    // bracket with strong connection to prevent addref/rel shutting down
    if (_pWR != NULL)
    {
	_pWR->AddRef();
    }

    if (SUCCEEDED(sc))
    {
        DWORD dwContext;

        dwContext = fWowCustom ? CLSCTX_INPROC_SERVER16 : CLSCTX_INPROC_SERVER;

	// load the dll and get the PS class object
	sc = CoGetClassObject(clsid, dwContext, NULL, IID_IPSFactoryBuffer,
			     (void **)&pIPSF);
	AssertOutPtrIface(sc, pIPSF);

	if (SUCCEEDED(sc))
	{
	    sc = pIPSF->CreateStub(riid, _punkObj, &pIStub);
#ifndef _CAIRO_		// BUGBUG [mikese] CMIDL stubs don't obey the convention
	    AssertOutPtrIface(sc, pIStub);
#endif
	    pIPSF->Release();
	}
    }

    if (SUCCEEDED(sc))
    {
       pIX = new CPSIX(riid, pIStub);
       if (pIX == NULL)
           // release below will release stub (and disconnect)
           sc = E_OUTOFMEMORY;

       pIStub->Release();
    }
    else
    {
       CairoleDebugOut((DEB_WARN, "No Stub for interface %ws; error = %lx\n" , wszGuid, sc));
    }

    // end bracket of strong connection; change ref count via _pWR
    if (_pWR != NULL)
    {
	if (pIX != NULL)
	{
#if DBG == 1
	    // verify that the stub has the number of refs it thinks it has
	    // (and also exercise Disconnect followed by Connect).
	    pIX->VerifyReconnect(_punkObj);
#endif
	    // now convert refs held by stub to weak refs by calling pWR;
	    // these refs are removed only during ::DisconnectObject;
	    // the stub may not change its mind on the number of refs it is
	    // going to hold outside of calls to Connect and Disconnect.
	    _pWR->ChangeWeakCount((LONG)pIX->GetIStub()->CountRefs());
	}

	_pWR->ReleaseKeepAlive(_pWR,0);		// balance above
    }

    // verify that all is well
    _IXList.AssertValid(IS_LOCAL_RH);

    Win4Assert((sc == S_OK) == (pIX != NULL));
    *phr = sc;
    return  pIX;
}





//+-------------------------------------------------------------------
//
//  Member:     CRemoteHdlr::LookupStub, public
//
//  Synopsis:   Returns the stub for the requested IID; this pointer is
//      not AddRefd and so callers must hold on to the rh longer
//      than they hold this; i.e., the lifetime of a stub pointer
//      from this call is no longer than the rh from which it was gotten
//
//      Also returns an addref'd pointer on the server to stablize it
//      during calls.  If such a pointer is returned, the caller must
//	call FinishCall with the same pointer when done.
//
//  History:    27-Nov-93   AlexMit       Created
//		31-May-94   CraigWi	  Now paired with FinishCall method.
//
//--------------------------------------------------------------------

IRpcStubBuffer *CRemoteHdlr::LookupStub(REFIID riid,
    IUnknown **ppUnkServer, HRESULT *phr )
{
    IRpcStubBuffer *pStub;

    AssertValid();
    Win4Assert(IS_LOCAL_RH);

    // find the IX with the matching IID
    // need to create the stub if it is not present;
    // returns error if not connected
    CPSIX* pIX = FindIX(riid, NULL, FLG_MARSHAL, phr);
    AssertOutPtrParam(*phr, pIX);
    if (pIX == NULL)
    {
       pStub = NULL;
       if (ppUnkServer != NULL)
           *ppUnkServer = NULL;
    }
    else
    {
       pStub = pIX->GetIStub();
       if (ppUnkServer != NULL && pStub != NULL)
       {
	   // get server interface from stub; this is a new use of
	   // DebugServerQueryInterface and we may want to change the
	   // name of that method someday.

	   HRESULT hr;
	   hr = pStub->DebugServerQueryInterface((void **)ppUnkServer);
	   AssertOutPtrIface(hr, *ppUnkServer);

	   if (hr == NOERROR)
	   {
	       // don't need to addref the pointer here since the stub will
	       // keep ptr until it is disconnected and we don't disconnect
	       // stub until after the calls complete;
	       // CODEWORK: assert in stub that DebugServerRelease
	       // doesn't happen after disconnect.

	       _cCallsInProgress++;
	   }
       }
    }

    return pStub;
}


//+-------------------------------------------------------------------
//
//  Member:	CRemoteHdlr::FinishCall, public
//
//  Synopsis:	Releases the pUnk previously returned from LookupStub
//		and also finishes pending releases.
//		If the object supports weak connections, we use
//		IWeakRef to release and keep the server alive.
//
//  Arguments:	[pStub]  -- The stub upon which the call was made
//		[pUnkServer] -- The IUnknown on which the call was made
//
//  History:	31-May-94   CraigWi	Created
//
//--------------------------------------------------------------------

void CRemoteHdlr::FinishCall(IRpcStubBuffer *pStub, IUnknown *pUnkServer)
{
    AssertValid();
    Win4Assert(IS_LOCAL_RH);

    Win4Assert((pStub == NULL) == (pUnkServer == NULL));

    // check the pUnkServer argument since there could be a very, very rare
    // case when we got a stub, but no server object.
    if (pUnkServer != NULL)
    {
	pStub->DebugServerRelease(pUnkServer);

	// one less pending call; check for pending disconnect
	if (--_cCallsInProgress == 0 && _dwFlags & RHFLAGS_PENDINGDISCONNECT)
	    DisconnectObject(0);
    }
}


//+-------------------------------------------------------------------
//
//  Member:     CRemoteHdlr::GetObjectId, public
//
//  Synopsis:   returns the remote handlers identity
//
//  History:    10-Jan-94   AlexMit       Created
//
//--------------------------------------------------------------------

void CRemoteHdlr::GetObjectID( OID * pOid )
{
    AssertValid();

    // this is only called on the client side presently and so this assert is ok
    Win4Assert( _pstdID );
    _pstdID->GetObjectID( pOid );
}


//+-------------------------------------------------------------------
//
//  Member:     CRemoteHdlr::IsRequestedByWOW, public
//
//  Synopsis:   return TRUE if requested by WOW app
//
//  History:    03-May-94   JohannP       Created
//
//--------------------------------------------------------------------

BOOL CRemoteHdlr::IsRequestedByWOW(REFIID riid)
{
    AssertValid();
    BOOL fRet = FALSE;

    if(g_pOleThunkWow)
    {
        // dll used by WOW app
        // query for the thunkmanager
        IThunkManager *pThkMgr;
        if (g_pOleThunkWow->GetThunkManager(&pThkMgr) == NOERROR)
        {
            fRet = pThkMgr->IsIIDRequested(riid);
            pThkMgr->Release();
        }
        else
        {
            Win4Assert(FALSE && L"pUnk in WOW does not support IThunkManager.");
        }
    }
    return fRet;
}


#if DBG == 1
//+-------------------------------------------------------------------
//
//  Member:     CRemoteHdlr::AssertValid
//
//  Synopsis:   Validates that the state of the object is consistent.
//
//  History:    25-Jan-94   CraigWi     Created.
//
//--------------------------------------------------------------------

void CRemoteHdlr::AssertValid()
{
    Win4Assert((_dwFlags & ~(RHFLAGS_LOCAL | RHFLAGS_GETTINGIX | RHFLAGS_PENDINGDISCONNECT)) == 0);

    Win4Assert(_cReferences < 0x7fff && "RemoteHdlr ref count unreasonably high");

    if (_punkObj != NULL)
    {
       // if we have a pointer, it should be valid.
       Win4Assert(IsValidInterface(_punkObj));

       // NOTE: can't AddRef/Release _punkObj since we may been in a
       // situation where there are only weak refs

       if (_pWR != NULL)
       {
	   // ensure the object supports IWR (may return different impl)
	   IWeakRef *pWR;
	   Win4Assert(_punkObj->QueryInterface(IID_IWeakRef, (void **)&pWR) == NOERROR);
	   Win4Assert(pWR->ReleaseKeepAlive(pWR, 0) != 0);
       }
    }

    if (!IS_LOCAL_RH)
    {
       Win4Assert(_punkObj != NULL);
       Win4Assert(_pWR == NULL);

       // only allow pending disconnects on server side.
       Win4Assert((_dwFlags&RHFLAGS_PENDINGDISCONNECT) == 0);
    }

    if (_pstdID != NULL)
    {
       Win4Assert(IsValidInterface(_pstdID));
       if (IS_LOCAL_RH && _punkObj != NULL
	&& (_dwFlags & RHFLAGS_PENDINGDISCONNECT) == 0)
           // on remote side or when disconnected, GetServer returns NULL.
           Win4Assert(_pstdID->GetServer(FALSE) == _punkObj);
    }

    Win4Assert(IsValidInterface(_pChannel));
    _pChannel->AssertValid(FALSE, FALSE);

    _IXList.AssertValid(IS_LOCAL_RH);
}
#endif // DBG == 1


//+-------------------------------------------------------------------
//
//  Member:     CIXList::FindProxyIX
//
//  Synopsis:   finds an interface proxy object supporting the specified 
//		interface and returns that pointer on the proxy.  Returns
//		NULL indicating no existing proxy supports the interface.
//
//  History:    23-Nov-93   Rickhi       Created
//		21-Jul-94   CraigWi	 Less abstract form of original FindIX
//
//  Note:       This code is called by the channel dispatch.
//              Thread synchronization is the responsibility of the caller.
//
//--------------------------------------------------------------------

CPSIX *CIXList::FindProxyIX(REFIID riid, void **ppv)
{
    //  validate input parms
    Win4Assert(!IsEqualIID(riid, IID_NULL));

    // starting from the list head
    CPSIX *pIX = first();

    while (pIX)
    {
      // we should only find proxies
      Win4Assert(pIX->_pIProxy != NULL);

      if (pIX->_pIProxy->QueryInterface(riid, ppv) == NOERROR)
	return pIX;

      pIX = next(pIX);
    }

    *ppv = NULL;
    return  NULL;
}


//+-------------------------------------------------------------------
//
//  Member:     CIXList::FindStubIX
//
//  Synopsis:   finds an interface stub object supporting the specified 
//		interface. Returns NULL indicating no existing proxy 
//		supports the interface.
//
//  History:    23-Nov-93   Rickhi       Created
//		21-Jul-94   CraigWi	 Less abstract form of original FindIX
//
//  Note:       This code is called by the channel dispatch.
//              Thread synchronization is the responsibility of the caller.
//
//--------------------------------------------------------------------

CPSIX *CIXList::FindStubIX(REFIID riid)
{
    //  validate input parms
    Win4Assert(!IsEqualIID(riid, IID_NULL));

    // starting from the list head
    CPSIX *pIX = first();

    while (pIX)
    {
      Win4Assert(pIX->_pIProxy == NULL);

      if (IsEqualIID( riid, pIX->_iid ))
	return pIX;

      pIX = next(pIX);
    }

    return NULL;
}


//+-------------------------------------------------------------------
//
//  Member:     CIXList::ConnectProxies
//
//  Synopsis:   walks the list of proxy IXs and connects each of them
//
//  History:    27-Jan-94   CraigWi      Created
//
//  Note:       Thread synchronization is the responsibility of the caller.
//
//--------------------------------------------------------------------

void CIXList::ConnectProxies(IRpcChannelBuffer *pChannel)
{
    //  validate input parms
    Win4Assert(IsValidInterface(pChannel));

    // starting from the list head
    CPSIX *pIX = first();

    while (pIX)
    {
       Win4Assert(pIX->_pIProxy != NULL);
       HRESULT sc = pIX->_pIProxy->Connect(pChannel);
       Win4Assert(SUCCEEDED(sc) && "Proxy Connect Failed");
       pIX = next(pIX);
    }
}


//+-------------------------------------------------------------------
//
//  Member:     CIXList::CountStubRefs
//
//  Synopsis:   walks the list of IXs and sum up the refs
//
//  History:	 1-June-94   CraigWi	Created
//
//  Note:       Thread synchronization is the responsibility of the caller.
//
//  CODEWORK: could keep running sum in IXList rather than computing it
//
//--------------------------------------------------------------------

DWORD CIXList::CountStubRefs(void)
{
    // starting from the list head
    CPSIX *pIX = first();
    DWORD cRefs = 0;

    while (pIX)
    {
       Win4Assert(pIX->_pIProxy == NULL);
       Win4Assert(pIX->_cRefs == -1 || pIX->_cRefs == pIX->_pStub->CountRefs());
       cRefs += pIX->_pStub->CountRefs();
       pIX = next(pIX);
    }

    return cRefs;
}


//+-------------------------------------------------------------------
//
//  Member:     CIXList::DisconnectStubs
//
//  Synopsis:   walks the list of IXs and disconnects each of them
//
//  History:    23-Nov-93   Rickhi       Created
//
//  Note:       Thread synchronization is the responsibility of the caller.
//
//--------------------------------------------------------------------

void CIXList::DisconnectStubs(void)
{
    // starting from the list head
    CPSIX *pIX = first();

    while (pIX)
    {
       Win4Assert(pIX->_pIProxy == NULL);
       pIX->_pStub->Disconnect();
#if DBG==1
       pIX->_cRefs = (DWORD)-1;
#endif
       pIX = next(pIX);
    }
}


//+-------------------------------------------------------------------
//
//  Member:     CIXList::DisconnectProxies
//
//  Synopsis:   walks the list of IXs and disconnects each of them
//
//  History:    23-Nov-93   Rickhi       Created
//
//  Note:       Thread synchronization is the responsibility of the caller.
//
//--------------------------------------------------------------------

void CIXList::DisconnectProxies(void)
{
    // starting from the list head
    CPSIX *pIX = first();

    while (pIX)
    {
       Win4Assert(pIX->_pIProxy != NULL);
       pIX->_pIProxy->Disconnect();
       pIX = next(pIX);
    }
}


#if DBG == 1
//+-------------------------------------------------------------------
//
//  Member:     CIXList::AssertValid
//
//  Synopsis:   Validates that the state of the object is consistent.
//
//  History:    25-Jan-94   CraigWi     Created.
//
//--------------------------------------------------------------------

void CIXList::AssertValid(BOOL fLocal)
{
    // check IX list
    CPSIX *pIX = first();
    BOOL fStubsConnected;

    // determine if all stubs are supposed to connected or disconnected
    // (this shouldn't be called during the disconnect steps)
    if (pIX != NULL && pIX->_pIProxy == NULL && pIX->_cRefs != -1)
	fStubsConnected = TRUE;
    else
	fStubsConnected = FALSE;

    while (pIX)
    {
      // local means stubs and we must have only stubs or only proxies
      Win4Assert(!!fLocal == (pIX->_pIProxy == NULL));

      if (pIX->_pIProxy)
      {
       Win4Assert(IsValidInterface(pIX->_pIProxy));
       Win4Assert(pIX->_pStub == NULL);
       Win4Assert(IsEqualGUID(pIX->_iid, IID_NULL));
      }
      else
      {
       Win4Assert(IsValidInterface(pIX->_pStub));
       if (pIX->_cRefs != -1)
       {
	   Win4Assert(fStubsConnected);
	   Win4Assert(pIX->_cRefs == pIX->_pStub->CountRefs());
       }
       else
       {
	   Win4Assert(!fStubsConnected);
	   Win4Assert(0 == pIX->_pStub->CountRefs());
       }
      }
      pIX = next(pIX);
    }
}
#endif // DBG == 1
