/*++

copyright (c) 1992  Microsoft Corporation

Module Name:

    chancont.cxx

Abstract:

    This module contains thread switching code for the single threaded mode
    and the message filter hooks

Author:

    Alex Mitchell

Revision History:

       Mar 1994 JohannP         Added call category support.
    29 Dec 1993 Alex Mitchell   Creation.
    19 Jul 1994 CraigWi		Added support for ASYNC calls

Functions:

--*/

#include <ole2int.h>

#include <chancont.hxx>
#include <channelb.hxx>
#include <threads.hxx>
#include <objerror.h>
#include <callmain.hxx>

// Name of window class and message class for dispatching messages.
#define CHANNEL_WINDOW_CLASS            L"OleObjectRpcWindow"


/***************************************************************************/
/* Globals. */

// Windows identifiers needed to post to the messsage queue.
ATOM                ChannelClass = 0;

// Window message id used by channel in the channel window class.
UINT               channel_message      = WM_USER;
UINT		   channel_message_send = WM_USER+1;
UINT		   channel_message_done = WM_USER+2;

// Rpc worker thread cache.
CRpcThreadCache     RpcThreadCache;

// Event cache.
CEventCache         EventCache;

// List of channel controllers.
CChannelControl *CChannelControl::_pChanContRoot = NULL;
CMutexSem        CChannelControl::lock;
CChannelControl *ProcessChannelControl = NULL;

// caller-side ctor for packet;can be used for GetOffCOMThread or SwithCOMThread
STHREADCALLINFO::STHREADCALLINFO(TRANSMIT_FN fnTrans,
				 CALLCATEGORY callcat, DWORD tid)
{
    // filter.lid set by GetOffCOMThread and SwitchCOMThread.
    filter.id	    = CALLDATAID_UNUSED;
    filter.TIDCallee= tid;
    filter.CallCat  = callcat;
    filter.Event    = NULL;
    // filter.pRpcMsg set by caller if needed via ???
    fnDispatch	    = fnTrans;
    pServer	    = NULL;
    fLocal	    = FALSE;
    eState	    = in_progress_cs;
    pNextScmCall    = NULL;
    // hResult set later
}


// server-side ctor (a.k.a. recipient side); used for GetToCOMThread.
STHREADCALLINFO::STHREADCALLINFO(DISPATCH_FN fnDisp,
				 CALLCATEGORY callcat, REFLID lid)
{
    filter.lid	    = lid;
    // filter.id not set; only used on the transmit side (by the call control)
    // filter.TIDCallee not set; only used on the transmit side
    // (for RetryRejectedCall)
    filter.CallCat  = callcat;
    filter.Event    = NULL;
    // filter.pRpcMsg set by caller if needed via ???
    fnDispatch	    = fnDisp;
    pServer	    = NULL;
    fLocal	    = FALSE;
    eState	    = in_progress_cs;
    pNextScmCall    = NULL;
    // hResult set later
}


// virtual destructor
STHREADCALLINFO::~STHREADCALLINFO()
{
    if (filter.Event != NULL)
	EventCache.Free(filter.Event);
    if (pServer != NULL)
        pServer->Release();
}


// called to make a copy of the packet in the newly allocated memory; ptciNew
// has only been allocated, not initialized; this avoids redundant code.
// the original packet is not modified.  This can only be called on the
// receiving side of the call (pServer != NULL).
STHREADCALLINFO *STHREADCALLINFO::MakeAsyncCopy(STHREADCALLINFO *ptciNew)
{
    Win4Assert(ptciNew != NULL);	// must have new one to fill in
    Win4Assert(IsValidInterface(pServer)); // must be on receiving side

    // Async calls must be on their own logical thread; here we create one
    LID lidNew;
    RPC_STATUS status = UuidCreate(&lidNew);

    if (!(status == RPC_S_OK || status == RPC_S_UUID_NO_ADDRESS))
    {
	return NULL;
    }

    // the following is like a ctor on the receving (dispatch) side,
    // but more efficient in the copy case
    ptciNew->filter.lid	    = lidNew;
    // filter.id set by call control
    // filter.TIDCallee not set ???
    ptciNew->filter.CallCat  = filter.CallCat;
    ptciNew->filter.Event    = NULL;
    ptciNew->filter.pRpcMsg  = filter.pRpcMsg;
    ptciNew->fnDispatch	    = fnDispatch;
    ptciNew->pServer	    = pServer;
    pServer->AddRef();
    ptciNew->fLocal	    = fLocal;
    ptciNew->eState	    = eState;
    ptciNew->pNextScmCall   = NULL;
    // hResult set later

    return this;
}


// convert this packet into a reply packet that indicates success;
// returns FALSE if OOM.  Most of the work done by the derived classes.
BOOL STHREADCALLINFO::FormulateAsyncReply()
{
    hResult = S_OK;
    return TRUE;
}


