//+-------------------------------------------------------------------
//
//  File:       idtable.cxx
//
//  Contents:   identity table
//
//  Functions:
//
//  History:     1-Dec-93   CraigWi	Created
//
//--------------------------------------------------------------------

// CODEWORK: lookup from party apartment should not get individual apartments;
// but lookup from individual apartments should get party.  Currently
// we only support individual apartments or a single party apartment, not both.

#include <ole2int.h>

#include <idtable.hxx>
#include <sem.hxx>

CIDArray sg_idtable;

CMutexSem   sg_mxsTable;	//  global mutext semaphore for id table


//+-------------------------------------------------------------------
//
//  Function:	LookupIDFromUnk, private
//
//  Synopsis:	Looks up and may create the identity object for the given
//		object.  If the identity object is created, it is not
//		aggregated to the given object.
//		
//		Identity lookup is based on pUnkControl.
//
//  Effects:	Due to multiple threads, it is possible for two identity
//		objects to temporaily exist for the given object.  Given that
//		we don't aggregate the identity object in this routine, we are
//		able to use any other identity object created in another
//		thread.  See CreateStdIdentity for more information.
//
//  Arguments:	[pUnk] -- the object; not necessarily the controlling unknown
//		[fCreate] -- if the identity does not exist, create one.
//		[ppStgID] -- when S_OK is returned, this is the identity
//			object.  This pointer doesn't necessarily hold
//			the object alive.  If the identity object was
//			aggregated to the object, it will; if not, it will
//			not.  Only marshaling or lock connection will
//			ensure the object stays alive.
//
//  Returns:	S_OK - identity now exists (whether created here or not)
//
//		CO_E_OBJNOTREG - no identity and !fCreate
//
//		BUGBUG - the identity GUID could not be created
//
//		E_OUTOFMEMORY -
//
//		E_UNEXPECTED - at least: no controlling unknown
//
//  History:	11-Dec-93   CraigWi	Created.
//
//--------------------------------------------------------------------

INTERNAL LookupIDFromUnk(IUnknown *pUnk, BOOL fCreate, IStdIdentity **ppStdID)
{
    // QI for IStdID; if ok, return that
    if (pUnk->QueryInterface(IID_IStdIdentity, (void **)ppStdID) == NOERROR)
	return NOERROR;

#ifndef _CAIRO_
    // BUGBUG: this assert far too annoying on Cairo right now [mikese]
    //         Remove when we convert to MIDL 2.0 for ctypes
    AssertOutPtrFailed(*ppStdID);
#endif

    // NOTE: other thread may create id on object before we check again

    IUnknown *pUnkControl;
    // QI for controlling unk; should succeed
    if (pUnk->QueryInterface(IID_IUnknown, (void **)&pUnkControl) != NOERROR)
	return E_UNEXPECTED;

    IStdIdentity *pStdID = NULL;
    sg_mxsTable.Request();
	// scan for value in map; may find one attached to object created by now
	int iID;

	if (FreeThreading)
	    iID = sg_idtable.IndexOf((void *)&pUnkControl,
			sizeof(pUnkControl), offsetof(IDENTRY, m_pUnkControl));
	else
	{
	    IDENTRY identry;

	    identry.m_tid = GetCurrentThreadId();
	    identry.m_pUnkControl = pUnkControl;
	    iID = sg_idtable.IndexOf((void *)&identry.m_tid,
		    sizeof(identry.m_tid) + sizeof(identry.m_pUnkControl),
		    offsetof(IDENTRY, m_tid));
	}

	// if found, addref pStdID which holds the identity alive
	if (iID != -1)
	{
	    pStdID = sg_idtable[iID].m_pStdID;
	    pStdID->AddRef();

#if DBG == 1
	    // verify correctness of entry
	    OID oidT;

	    Assert(pUnkControl == sg_idtable[iID].m_pUnkControl);
	    pStdID->GetObjectID(&oidT);
	    Assert(IsEqualGUID(oidT, sg_idtable[iID].m_oid));

	    if (FreeThreading)
		Assert(sg_idtable[iID].m_tid == NULL);
	    else
		Assert(sg_idtable[iID].m_tid == GetCurrentThreadId());
#endif
	}
    sg_mxsTable.Release();

    // We don't hold the mutex since CreateStdIdentity can call the server
    // object, which we must assume can be expensive.  CreateStdIdentity
    // object calls SetObjectID() which gets the mutex again.

    // CODEWORK: however, we must hold the mutex long enough so that two
    // threads in the party apartment don't try to create the same id twice.

    HRESULT hr;

    // pStdID != NULL if found.
    *ppStdID = pStdID;
    if (pStdID != NULL)
	hr = NOERROR;

    // one didn't exist (when we checked a little bit ago) and we can't create;
    // *ppStdID already == NULL.
    else if (!fCreate)
	hr = CO_E_OBJNOTREG;

    // create identity; may return success with already created id; will
    // never fail if id created by the time we register the newly created one
    else
	hr = CreateStdIdentity(NULL, pUnkControl, PSTDMARSHAL,
		IID_IStdIdentity, (void **)ppStdID);

    // we need to do this release via IWR if supported so it won't shutdown
    // the server
    if (hr != NOERROR)
    {
	// no identity to help in error case; for now, just let it shutdown
	// this seems reasonsable since there may be no other time that it
	// would shutdown.
	pUnkControl->Release();
    }
    else
    {
	(*ppStdID)->ReleaseKeepAlive(pUnkControl, 0);
    }

    return hr;
}



