/*===================================================================
Microsoft Denali

Microsoft Confidential.
Copyright 1997 Microsoft Corporation. All Rights Reserved.

Component: Typelibrary cache

File: tlbcache.cpp

Owner: DmitryR

This is the typelibrary cache source file.
===================================================================*/

#include "denpre.h"
#pragma hdrstop

#include "tlbcache.h"
#include "memchk.h"

/*===================================================================
  Globals
===================================================================*/

CTypelibCache g_TypelibCache;

/*===================================================================
  C  T y p e l i b  C a c h e  E n t r y
===================================================================*/

/*===================================================================
CTypelibCacheEntry::CTypelibCacheEntry

Constructor
===================================================================*/
CTypelibCacheEntry::CTypelibCacheEntry()
    :
    m_fInited(FALSE), m_fIdsCached(FALSE), m_fStrAllocated(FALSE),
    m_wszProgId(NULL), m_clsid(CLSID_NULL), m_cmModel(cmUnknown), 
    m_idOnStartPage(DISPID_UNKNOWN), m_idOnEndPage(DISPID_UNKNOWN),
    m_gipTypelib(NULL_GIP_COOKIE)
    {
    }

/*===================================================================
CTypelibCacheEntry::~CTypelibCacheEntry

Destructor
===================================================================*/
CTypelibCacheEntry::~CTypelibCacheEntry()
    {
    if (m_gipTypelib != NULL_GIP_COOKIE)
        {
        g_GIPAPI.Revoke(m_gipTypelib);
        }
        
    if (m_fStrAllocated)
        {
        Assert(m_wszProgId);
		free(m_wszProgId);
        }
    }

/*===================================================================
CTypelibCacheEntry::StoreProgID

Store ProgID with the structure

Parameters
    wszProgId       ProgId
    cbProgId        ProgId byte length

Returns
    HRESULT
===================================================================*/
HRESULT CTypelibCacheEntry::StoreProgID
(
LPWSTR wszProgId, 
DWORD  cbProgId
)
    {
    Assert(wszProgId);
    Assert(*wszProgId != L'\0');
    Assert(cbProgId == (wcslen(wszProgId) * sizeof(WCHAR)));

    // required buffer length
    DWORD cbBuffer = cbProgId + sizeof(WCHAR);
    WCHAR *wszBuffer = (WCHAR *)m_rgbStrBuffer;

    if (cbBuffer > sizeof(m_rgbStrBuffer))
        {
        // the prog id doesn't fit into the member buffer -> allocate
        wszBuffer = (WCHAR *)malloc(cbBuffer);
        if (!wszBuffer)
            return E_OUTOFMEMORY;
        m_fStrAllocated = TRUE;
        }
    
    memcpy(wszBuffer, wszProgId, cbBuffer);
    m_wszProgId = wszBuffer;
    return S_OK;
    }

/*===================================================================
CTypelibCacheEntry::InitByProgID

Real constructor.
Store ProgID. Init CLinkElem portion with ProgID.

Parameters
    wszProgId       ProgId
    cbProgId        ProgId byte length

Returns
    HRESULT
===================================================================*/
HRESULT CTypelibCacheEntry::InitByProgID
(
LPWSTR wszProgId, 
DWORD  cbProgId
)
    {
    StoreProgID(wszProgId, cbProgId);
    
    // init link with prog id as key (length excludes null term)
	CLinkElem::Init(m_wszProgId, cbProgId);
	m_fInited = TRUE;
	return S_OK;
    }


/*===================================================================
CTypelibCacheEntry::InitByCLSID

Real constructor.
Store ProgID. Init CLinkElem portion with CLSID.

Parameters
    clsid           CLSID
    wszProgId       ProgId

Returns
    HRESULT
===================================================================*/
HRESULT CTypelibCacheEntry::InitByCLSID
(
const CLSID &clsid, 
LPWSTR wszProgid
)
    {
    StoreProgID(wszProgid, CbWStr(wszProgid));
    m_clsid = clsid;
        
    // init link with CLSID id as key
	CLinkElem::Init(&m_clsid, sizeof(m_clsid));
	m_fInited = TRUE;
	return S_OK;
    }