// chain the given thread callinfo on the end of the chain that
// this one is on.
void STHREADCALLINFO::ChainScmCall(STHREADCALLINFO *pCallToChain)
{
    STHREADCALLINFO *pCur = this;

    while (1)
    {
	if (pCur->pNextScmCall == NULL)
	{
	    pCur->pNextScmCall = pCallToChain;
	    break;
	}
	pCur = pCur->pNextScmCall;
    }
}


/***************************************************************************/
STDMETHODIMP CChannelControl::QueryInterface( THIS_ REFIID riid, LPVOID FAR* ppvObj)
{
  if (IsEqualIID(riid, IID_IUnknown)
//    || IsEqualIID(riid, IID_IChannelControl)
     )
  {
    *ppvObj = (IChannelControl *) this;
  }
  else
  {
    *ppvObj = NULL;
    return E_NOINTERFACE;
  }

  AddRef();
  return S_OK;
}

/***************************************************************************/
STDMETHODIMP_(ULONG) CChannelControl::Release( THIS )
{
  if (InterlockedDecrement( (long*) &ref_count ) == 0)
  {
    delete this;
    return 0;
  }
  else
    return ref_count;
}

/***************************************************************************/
STDMETHODIMP_(ULONG) CChannelControl::AddRef( THIS )
{
  InterlockedIncrement( (long *) &ref_count );
  return ref_count;
}


/***************************************************************************/
// executed on client thread (in local case) and RPC thread (in remote case);
// posts a message to the server thread, guarding against disconnected channels
HRESULT CChannelControl::ProtectedPostToCOMThread(STHREADCALLINFO *call)
{
    CairoleDebugOut((DEB_CHANNEL, "ProtectedPostToCOMThread hWnd:%x pCall:%x\n",
	    ChannelWindow, call));

    HRESULT result;

    // NOTE: this lock is on the server's channel control, not the client's;
    // in the apartment model this makes a difference because the state we
    // are checking is in the server channel control.

    lock.Request();
    if (state == cool_ccs)
    {
      if (PostMessage(ChannelWindow, channel_message, 0, (DWORD)call))
	result = S_OK;
      else
	result = RPC_E_SYS_CALL_FAILED;
    }
    else
      result = RPC_E_SERVER_DIED_DNE;
    lock.Release();

    return result;
}


/***************************************************************************/
STDMETHODIMP_(void) CChannelControl::Cancel( STHREADCALLINFO **call )
{
  DWORD result;

  // If the call is still in progress, change it to canceled.
  lock.Request();
  if ((*call)->eState == in_progress_cs)
    (*call)->eState = canceled_cs;
  lock.Release();

  // If the call completed before it could be canceled, wait for it to
  // signal the completion event and clean up.
  if ((*call)->eState == done_cs)
  {
    if (InWow() && ((*call)->fLocal ||
		    (*call)->filter.CallCat == CALLCAT_SCMCALL))
		    // cant cancel inputsync calls
    {
      // the completion routine will have posted a message instead of
      // setting an event, so we have to mark it as canceled and cleanup
      // when the Reply msg comes in.
      (*call)->eState = canceled_cs;
      return;
    }
    else
    {
      // A call that completed in TransmitCall (ie, didn't create an event)
      // cannot be canceled.

      Win4Assert( (*call)->filter.Event != NULL );
      result = WaitForSingleObject((*call)->filter.Event, INFINITE);
      Win4Assert( result == WAIT_OBJECT_0 );

      delete *call;
    }
  }

  // Null the STHREADCALLINFO pointer so no one tries to access it.
  *call = NULL;
}