//+-------------------------------------------------------------------
//
//  Function:	LookupIDFromID, private
//
//  Synopsis:	Lookup an identity object based on an OID; does not create.
//
//  Arguments:	[oid] -- The identity
//		[ppStdID] -- The cooresponding identity object if successful
//
//  Returns:	S_OK - have the identity object
//
//		CO_E_OBJNOTREG - not present (when we looked)
//
//  History:	11-Dec-93   CraigWi	Created.
//
//--------------------------------------------------------------------

INTERNAL LookupIDFromID(REFOID oid, BOOL fAddRef, IStdIdentity **ppStdID)
{
    *ppStdID = NULL;
    sg_mxsTable.Request();
	// scan for value in map; may find one attached to object created by now
	int iID;
	if (FreeThreading)
	    iID = sg_idtable.IndexOf((void *)&oid, sizeof(oid), offsetof(IDENTRY, m_oid));
	else
	{
	    IDENTRY identry;

	    identry.m_oid = oid;
	    identry.m_tid = GetCurrentThreadId();
	    iID = sg_idtable.IndexOf((void *)&identry.m_oid,
		    sizeof(identry.m_oid) + sizeof(identry.m_tid),
		    offsetof(IDENTRY, m_oid));
	}


	// if found, addref pStdID which holds the identity alive
	if (iID != -1)
	{
	    *ppStdID = sg_idtable[iID].m_pStdID;

	    if (fAddRef)
		// I sure hope the apps doesn't try anything fancy in AddRef
		// that would cause a deadlock here! (That is, in the aggregated
		// case we will run app code).
		(*ppStdID)->AddRef();

#if DBG == 1
	    // verify correctness of entry
	    Assert(IsEqualGUID(oid, sg_idtable[iID].m_oid));

	    OID oidT;
	    (*ppStdID)->GetObjectID(&oidT);
	    Assert(IsEqualGUID(oid, oidT));

	    if (FreeThreading)
		Assert(sg_idtable[iID].m_tid == NULL);
	    else
		Assert(sg_idtable[iID].m_tid == GetCurrentThreadId());

#ifdef BUGBUG
can't do this yet since this occurs in dtors with unstable ref count;
e.g., CAdvBnd::~CAdvBnd does prot->Revoke which does CoUnmarshalInterface.
in 16bit OLE2 we required such dtors to artifically bump the ref count.
	    IUnknown *pUnkControl;
	    Verify(sg_idtable[iID].m_pUnkControl->QueryInterface(IID_IUnknown,
		    (void **)&pUnkControl) == NOERROR);

	    Assert(pUnkControl == sg_idtable[iID].m_pUnkControl);
	    pUnkControl->Release();
#endif // BUGBUG
#endif
	}
    sg_mxsTable.Release();

    return *ppStdID == NULL ? CO_E_OBJNOTREG : NOERROR;
}