/*===================================================================
  C  T y p e l i b  C a c h e
===================================================================*/

/*===================================================================
CTypelibCache::CTypelibCache

Constructor
===================================================================*/
CTypelibCache::CTypelibCache()
    :
    m_fInited(FALSE)
    {
    }
    
/*===================================================================
CTypelibCache::~CTypelibCache

Destructor
===================================================================*/
CTypelibCache::~CTypelibCache()
    {
    UnInit();
    }

/*===================================================================
CTypelibCache::Init

Init

Returns
    HRESULT
===================================================================*/
HRESULT CTypelibCache::Init()
    {
    HRESULT hr;

    hr = m_htProgIdEntries.Init(199);
    if (FAILED(hr))
        return hr;

    hr = m_htCLSIDEntries.Init(97);
    if (FAILED(hr))
        return hr;

    ErrInitCriticalSection(&m_csLock, hr);
    if (FAILED(hr))
        return(hr);
        
    m_fInited = TRUE;
    return S_OK;
    }
    
/*===================================================================
CTypelibCache::UnInit

UnInit

Returns
    HRESULT
===================================================================*/
HRESULT CTypelibCache::UnInit()
    {
    CTypelibCacheEntry *pEntry;
    CLinkElem *pLink;

    // ProgID Hash table
    pLink = m_htProgIdEntries.Head();
    while (pLink)
        {
        pEntry = static_cast<CTypelibCacheEntry *>(pLink);
        pLink = pLink->m_pNext;

        // remove the entry
        delete pEntry;
        }
    m_htProgIdEntries.UnInit();

    // CLSID Hash table
    pLink = m_htCLSIDEntries.Head();
    while (pLink)
        {
        pEntry = static_cast<CTypelibCacheEntry *>(pLink);
        pLink = pLink->m_pNext;

        // remove the entry
        delete pEntry;
        }
    m_htCLSIDEntries.UnInit();

    // Critical section
    if (m_fInited)
        {
        DeleteCriticalSection(&m_csLock);
        m_fInited = FALSE;
        }

    return S_OK;
    }