/***************************************************************************/
/*
   This routine is called by the OLE Worker thread on the client side,
   by the RPC worker thread on the server side for remote calls, and
   by ThreadWndProc for local calls on the server side.

   For the client case, it calls the dispatch routine which will send the
   the data over to the server side.
   This routine notifies the COM thread when the call is complete.  If the
   call is canceled before completion, the routine cleans up.

   CODEWORK: the ThreadDispatch call could be reimplented to be faster
   since there are various constraints on its operation (as
   asserted in threads.cxx).
*/
/* static */
void CChannelControl::ThreadDispatch( STHREADCALLINFO **ppcall,
                                      BOOL dispatch )
{
  STHREADCALLINFO *pcall = *ppcall;

  // Dispatch the call.
  if (dispatch)
    pcall->hResult = pcall->fnDispatch( pcall );

  // Change the state to done; we cheat on non-local, recipient side since
  // there is only one thread accessing the channel control; no need to
  // lock and no need to check for cancel since it can't happen.
  if (pcall->pServer == NULL || pcall->fLocal)
  {
    // sender or local case; use lock in case other thread accesses it
    lock.Request();
    if (pcall->eState == in_progress_cs)
	pcall->eState = done_cs;
    lock.Release();
  }
  else
  {
    // non-local recipient; just set to done and skip the next test
    Win4Assert(pcall->eState == in_progress_cs);
    pcall->eState = done_cs;
    goto Done;
  }

  // If the call completed, wake up the client COM thread.
  if (pcall->eState == done_cs)
  {
Done:
    if (pcall->filter.Event != NULL)
    {
	if (!InWow() || (!pcall->fLocal && pcall->pServer) ||
	    pcall->filter.CallCat == CALLCAT_SCMCALL	   ||
	    pcall->filter.CallCat == CALLCAT_INPUTSYNC	   ||
	    pcall->filter.CallCat == CALLCAT_INTERNALINPUTSYNC)
	{
	    // 32bit always uses events for notification
	    // remote server side calls always use events
	    // client side SCMCALL and INPUTSYNC always uses events

	    // someone waiting (e.g., not a SendMessage-type call)
	    CairoleDebugOut((DEB_CHANNEL,"SetEvent pInfo:%x hEvent:%x\n",
			     pcall, pcall->filter.Event));
	    SetEvent( pcall->filter.Event );
	}
	else
        {
            //  NOTE NOTE NOTE NOTE NOTE NOTE NOTE
	    //	16bit OLE used to do PostMessage for the Reply; we
	    //	tried using SetEvent (which is faster) but this caused
            //  compatibility problems for applications which had bugs that
	    //	were hidden by the 16bit OLE DLLs because messages happened
            //  to be dispatched in a particular order (see NtBug 21616 for
	    //	an example).  To retain the old behavior, we do a
	    //	PostMessage here.

	    CairoleDebugOut((DEB_CHANNEL,
		"PostMessage Reply hWnd:%x pCall:%x hEvent:%x\n",
		 pcall->hWndCaller, pcall, pcall->filter.Event));

	    Verify(PostMessage(pcall->hWndCaller,
			       channel_message_done, 0, (DWORD)pcall));
        }

	// pcall likely invalid here as other thread probably deleted it
    }
    else if (pcall->pServer != NULL && pcall->filter.CallCat == CALLCAT_ASYNC)
    {
	// async call and on recipient side, free packet (no one waiting)
	delete pcall;
	*ppcall = NULL;
    }
  }

  // If the call was canceled, clean up.
  else
  {
    // can only cancel when on client side or local call
    Win4Assert(pcall->pServer == NULL || pcall->fLocal);

    delete pcall;
    *ppcall = NULL;
  }
}

/***************************************************************************/
STDMETHODIMP CChannelControl::DispatchCall( PDISPATCHDATA data )
{
  STHREADCALLINFO *call = (STHREADCALLINFO *) data->pData;
  return call->fnDispatch( call );
}

/***************************************************************************/
STDMETHODIMP CChannelControl::OnEvent( PCALLDATA data )
{
  STHREADCALLINFO *call = STHREADCALLINFO::MapCDToTCI(data);
  SERVERCALLEX     type;

  // NOTE: the event used to be freed here; it is now freed in the dtor
  // of STHREADCALLINFO.

  HRESULT hr = call->hResult;

  // CODEWORK: just return the error and let the callcontrol set its
  //	       own state...eliminates a stupid callback

  if (hr == RPC_E_SERVERCALL_RETRYLATER)
    type = SERVERCALLEX_RETRYLATER;
  else if (hr == RPC_E_SERVERCALL_REJECTED)
    type = SERVERCALLEX_REJECTED;
  else
  {
    type = SERVERCALLEX_ISHANDLED;
    hr = S_OK;
  }

  return _pCallControl->SetCallState(data, type, hr);
}

/***************************************************************************/
/* This function is static. */
CChannelControl *CChannelControl::Lookup( HAPT apt )
{
  CChannelControl *pChannelControl;

  lock.Request();
  pChannelControl = _pChanContRoot;
  if (pChannelControl != NULL && !FreeThreading)
  {
    do
    {
      if (pChannelControl->_dwMyThreadId == apt.dwThreadId)
        break;
      pChannelControl = pChannelControl->_pChanContNext;
    }
    while (pChannelControl != _pChanContRoot);
    if (pChannelControl->_dwMyThreadId != apt.dwThreadId &&
        apt.dwThreadId != ANY_APT.dwThreadId)
      pChannelControl = NULL;
  }

  if (pChannelControl != NULL)
    pChannelControl->AddRef();
  lock.Release();

  return pChannelControl;
}