//+-------------------------------------------------------------------
//
//  Function:	SetObjectID, private
//
//  Synopsis:	Called by the object id creation and unmarshal functions
//		to establish the identity for an object (handler or server).
//		Can fail if we discover an existing identity.
//
//		Identity lookup is based on pUnkControl.
//
//  Arguments:	[oid] -- The id for the object
//		[pUnkControl] -- The controlling uknown of the object being
//			identitified.
//		[pStdID] -- The identity object itself.
//		[ppStdIDExisting] -- If another identity object go created
//			since we (this thread) checked last, this is
//			where that object is returned.  May be NULL
//			indicating that the caller doesn't care about
//			an existing id.
//
//  Returns:	S_OK - identity was set successfully
//
//		CO_E_OBJISREG - object was already registered (as determined
//			by pUnkControl); *ppStdIDExisting set (if requested).
//
//		E_OUTOFMEMORY -
//
//  History:	11-Dec-93   CraigWi	Created.
//
//--------------------------------------------------------------------

INTERNAL SetObjectID(REFOID oid, IUnknown *pUnkControl, IStdIdentity *pStdID,
	IStdIdentity **ppStdIDExisting)
{
    HRESULT hr = NOERROR;

    Assert(!IsEqualGUID(oid, OID_NULL));

    sg_mxsTable.Request();
	// scan for value in map; may find one attached to object created by now
	int iID;

	if (FreeThreading)
	    iID = sg_idtable.IndexOf((void *)&pUnkControl,
			sizeof(pUnkControl), offsetof(IDENTRY, m_pUnkControl));
	else
	{
	    IDENTRY identry;

	    identry.m_tid = GetCurrentThreadId();
	    identry.m_pUnkControl = pUnkControl;
	    iID = sg_idtable.IndexOf((void *)&identry.m_tid,
		    sizeof(identry.m_tid) + sizeof(identry.m_pUnkControl),
		    offsetof(IDENTRY, m_tid));
	}

	// if found, another thread created identity for same object;
	// addref pStdID and return it if requested
	if (iID != -1)
	{
	    if (ppStdIDExisting)
	    {
		*ppStdIDExisting = sg_idtable[iID].m_pStdID;
		(*ppStdIDExisting)->AddRef();
	    }

	    hr = CO_E_OBJISREG;

#if DBG == 1
	    // verify correctness of entry
	    OID oidT;

	    Assert(pUnkControl == sg_idtable[iID].m_pUnkControl);
	    sg_idtable[iID].m_pStdID->GetObjectID(&oidT);
	    Assert(IsEqualGUID(oidT, sg_idtable[iID].m_oid));

	    if (FreeThreading)
		Assert(sg_idtable[iID].m_tid == NULL);
	    else
		Assert(sg_idtable[iID].m_tid == GetCurrentThreadId());
#endif
	}
	else
	{
	    if (ppStdIDExisting)
		*ppStdIDExisting = NULL;

	    // no id yet; add at end; no addrefs
	    IDENTRY identry;
	    identry.m_oid = oid;
	    identry.m_tid = FreeThreading ? NULL : GetCurrentThreadId();
	    identry.m_pUnkControl = pUnkControl;
	    identry.m_pStdID = pStdID;

	    if (sg_idtable.Add(identry) == -1)
		hr = E_OUTOFMEMORY;
	}
    sg_mxsTable.Release();

    return hr;
}


//+-------------------------------------------------------------------
//
//  Function:	ClearObjectUnk, private
//
//  Synopsis:	Called during the disconnect of the id only.  Clears
//		the pUnkControl entry for the identity.
//
//		Identity lookup is based on oid.
//
//  Arguments:	[oid] -- The identity; used for asserts only.
//		[pUnkControl] -- The object for which the identity is being
//			revoked; used for asserts only.
//		[pStdID] -- The identity object; used for asserts only.
//
//  Returns:	S_OK - removed successfully
//
//		CO_E_OBJNOTREG - not present (often ignored).
//
//  History:	02-May-94   CraigWi	Created.
//
//--------------------------------------------------------------------