/*===================================================================
CTypelibCache::CreateComponent

Create the component using the cached info.
Adds cache entry if needed.
To be called from Server.CreateObject

Parameters
    bstrProgID      prog id
    pHitObj         HitObj needed for creation
    ppdisp          [out] IDispatch *
    pclsid          [out] CLSID

Returns
    HRESULT
===================================================================*/
HRESULT CTypelibCache::CreateComponent
(
BSTR         bstrProgID,
CHitObj     *pHitObj,
IDispatch  **ppdisp,
CLSID       *pclsid
)
    {
    HRESULT hr;
    CLinkElem *pElem;
    CTypelibCacheEntry *pEntry;
    CComponentObject *pObj;
    COnPageInfo OnPageInfo;
    CompModel cmModel; 

    *pclsid = CLSID_NULL;
    *ppdisp = NULL;

    if (bstrProgID == NULL || *bstrProgID == L'\0')
        return E_FAIL;

    WCHAR *wszProgId = bstrProgID;
    DWORD  cbProgId  = CbWStr(wszProgId);    // do strlen once

    BOOL fFound = FALSE;
    BOOL bDispIdsCached = FALSE;
    
    Lock();
    pElem = m_htProgIdEntries.FindElem(wszProgId, cbProgId);
    if (pElem)
        {
        // remember the elements of the entry while inside lock
        pEntry = static_cast<CTypelibCacheEntry *>(pElem);

        // return clsid
        *pclsid = pEntry->m_clsid;

        // prepate OnPageInfo with DispIds to pass to the creation function
        if (pEntry->m_fIdsCached)
            {
            OnPageInfo.m_rgDispIds[ONPAGEINFO_ONSTARTPAGE] = pEntry->m_idOnStartPage;
            OnPageInfo.m_rgDispIds[ONPAGEINFO_ONENDPAGE] = pEntry->m_idOnEndPage;
            bDispIdsCached = TRUE;
            }
        // remember the model
        cmModel = pEntry->m_cmModel;

        fFound = TRUE;
        }
    UnLock();

    if (fFound)
        {
        // create the object
        hr = pHitObj->PPageComponentManager()->AddScopedUnnamedInstantiated
            (
            csPage,
            *pclsid,
            cmModel,
            bDispIdsCached ? &OnPageInfo : NULL,
            &pObj
            );

        // get IDispatch*
        if (SUCCEEDED(hr))
            hr = pObj->GetAddRefdIDispatch(ppdisp);

        // return if succeeded
        if (SUCCEEDED(hr))
            {
            // don't keep the object around unless needed
            if (pObj->FEarlyReleaseAllowed())
                pHitObj->PPageComponentManager()->RemoveComponent(pObj);
            return S_OK;
            }

        // on failure remove from the cache and pretend
        // as if it was never found

        Lock();
        pElem = m_htProgIdEntries.DeleteElem(wszProgId, cbProgId);
        UnLock();

        if (pElem)
            {
            // Element removed from cache - delete the CacheEntry
            pEntry = static_cast<CTypelibCacheEntry *>(pElem);
            delete pEntry;
            }

        // don't return bogus CLSID
        *pclsid = CLSID_NULL;
        }

    // Not found in the cache. Create the object, get the info, and
    // insert the new cache entry.

   	hr = CLSIDFromProgID((LPCOLESTR)wszProgId, pclsid);
   	if (FAILED(hr))
   	    return hr;  // couldn't even get clsid - don't cache

    hr = pHitObj->PPageComponentManager()->AddScopedUnnamedInstantiated
        (
        csPage,
        *pclsid,
        cmUnknown,
        NULL,
        &pObj
        );
   	if (FAILED(hr))
   	    return hr;  // couldn't create object - don't cache

    // object created - get IDispatch*
    if (SUCCEEDED(hr))
        hr = pObj->GetAddRefdIDispatch(ppdisp);
   	if (FAILED(hr))
   	    return hr;  // couldn't get IDispatch* - don't cache

    // create new CTypelibCacheEntry
    pEntry = new CTypelibCacheEntry;
    if (!pEntry)
        return S_OK; // no harm

    // init link entry
    if (FAILED(pEntry->InitByProgID(wszProgId, cbProgId)))
        {
        delete pEntry;
        return S_OK; // no harm
        }
        
    // remember stuff in pEntry
    pEntry->m_clsid = *pclsid;
    pEntry->m_cmModel = pObj->GetModel();

    const COnPageInfo *pOnPageInfo = pObj->GetCachedOnPageInfo();
    if (pOnPageInfo)
        {
        pEntry->m_fIdsCached = TRUE;
        pEntry->m_idOnStartPage = pOnPageInfo->m_rgDispIds[ONPAGEINFO_ONSTARTPAGE];
        pEntry->m_idOnEndPage = pOnPageInfo->m_rgDispIds[ONPAGEINFO_ONENDPAGE];
        }
    else
        {
        pEntry->m_fIdsCached = FALSE;
        pEntry->m_idOnStartPage = DISPID_UNKNOWN;
        pEntry->m_idOnEndPage = DISPID_UNKNOWN;
        }

    // try to get the typelib
    pEntry->m_gipTypelib = NULL_GIP_COOKIE;
    if (FIsWinNT())  // don't do GIP cookies on Win95
        {
        ITypeInfo *ptinfo;
        if (SUCCEEDED((*ppdisp)->GetTypeInfo(0, 0, &ptinfo)))
            {
            ITypeLib *ptlib;
            UINT idx;
            if (SUCCEEDED(ptinfo->GetContainingTypeLib(&ptlib, &idx)))
                {
                // got it! convert to gip cookie
                DWORD gip;
                if (SUCCEEDED(g_GIPAPI.Register(ptlib, IID_ITypeLib, &gip)))
                    {
                    // remember the gip cookie with pEntry
                    pEntry->m_gipTypelib = gip;
                    }
                
                ptlib->Release();
                }
            ptinfo->Release();
            }
        }
        
    // pEntry is ready -- try to insert it into cache
    BOOL fInserted = FALSE;
    Lock();
    // make sure some other thread didn't insert it already
    if (m_htProgIdEntries.FindElem(wszProgId, cbProgId) == NULL)
        {
        if (m_htProgIdEntries.AddElem(pEntry))
            fInserted = TRUE;
        }
    UnLock();

    // cleanup
    if (!fInserted)
        delete pEntry;
        
    // don't keep the object around unless needed
    if (pObj->FEarlyReleaseAllowed())
        pHitObj->PPageComponentManager()->RemoveComponent(pObj);

    return S_OK;
    }