/***************************************************************************/
/*
   Return a failure code to indicate that no event will ever be set.
   Then the message filter will pass the result back to the channel controller
   who will set up the STHREADCALLINFO.  Return S_OK if the event will be
   signalled some time.  In that case you must set the error fields of
   STHREADCALLINFO before returning.

   Note that the CallControl knows about the input_sync case.  In that
   case we call SendMessage which blocks.  There is never an event in the
   input_sync case.
*/
STDMETHODIMP CChannelControl::TransmitCall( PCALLDATA pdata )
{
  STHREADCALLINFO *call           = STHREADCALLINFO::MapCDToTCI(pdata);
  HRESULT          result         = S_OK;
  BOOL             set_call_state = TRUE;
  SERVERCALLEX     type           = SERVERCALLEX_ERROR;

  if (FreeThreading)
  {
    call->hResult = call->fnDispatch( call );
    type          = SERVERCALLEX_ISHANDLED;
  }

  // Dispatch directly to the server thread.
  else if (call->fLocal)
  {
    if (call->filter.CallCat == CALLCAT_INPUTSYNC ||
        call->filter.CallCat == CALLCAT_INTERNALINPUTSYNC)
    {
      // Send the message.
      if (state == cool_ccs)
      {
        // On CoUninitialize this may fail when the window is destroyed.
        if (SendMessage(call->pServer->ChannelWindow,
                        channel_message_send, 0, (DWORD) call))
        {
          if (call->hResult == RPC_E_SERVERCALL_RETRYLATER)
            type = SERVERCALLEX_RETRYLATER;
          else if (call->hResult == RPC_E_SERVERCALL_REJECTED)
            type = SERVERCALLEX_REJECTED;
          else
            type = SERVERCALLEX_ISHANDLED;
        }
        else
        {
          result = RPC_E_SERVER_DIED;
        }
      }
      else
        result = RPC_E_SERVER_DIED_DNE;
    }
    else if (call->filter.CallCat == CALLCAT_ASYNC)
    {
	// async call; copy message, post message and return.
	// NOTE that the FreeThreading case is caught above.  Async
	// support is only for single-threaded apps.

	STHREADCALLINFO *callT;
	if ((callT = call->MakeAsyncCopy(NULL)) == NULL)
	{
	    result = RPC_E_OUT_OF_RESOURCES;
	}
	else if (!call->FormulateAsyncReply())
	{
	    delete callT;
	    result = RPC_E_OUT_OF_RESOURCES;
	}
	else
	{
	    result = callT->pServer->ProtectedPostToCOMThread(callT);

	    if (result == S_OK)
	    {
		// post succeeded; will be dispatched and freed;
		// set call state to indicate a successful call;
		// simulate overall success (normally set by ThreadDispatch);
	        // FormulateAsyncReply setup the buffer for successful return
		// including setting hResult to S_OK.
		type = SERVERCALLEX_ISHANDLED;
	    }
	    else
	    {
		// result and type already set; delete copy of message
		delete callT;
	    }
	}
    }
    else
    {
      // Get an event from the cache.
      result = call->AllocEvent();
      if (result == S_OK)
      {
        // Post a message to server
	result = call->pServer->ProtectedPostToCOMThread(call);

	if (result == S_OK)
	    // post successful, but call not complete; don't set call state;
	    // call->hResult not set also
	    set_call_state = FALSE;
      }
    }
  }
  else if (call->filter.CallCat == CALLCAT_ASYNC)
  {
    // inter-proceess async call; make rpc call to other process (which will
    // post a message) directly on this thread.  This is done to avoid any
    // processing of incoming calls.  This call has no event to signal and
    // can not be canceled (this might change in the network case).
    // Much like the free threading case above.
    call->hResult = call->fnDispatch( call );
    type          = SERVERCALLEX_ISHANDLED;
  }

  // Get a RPC thread to do the work.
  else
  {
    //	dispatch to a worker thread to make the call
    result = call->AllocEvent();
    if (result == S_OK)
    {
	result = RpcThreadCache.Dispatch( call );
	set_call_state = FALSE;
    }
  }

  if (set_call_state)
    _pCallControl->SetCallState( &call->filter, type, result );
  return result;
}

/***************************************************************************/
/* The result of this routine indicates comm status.  This routine throws
   exceptions to indicate fault status.  If FreeThreading is false, a
   thread_switch_data record is used to communicate with the thread doing the
   work.  That thread will return both kinds of results in the record.
   If a request comes in, this routine will call AppInvoke which will catch
   all exceptions and return both types as its result.  If FreeThreading is
   TRUE, this routine just calls I_RpcSendReceive.  For now, the result of
   I_RpcSendReceive will always be treated as a comm status (and thus just
   returned).  Someday, I_RpcSendReceive will indicate server faults which
   this routine can throw.
*/
HRESULT CChannelControl::GetOffCOMThread( STHREADCALLINFO **call )
{
  TRACECALL(TRACE_RPC, "CChannelControl::GetOffCOMThread");

  HRESULT          result;
  IID             *logical_thread;
  CChannelControl *pChannelControl;

  // assert initialized correctly.
  Win4Assert((*call)->pServer == NULL && !(*call)->fLocal);
  Win4Assert((*call)->filter.Event == NULL);

  // Find the channel controller for this thread.
  GetLocalChannelControl( pChannelControl );
  if (pChannelControl)
  {
    (*call)->hWndCaller = pChannelControl->ChannelWindow;

    // Find the logical thread id.
    logical_thread = TLSGetLogicalThread();
    if (logical_thread)
    {
      // Fill in the fields not set by the ctor
      (*call)->filter.lid   = *logical_thread;

      CairoleDebugOut((DEB_CHANNEL,
		"GetOffCOMThread hWnd:%x pCall:%x hEvent:%x\n",
		 (*call)->hWndCaller, (*call), (*call)->filter.Event));

      // Let the modal loop transmit the call and wait for the reply.
      result = pChannelControl->_pCallControl->CallRunModalLoop( &(*call)->filter );
      if (result == S_OK)
        result = (*call)->hResult;
      else if (result == RPC_E_CALL_CANCELED)
        Cancel( call );
    }
    else
    {
      //  cant get logical thread
      result	  = RPC_E_OUT_OF_RESOURCES;
    }
  }

  // Can't get channel controller.
  else
  {
    result      = RPC_E_THREAD_NOT_INIT;
  }
  return result;
}