INTERNAL ClearObjectUnk(REFOID oid, IUnknown *pUnkControl, IStdIdentity *pStdID)
{
    HRESULT hr = NOERROR;

    sg_mxsTable.Request();
	// scan for value in map; may find one attached to object created by now
	int iID;

	if (FreeThreading)
	    iID = sg_idtable.IndexOf((void *)&oid, sizeof(oid), offsetof(IDENTRY, m_oid));
	else
	{
	    IDENTRY identry;

	    identry.m_oid = oid;
	    identry.m_tid = GetCurrentThreadId();
	    iID = sg_idtable.IndexOf((void *)&identry.m_oid,
		    sizeof(identry.m_oid) + sizeof(identry.m_tid),
		    offsetof(IDENTRY, m_oid));
	}

	// if found, clear the pUnkControl field
	if (iID != -1)
	{
#if DBG == 1
	    // verify correctness of entry
	    OID oidT;

	    Assert(pUnkControl == sg_idtable[iID].m_pUnkControl);
	    pStdID->GetObjectID(&oidT);
	    Assert(IsEqualGUID(oidT, sg_idtable[iID].m_oid));
	    Assert(pStdID == sg_idtable[iID].m_pStdID);
	    if (FreeThreading)
		Assert(sg_idtable[iID].m_tid == NULL);
	    else
		Assert(sg_idtable[iID].m_tid == GetCurrentThreadId());
#endif

	    sg_idtable[iID].m_pUnkControl = NULL;
	}
	else
	    hr = CO_E_OBJNOTREG;
    sg_mxsTable.Release();

    return hr;
}



//+-------------------------------------------------------------------
//
//  Function:	ClearObjectID, private
//
//  Synopsis:	Called during the revokation of the id only.  Clears
//		the identity entry in the table.
//
//		Identity lookup is based on oid.
//
//  Arguments:	[oid] -- The identity; used for asserts only.
//		[pUnkControl] -- The object for which the identity is being
//			revoked; used for asserts only.
//		[pStdID] -- The identity object; used for asserts only.
//
//  Returns:	S_OK - removed successfully
//
//		CO_E_OBJNOTREG - not present (often ignored).
//
//  History:	11-Dec-93   CraigWi	Created.
//
//--------------------------------------------------------------------

INTERNAL ClearObjectID(REFOID oid, IUnknown *pUnkControl, IStdIdentity *pStdID)
{
    HRESULT hr = NOERROR;

    sg_mxsTable.Request();
	// scan for value in map; may find one attached to object created by now
	int iID;

	if (FreeThreading)
	    iID = sg_idtable.IndexOf((void *)&oid, sizeof(oid), offsetof(IDENTRY, m_oid));
	else
	{
	    IDENTRY identry;

	    identry.m_oid = oid;
	    identry.m_tid = GetCurrentThreadId();
	    iID = sg_idtable.IndexOf((void *)&identry.m_oid,
		    sizeof(identry.m_oid) + sizeof(identry.m_tid),
		    offsetof(IDENTRY, m_oid));
	}

	// if found, remove it.
	if (iID != -1)
	{
#if DBG == 1
	    // verify correctness of entry
	    OID oidT;

# ifndef _CAIRO_
	    // BUGBUG [mikese] There is a race during shutdown of a
	    //  multithreaded server which renders this assertion invalid.
	    // CraigWi is fixing the race (see CODEWORK in stdid.cxx l 1570)
	    //  but in the mean time we disable the assert for Cairo builds.
	    Assert(pUnkControl == sg_idtable[iID].m_pUnkControl);
# endif
	    pStdID->GetObjectID(&oidT);
	    Assert(IsEqualGUID(oidT, sg_idtable[iID].m_oid));
	    Assert(pStdID == sg_idtable[iID].m_pStdID);
	    if (FreeThreading)
		Assert(sg_idtable[iID].m_tid == NULL);
	    else
		Assert(sg_idtable[iID].m_tid == GetCurrentThreadId());
#endif

	    Assert(sg_idtable.GetSize() != 0);
	    int iLast = sg_idtable.GetSize() - 1;
	    if (iID != iLast)
	    {
		// element removed is not last; copy last element to current
		sg_idtable[iID] = sg_idtable[iLast];
	    }

	    // now setsize to one less to remove the now unused last element

	    sg_idtable.SetSize(iLast);
	}
	else
	    hr = CO_E_OBJNOTREG;
    sg_mxsTable.Release();

    return hr;
}