/*===================================================================
CTypelibCache::RememberProgidToCLSIDMapping

Adds an entry to CLSID hashtable.
To be called from template after mapping ProgId to CLSID.

Parameters
    wszProgID      prog id
    clsid          clsid

Returns
    HRESULT
===================================================================*/
HRESULT CTypelibCache::RememberProgidToCLSIDMapping
(
WCHAR *wszProgid, 
const CLSID &clsid
)
    {
    HRESULT hr;
    CLinkElem *pElem;
    CTypelibCacheEntry *pEntry;

    // check if already there first
    BOOL fFound = FALSE;
    Lock();
    if (m_htCLSIDEntries.FindElem(&clsid, sizeof(CLSID)) != NULL)
        fFound = TRUE;
    UnLock();
    if (fFound)
        return S_OK;
    
    // create new entry    
    pEntry = new CTypelibCacheEntry;
    if (!pEntry)
        return E_OUTOFMEMORY;

    BOOL fInserted = FALSE;
    
    // remember prog id and init link entry
    hr = pEntry->InitByCLSID(clsid, wszProgid);
    
    if (SUCCEEDED(hr))
        {
        Lock();
        // make sure some other thread didn't insert it already
        if (m_htCLSIDEntries.FindElem(&clsid, sizeof(CLSID)) == NULL)
            {
            if (m_htCLSIDEntries.AddElem(pEntry))
                fInserted = TRUE;
            }
        UnLock();
        }

    // cleanup
    if (!fInserted)
        delete pEntry;
        
    return hr;
    }

/*===================================================================
CTypelibCache::UpdateMappedCLSID

Update CLSID since remembered.

To be called from object creation code to update CLSID
if the object creation failed.

Parameters
    *pclsid         [in, out] CLSID

Returns
    HRESULT
        S_FALSE = didn't change
        S_OK    = did change and the new one found
===================================================================*/
HRESULT CTypelibCache::UpdateMappedCLSID
(
CLSID *pclsid
)
    {
    HRESULT hr = S_FALSE; // not found
    CLinkElem *pElem;
    CTypelibCacheEntry *pEntry;
    CLSID clsidNew;
    
    Lock();
    // Do everything under lock so the ProgID stored in
    // the entry is still valid.
    // Not very perfomant -- but is is an error path anyway
    
    pElem = m_htCLSIDEntries.FindElem(pclsid, sizeof(CLSID));
    if (pElem)
        {
        pEntry = static_cast<CTypelibCacheEntry *>(pElem);

        // find new mapping
        if (SUCCEEDED(CLSIDFromProgID(pEntry->m_wszProgId, &clsidNew)))
            {
            // compare with the old one
            if (!IsEqualCLSID(clsidNew, *pclsid))
                {
                // the mapping did change!
                *pclsid = clsidNew;
                hr = S_OK;
                }
            }
        }
        
    UnLock();

    return hr;
    }