/***************************************************************************/
/* Really, I'm static.  No, really. */
HRESULT CChannelControl::GetToCOMThread( HAPT apt, STHREADCALLINFO *call )
{
  CChannelControl *pChannelControl;
  HRESULT          result;

  pChannelControl = Lookup( apt );
  if (pChannelControl != NULL)
  {
    result = pChannelControl->GetToCOMThread( call );
    pChannelControl->Release();
  }
  else
  {
    result      = RPC_E_SERVER_DIED_DNE;
  }
  return result;
}

/***************************************************************************/
HRESULT CChannelControl::GetToCOMThread( STHREADCALLINFO *call )
{
  TRACECALL(TRACE_RPC, "CChannelControl::GetToCOMThread");

  CairoleDebugOut((DEB_CHANNEL, "GetToCOMThread pCall:%x\n", call));

  HRESULT result;

  Win4Assert(call->pServer == NULL && !call->fLocal);
  Win4Assert(call->filter.Event == NULL);

  // ctor sets pServer to NULL; we need one; will be released in dtor
  call->pServer = this;
  AddRef();

  if (FreeThreading)
  {
    //  In the multithreaded case, just call the dispatch function
    //  directly from this thread.
    if (TLSSetLogicalThread(call->filter.lid))
      result = call->fnDispatch( call );
    else
      result = RPC_E_SYS_CALL_FAILED;
  }
  else if (   call->filter.CallCat == CALLCAT_INPUTSYNC
	   || call->filter.CallCat == CALLCAT_INTERNALINPUTSYNC)
  {
    //	input synchronous call, send a message
    if (state == cool_ccs)
    {
      // On CoUninitialize this may fail when the window is destroyed.
      if (SendMessage(ChannelWindow, channel_message_send, 0, (DWORD) call))
        result = call->hResult;
      else
        result = RPC_E_SERVER_DIED;
    }
    else
    {
      result = RPC_E_SERVER_DIED_DNE;
    }
  }
  else if ( call->filter.CallCat == CALLCAT_ASYNC)
  {
    // async call; copy message, post message and return.
    // NOTE that the FreeThreading case is caught above.  Async
    // support is only for single-threaded apps.

    STHREADCALLINFO *callT;
    if ((callT = call->MakeAsyncCopy(NULL)) == NULL)
    {
	result = RPC_E_OUT_OF_RESOURCES;
    }
    else if (!call->FormulateAsyncReply())
    {
	delete callT;
	result = RPC_E_OUT_OF_RESOURCES;
    }
    else
    {
	// Post a message and wait for the app to get back to GetMessage.
	result = ProtectedPostToCOMThread(callT);

	if (result == S_OK)
	{
	  // post succeeded; will be dispatched and freed
	  // fnAsyncCopy setup the buffer for successful return
	}
	else
	{
	  // error in posting; free packet and return error (result set above)
	  delete callT;
	}
    }
  }
  else
  {
    //	Get this thread's event. May cause an event to be created.
    result = call->AllocEvent();
    if (result == S_OK)
    {
      call->pNextScmCall = NULL;
      if (call->filter.CallCat == CALLCAT_SCMCALL &&
	  _pCMC->IncomingScmCall(call) )
      {
	// scmcall, already processed.
      }
      else
      {
	result = ProtectedPostToCOMThread(call);
      }

      if (result == S_OK)
      {
        // Wait for the app to finish processing the request.
        if (WaitForSingleObject(call->filter.Event, INFINITE) == WAIT_OBJECT_0)
          result = call->hResult;
        else
          result = RPC_E_SYS_CALL_FAILED;
      }
    }
  }
  return result;
}

/***************************************************************************/
/* Really, I'm static.  No, really. */
HRESULT CChannelControl::SwitchCOMThread( HAPT apt, STHREADCALLINFO **call )
{
  CChannelControl *pChannelControl;
  HRESULT          result;

  pChannelControl = Lookup( apt );
  if (pChannelControl != NULL)
  {
    result = pChannelControl->SwitchCOMThread( call );
    pChannelControl->Release();
  }
  else
  {
    result      = RPC_E_SERVER_DIED_DNE;
  }
  return result;
}