//+-------------------------------------------------------------------
//
//  Function:	IDTableUninitialize, private
//
//  Synopsis:	Clears the id table memory for the current thread (or all
//              if party model).  This involves scanning the table and for
//              entries on the current thread, calling
//              IMarshal::DisconnectObject.
//		
//		The purpose of this routine is to simulate inter-thread rundown
//		as well as clean up memory.
//
//  History:	23-Dec-93   CraigWi	Created.
//		26-Apr-94   CraigWi	Now called per-thread and disconnects
//
//  Note:       This function should only be called when the IDTable
//              really needs to be uninitialized.  For the party model, this
//              means that it should only be called when the last thread
//              is exiting.
//
//              This function must NOT assume that it is being called within
//              a critical section.
//
//--------------------------------------------------------------------

INTERNAL_(void) IDTableThreadUninitialize(void)
{
    DWORD tid = GetCurrentThreadId();   // CODEWORK get apartment for thread

    // CODEWORK: we will hold this for each entry; this needs to be changed
    // later since we could skip an entry if another thread
    // deleted an entry before our point in the list.
    sg_mxsTable.Request();

    for (int i = 0; i < sg_idtable.GetSize(); i++)
    {
        if (IsPartyApartment() || sg_idtable[i].m_tid == tid)
        {
            CairoleDebugOut((DEB_ERROR,
                             "Object at %lx still has connections\n",
                             sg_idtable[i].m_pUnkControl));

            // NOTE: for party apartment model, we don't have to check
            // for valid entry since we only scan it once, at process exit.

            Assert(IsValidInterface(sg_idtable[i].m_pStdID));
            IStdIdentity *pStdID = sg_idtable[i].m_pStdID;
            pStdID->AddRef();

            // release semaphore for this period since the disconnect
            // could take a long time and the revoke id needs access to
            // the table (even though it won't find anything).
            sg_mxsTable.Release();

            IMarshal *pMarshal;
            if (pStdID->QueryInterface(IID_IMarshal, (void **)&pMarshal) == NOERROR)
            {
                pMarshal->DisconnectObject(0);
                pMarshal->Release();
            }

            pStdID->RevokeObjectID();
            pStdID->Release();

            // re-request the table since we need to guard the GetSize above
            sg_mxsTable.Request();
        }
    }

    sg_mxsTable.Release();
}

//+-------------------------------------------------------------------------
//
//  Function:   IDTableProcessUninitialize
//
//  Synopsis:   Process specific IDTable uninitialization
//
//  Effects:    Frees up table memory
//
//  Requires:   All thread specific uninitialization already complete.  This
//              function assumes that the caller is holding the
//              g_mxsSingleThreadOle mutex (so that no other thread is trying
//              to use the table while we clean it up).
//
//  Modifies:   sg_idtable
//
//  History:    29-Jun-94 AlexT     Created
//
//--------------------------------------------------------------------------

INTERNAL_(void) IDTableProcessUninitialize(void)
{
    sg_idtable.RemoveAll();
}

#if DBG == 1

//+-------------------------------------------------------------------
//
//  Function:   Dbg_FindRemoteHdlr
//
//  Synopsis:   finds a remote object handler for the specified object,
//              and returns an instance of IMarshal on it. This is debug
//              code for assert that reference counts are as expected.
//
//  Exceptions: None
//
//  History:    23-Nov-93   Rickhi       Created
//		23-Dec-93   CraigWi	 Changed to identity object
//
//--------------------------------------------------------------------

extern "C"
IMarshal * _stdcall Dbg_FindRemoteHdlr(IUnknown *punkObj)
{
    //  validate input parms
    Win4Assert(punkObj);

    IMarshal *pIM = NULL;
    IStdIdentity *pStdID;
    if (LookupIDFromUnk(punkObj, FALSE, &pStdID) == NOERROR)
    {
	(void)pStdID->QueryInterface(IID_IMarshal, (void **)&pIM);
	pStdID->Release();
    }
    return pIM;
}

#endif	//  DBG == 1