/***************************************************************************/
HRESULT CChannelControl::SwitchCOMThread( STHREADCALLINFO **call )
{
  TRACECALL(TRACE_RPC, "CChannelControl::SwitchCOMThread");

  HRESULT          result;
  IID             *logical_thread;
  CChannelControl *pChannelControl;

  Win4Assert((*call)->pServer == NULL && !(*call)->fLocal);
  Win4Assert((*call)->filter.Event == NULL);

  Win4Assert( !FreeThreading );
  logical_thread = TLSGetLogicalThread();
  if (logical_thread)
  {
    // set fields we need different than ctor
    (*call)->filter.lid	 = *logical_thread;
    (*call)->fLocal	 = TRUE;
    (*call)->pServer	 = this;
    AddRef();		 // will be released in dtor

    // Call the message filter for this thread, not the server's thread.
    pChannelControl = (CChannelControl *) TLSGetChannelControl();
    if (pChannelControl != NULL)
    {
      (*call)->hWndCaller = pChannelControl->ChannelWindow;

      CairoleDebugOut((DEB_CHANNEL,
		"SwitchCOMThread hWnd:%x pCall:%x hEvent:%x\n",
		 (*call)->hWndCaller, (*call), (*call)->filter.Event));

      result = pChannelControl->_pCallControl->CallRunModalLoop( &(*call)->filter );
      if (result == S_OK)
        result = (*call)->hResult;
      else if (result == RPC_E_CALL_CANCELED)
        Cancel( call );
    }
    else
      result = RPC_E_THREAD_NOT_INIT;

    // NOTE: Event and pServer are cleaned up by dtor of STHREADCALLINFO.
  }
  else
  {
    //  cant get logical thread
    result = RPC_E_OUT_OF_RESOURCES;
  }
  return result;
}

/***************************************************************************/
/* This routine is only called if FreeThreading is false.  AppInvoke will
   catch all exceptions and return both comm status and server faults in
   its result.  This routine stuffs the result of comm status into a
   thread_switch_data record that is passed back to ThreadInvoke on another
   thread. */

LRESULT ThreadWndProc(HWND window, UINT message, WPARAM unused, LPARAM params)
{
  if (message == channel_message ||
      message == channel_message_send)
  {
    STHREADCALLINFO *call = (STHREADCALLINFO *) params;
    CairoleDebugOut((DEB_CHANNEL, "ThreadWndProc: Incoming Call pCall:%x\n", call));

    // If the server isn't taking calls, just fail this one.
    if (call->pServer->state != cool_ccs)
    {
      call->hResult      = RPC_E_SERVER_DIED_DNE;

      // Wake up the caller; ThreadDispatch takes care of the two main
      // cases: local (pServer != NULL and fLocal == TRUE) and server
      // side non-local (pServer != NULL and fLocal == FALSE).  It also
      // handles the orthoginal cases of send message (filter.Event == NULL)
      // and async (CallCat == CALLCAT_ASYNC and pServer != NULL).

      // In the server-side non-local case, we assert that the call is not
      // canceled.  Canceling is handled only on the client side or local cases.

      CChannelControl::ThreadDispatch( &call, FALSE );
    }

    // This server is running, dispatch the call.
    else
    {
      //  Set the logical thread id. Note this cant fail because we
      //  are on the app main thread and we pre-allocated the TLS
      //  in CoInitialize and the uuid allocation can't fail.
      IID *threadid_ptr = TLSGetLogicalThread();
      Win4Assert(threadid_ptr && "TLSGetLogicalThread failed.");
      Win4Assert( !FreeThreading );

      //  save the original threadid & copy in the new one.
      UUID saved_threadid = *threadid_ptr;
      *threadid_ptr = call->filter.lid;

      // Dispatch all calls through ThreadDispatch.  Local calls may be
      // canceled.  Server-side, non-local calls cannot be canceled.  Send
      // message calls (filter.Event == NULL) are handled as well.
      CChannelControl::ThreadDispatch( &call, TRUE );

      //  restore the original thread id.
      *threadid_ptr = saved_threadid;
    }

    return 1;
  }
  else if (message == channel_message_done)
  {
      // call completed - only happens InWow()
      STHREADCALLINFO *call = (STHREADCALLINFO *) params;
      CairoleDebugOut((DEB_CHANNEL, "ThreadWndProc: Call Completed hWnd:%x pCall:%x\n", window, call));

      if (call->eState == canceled_cs)
      {
	// canceled, throw it away
	delete call;
      }
      else if (call->filter.Event)
      {
	// mark the call as done
	SetEvent(call->filter.Event);
      }

      return 1;
  }
  else
  {
      // Otherwise let the default window procedure have the message.
      return DefWindowProc( window, message, unused, params );
  }
}



/***************************************************************************/
CChannelControl::CChannelControl( HRESULT *result )
{
  ORIGINDATA OriginDataQ;

  state     = bummin_ccs;
  ref_count = 1;
  *result   = RPC_E_OUT_OF_RESOURCES;
  _dwMyThreadId	= GetCurrentThreadId();

  // Get on the ChannelController list so that even if this instance isn't
  // created correctly, ThreadUninitialize can find it to clean up.
  lock.Request();
  if (_pChanContRoot == NULL)
    _pChanContRoot = _pChanContPrev = _pChanContNext = this;
  else
  {
    _pChanContNext             = _pChanContRoot->_pChanContNext;
    _pChanContPrev             = _pChanContRoot;
    _pChanContRoot->_pChanContNext->_pChanContPrev = this;
    _pChanContRoot->_pChanContNext       = this;
  }
  lock.Release();

  if (!FreeThreading)
  {

    // Create hidden channel window.
    ChannelWindow = CreateWindowEx(0,
                      (LPCWSTR) ChannelClass,
		      L"OLEChannelWnd",
                      WS_DISABLED,
                      CW_USEDEFAULT,
                      CW_USEDEFAULT,
                      CW_USEDEFAULT,
                      CW_USEDEFAULT,
                      NULL,
                      NULL,
		      g_hinst,
                      NULL );
    if (!ChannelWindow)
    {
      CairoleDebugOut((DEB_ERROR,"CreateWindowEx failed in CChannelControl constructor\n"));
      return;
    }
  }
  else
  {
    ChannelWindow = NULL;
  }

  // Get a message filter.
  if (FreeThreading)
    OriginDataQ.CallOrigin = CALLORIGIN_RPC32_MULTITHREAD;
  else
    OriginDataQ.CallOrigin = CALLORIGIN_RPC32_APARTMENT;

  OriginDataQ.pChCont	 = this;
  OriginDataQ.hwnd       = ChannelWindow;
  OriginDataQ.wFirstMsg  = channel_message;
  OriginDataQ.wLastMsg	 = channel_message_done;

  *result = CoGetCallControl( &OriginDataQ, &_pCallControl );

  _pCMC = GetCallMainControlForThread();

  if (SUCCEEDED(*result) && _pCMC)
  {
    // pre-allocate thread local storage so we dont have to test this on
    // every dispatch later on.  Don't preallocate the logical
    // thread id itself since that casuses some a hang on Cairo startup;
    // the allocation of the logical thread id (UuidCreate) can't fail.

    if(TLSGetData())
    {
      *result = S_OK;
      state = cool_ccs;
    }
  }
}

/***************************************************************************/
CChannelControl::~CChannelControl()
{
  // We should have changed state and been removed from the channel controller
  // list in ThreadUnintialize.
  Assert( state == bummin_ccs );
}

/***************************************************************************/

//+-------------------------------------------------------------------------
//
//  Member:     CChannelControl::ThreadStop
//
//  Synopsis:   Per thread uninitialization
//
//  Effects:
//
//  Modifies:
//
//  Algorithm:
//
//  History:    ??-???-?? ?         Created
//              05-Jul-94 AlexT     Separated thread and process uninit
//
//  Notes:      We are not holding the single thread mutex during this call
//
//--------------------------------------------------------------------------

void CChannelControl::ThreadStop(void)
{
  MSG    msg;
  BOOL   got_quit = FALSE;
  WPARAM quit_val;

  // Change state and get off pChannelControl list.
  lock.Request();
  state = bummin_ccs;
  if (_pChanContNext == this)
    _pChanContNext = _pChanContPrev = _pChanContRoot = NULL;
  else
  {
    _pChanContNext->_pChanContPrev = _pChanContPrev;
    _pChanContPrev->_pChanContNext = _pChanContNext;
    if (_pChanContRoot == this)
      _pChanContRoot = _pChanContNext;
  }
  lock.Release();

  // Wait for all current calls to complete.
  if (!FreeThreading)
  {
    while( PeekMessage( &msg, ChannelWindow, channel_message,
                       channel_message_send, PM_REMOVE ) )
    {
      if (msg.message == WM_QUIT)
      {
        got_quit = TRUE;
        quit_val = msg.wParam;
      }
      else
        DispatchMessage( &msg );
    }
  }

  // Destroy the window.  This will unblock any pending send messages.
  if (ChannelWindow != NULL)
  {
    // This may fail if threads get terminated.
    DestroyWindow( ChannelWindow );
    ChannelWindow = 0;
  }

  // Release the call controller.
  if (_pCallControl != NULL)
  {
    _pCallControl->Release();
    _pCallControl = NULL;
  }

  if (got_quit)
    PostQuitMessage( quit_val );
}

/***************************************************************************/
HRESULT ThreadInitialize( BOOL first )
{
  WNDCLASS         stuff;
  HRESULT          result =  S_OK;
  CChannelControl *ChannelControl;

  // initialize the event cache
  EventCache.Initialize();

  // Only get window stuff for the single threaded mode.
  if (!FreeThreading)
  {
    // Register windows class.
    if (ChannelClass == 0)
    {
      stuff.style         = 0;
      stuff.lpfnWndProc   = ThreadWndProc;
      stuff.cbClsExtra    = 0;
      stuff.cbWndExtra    = 0;
      stuff.hInstance	  = g_hinst;
      stuff.hIcon         = NULL;
      stuff.hCursor       = NULL;
      stuff.hbrBackground = (HBRUSH) (COLOR_BACKGROUND + 1);
      stuff.lpszMenuName  = NULL;
      stuff.lpszClassName = CHANNEL_WINDOW_CLASS;

      ChannelClass = RegisterClass( &stuff );
      if (ChannelClass == 0)
        CairoleDebugOut((DEB_ERROR,"RegisterClass failed in ThreadInitialize\n"));
    }

    if (ChannelClass != 0)
    {
      // Create a class for message filter hooks.
      // This is released in ThreadUninitialize.
      ChannelControl = new CChannelControl( &result );

      if (ChannelControl)
      {
	 if (SUCCEEDED(result))
	 {
	    if (TLSSetChannelControl( ChannelControl ))
		return S_OK;
	 }
	 delete ChannelControl;
      }
    }

    result = CO_E_INIT_TLS_CHANNEL_CONTROL;
  }

  // Only get a channel controller the first time we are initialized in
  // the multithreaded mode.
  else if (first)
  {
      // Create a class for message filter hooks.
      ProcessChannelControl = new CChannelControl( &result );
      Win4Assert(ProcessChannelControl && "Could not allocate ChannelControl");
      if (SUCCEEDED(result))
        return S_OK;
  }

  // Do nothing when reinitialized in multithreaded mode.
  else
    return S_OK;

  // If we get here, something failed.  Our caller (CoInitializeEx) will
  // call the necessary uninitialization routines on our behalf.

  return result;
}

/***************************************************************************/
void ChannelControlThreadUninitialize(void)
{
  CChannelControl *pChannelControl;

  if (!FreeThreading)
  {
    // Find the channel controller for this thread.  Stop it and release it.
    pChannelControl = (CChannelControl *) TLSGetChannelControl();
    if (pChannelControl != NULL)
    {
      TLSSetChannelControl( NULL );
      pChannelControl->ThreadStop();

      // This release matches the create in ThreadInitialize.
      pChannelControl->Release();
    }
  }
}

void ChannelControlProcessUninitialize(void)
{
  BOOL success;

  if (!FreeThreading)
  {
    // When the process is stopping, tell RPC to stop listening.
    if (sg_pLocalSrv != NULL)
      sg_pLocalSrv->StopListen();

    // Free up the resources needed for single threading.
      // Deregister the window class.
      if (ChannelClass != 0)
      {
	success      = UnregisterClass( CHANNEL_WINDOW_CLASS, g_hinst );
#if DBG == 1
        if (!success)
	  CairoleDebugOut((DEB_ERROR, "Could not UnregisterClass 0x%x\n",
                           GetLastError()));
#endif
        ChannelClass = 0;
      }
  }
  else
  {
    Assert(FreeThreading && "Bad threading logic");

    if (ProcessChannelControl != NULL)
    {
      ProcessChannelControl->ThreadStop();
      if (sg_pLocalSrv != NULL)
	sg_pLocalSrv->StopListen();

      ProcessChannelControl->Release();
      ProcessChannelControl = NULL;
    }
  }

  //	release all cached threads
  RpcThreadCache.FreeAllThreads();

  //	release cached events
  EventCache.Cleanup();
}

/***************************************************************************/
void CEventCache::Initialize()
{
    CLock   lck(_EventLock);

    memset(_list, 0, sizeof(_list));
    _ifree = 0;
}


/***************************************************************************/
void CEventCache::Cleanup(void)
{
    CLock   lck(_EventLock);

    if (_ifree < CEVENTCACHE_MAX_EVENT + 1)
    {
	while (_ifree > 0)
	{
	    _ifree--;		      // decrement the index first!
	    Verify(CloseHandle(_list[_ifree]));
	    _list[_ifree] = NULL;
	}
    }

    //	set the index high so that if an event is returned to the cache
    //	after Cleanup, it gets Closed instead of lost in the list.
    _ifree = CEVENTCACHE_MAX_EVENT + 1;
}


/***************************************************************************/
void CEventCache::Free( HANDLE event )
{
    // Do nothing if there is no event.
    if (event == NULL)
	return;

    CLock lck(_EventLock);

    if (_ifree < CEVENTCACHE_MAX_EVENT)
    {
	// there is space, save this event.

#if DBG==1
	// in debug, ensure slot is NULL
	Win4Assert(_list[_ifree] == NULL && "Free: _list[_ifree] != NULL");

	//  enusre not already in the list
	for (ULONG j=0; j<_ifree; j++)
	{
	    Win4Assert(_list[j] != event && "Free: event already in cache!");
	}

	// ensure that the event is in the non-signalled state
	Win4Assert(WaitForSingleObject(event, 0) == WAIT_TIMEOUT &&
		"Free: Signalled event returned to cache!\n");

#endif

	_list[_ifree] = event;
	_ifree++;
    }
    else
    {
	// Otherwise really free it.
	Verify(CloseHandle(event));
    }
}

/***************************************************************************/
HANDLE CEventCache::Get()
{
    HANDLE event = NULL;

    Win4Assert(_ifree <= CEVENTCACHE_MAX_EVENT);

    {
	CLock lck(_EventLock);

	// If there is an event in the cache, use it.
	if (_ifree > 0)
	{
	    _ifree--;
	    event = _list[_ifree];
#if DBG==1
	    //	in debug, NULL the slot.
	    _list[_ifree] = NULL;
#endif
	}

	// lock goes out of scope at this point.
    }

    // Otherwise allocate a new one.
    if (event == NULL)
	event = CreateEvent( NULL, FALSE, FALSE, NULL );

    Win4Assert(event != NULL && "CEventCache:GetEvent returning NULL");

    return event;
}
