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

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

Component: Component Collection

File: Compcol.cpp

Owner: DmitryR

This is the Component Collection source file.

Component collection replaces:  (used in:)
COleVar, COleVarList            (HitObj, Session, Application)
CObjectCover                    (HitObj, Server, Session)
VariantLink HasTable            (Session, Application)
===================================================================*/
#include "denpre.h"
#pragma hdrstop

#include "Context.h"
#include "MTAcb.h"
#include "Request.h"
#include "Response.h"
#include "Server.h"
#include "tlbcache.h"
#include "memchk.h"

/*===================================================================
  Defines for hash table sizes
===================================================================*/

#define HT_TAGGED_OBJECTS_BUCKETS_MAX   19
#define HT_PROPERTIES_BUCKETS_MAX       17
#define HT_IUNKNOWN_PTRS_BUCKETS_MAX    23

#define HT_PAGE_OBJECTS_BUCKETS_MAX     17

/*===================================================================
  Static utility function prototypes
===================================================================*/

static HRESULT QueryOnPageInfo
    (
    IDispatch *pDisp,
    COnPageInfo *pOnPageInfo
    );

static HRESULT CLSIDToMultibyteString
    (
    CLSID ClsId,
    char *psz,
    int cch
    );

#define REG_MODEL_TEXT_LEN_MAX  20  // big enough for "Apartment"
static CompModel RegStrToCompModel
    (
    BYTE *pb,
    DWORD cb
    );

/*===================================================================
  Static utility functions code
===================================================================*/

/*===================================================================
QueryOnPageInfo

Query dispatch ids for OnStartPage and OnEndPage

Parameters:
    IDispatch   *pDisp              Object to query
    COnPageInfo *pOnPageInfo        Struct to fill in

Returns:
    HRESULT
===================================================================*/
HRESULT QueryOnPageInfo
(
IDispatch   *pDisp,
COnPageInfo *pOnPageInfo
)
    {
    static LPOLESTR BStrEntryPoints[ONPAGE_METHODS_MAX] =
        {
        L"OnStartPage",
        L"OnEndPage"
        };

    HRESULT hr = S_OK;
    for (int i = 0; i < ONPAGE_METHODS_MAX; i++)
        {
        hr = pDisp->GetIDsOfNames
            (
            IID_NULL,
            &BStrEntryPoints[i],
            1,
            LOCALE_SYSTEM_DEFAULT,
            &pOnPageInfo->m_rgDispIds[i]
            );
        if (FAILED(hr))
            {
            if (hr != DISP_E_UNKNOWNNAME &&
                hr != DISP_E_MEMBERNOTFOUND)
                {
                break;
                }

            // If UNKNOWNNAME, set dispid to DISPID_UNKNOWN
            hr = S_OK;
            pOnPageInfo->m_rgDispIds[i] = DISPID_UNKNOWN;
            }
        }
    return hr;
    }

/*===================================================================
CLSIDToMultibyteString

Converts CLSID into multibyte string
Used in CompModelFromCLSID

Parameters:
    CLSID  ClsId     (in) CLSID to convert
    char  *pb        put string into this buffer
    int    cch       of this length

Returns:
    HRESULT
===================================================================*/
HRESULT CLSIDToMultibyteString
(
CLSID  ClsId,
char  *psz,
int    cch
)
    {
    // First convert it to OLECHAR string
    OLECHAR *pszWideClassID = NULL; // temp wide string classid
    HRESULT hr = StringFromCLSID(ClsId, &pszWideClassID);
    if (FAILED(hr))
        return hr;

    // OLECHAR to MultiByte
    BOOL f = WideCharToMultiByte
        (
        CP_ACP,         // code page
        0,              // performance and mapping flags
        pszWideClassID, // address of wide-character string
        -1,             // length (-1 == null-terminated)
        psz,            // address of buffer for new string
        cch,            // size of buffer for new string
        NULL,           // address of default for unmappable
                        //      characters; quickest if null
        NULL            // address of flag set when default
                        //      char. used; quickest if null
        );
    if (f == FALSE)
        hr = E_FAIL;

    if (pszWideClassID)
        CoTaskMemFree(pszWideClassID);
    return hr;
    }

/*===================================================================
RegStrToCompModel

Get CompModel value from a registry string

Parameters:
    char  *pb        string as returned from registry
    int    cb        length returned from registry

Returns:
    HRESULT
===================================================================*/
CompModel RegStrToCompModel
(
BYTE *pb,
DWORD cb
)
    {
    CompModel cmModel = cmSingle; // assume single

    if (cb == 5)  // 5 include '\0'
        {
        if (!(_strnicmp((const char*)pb, "Both", cb)))
            cmModel = cmBoth;
        else if (!(_strnicmp((const char*)pb, "Free", cb)))
            cmModel = cmFree;
        }
    else if (cb == 10)  // 10 include '\0'
        {
        if (!(_strnicmp((const char*)pb, "Apartment", cb)))
            cmModel = cmApartment;
        }

    return cmModel;
    }

/*===================================================================
  Public utility functions code
===================================================================*/

/*===================================================================
CompModelFromCLSID

Get object's model and InProc flag by its CLSID from the registry

Parameters:
    CLSID     &ClsId       (in)
    CompModel *pcmModel    (out) Model (optional)
    BOOL      *pfInProc    (out) InProc flag (optional)

Returns:
    CompModel (cmFree, cmBoth, etc.)
===================================================================*/
HRESULT CompModelFromCLSID
(
const CLSID &ClsId,
CompModel   *pcmModel,
BOOL        *pfInProc
)
    {
    if (!Glob(fTrackThreadingModel) && !pfInProc)
        {
        // ignore registry value for threading model and
        // inproc flag is not requested -> take short return
        if (pcmModel)
            *pcmModel = cmUnknown;
        return S_OK;
        }

    // default returns
    CompModel cmModel  = cmSingle;   // assume single
    BOOL      fInProc  = TRUE;       // assume inproc

    HRESULT hr = S_OK;

    // Convert ClsId to multibyte string

    char szClassID[50];
    hr = CLSIDToMultibyteString(ClsId, szClassID, sizeof(szClassID));
    if (FAILED(hr))
        return hr;

    /*  query the registry; threading model is stored as:
        HKEY_CLASSES_ROOT
          key: CLSID
            key: <object's classid>
              key: InprocServer32
                name: ThreadingModel data: "Both" | "Apartment"
    */

    // Navigate the registry to "InprocServer32" key

    HKEY hKey1 = NULL;  // handle of open reg key
    HKEY hKey2 = NULL;  // handle of open reg key
    HKEY hKey3 = NULL;  // handle of open reg key

    if (SUCCEEDED(hr))
        {
        int nRet = RegOpenKeyExA
            (
            HKEY_CLASSES_ROOT,
            "CLSID",
            0,
            KEY_READ,
            &hKey1
            );
        if (nRet != ERROR_SUCCESS)
            hr = E_FAIL;
        }

    if (SUCCEEDED(hr))
        {
        int nRet = RegOpenKeyExA
            (
            hKey1,
            szClassID,
            0,
            KEY_READ,
            &hKey2
            );
        if (nRet != ERROR_SUCCESS)
            hr = E_FAIL;
        }

    // Get the stuff from the registry "InprocServer32" key

    if (SUCCEEDED(hr))
        {
        int nRet = RegOpenKeyExA
            (
            hKey2,
            "InprocServer32",
            0,
            KEY_READ,
            &hKey3
            );
        if (nRet == ERROR_SUCCESS)
            {
            DWORD cbData = REG_MODEL_TEXT_LEN_MAX;
            BYTE  szData[REG_MODEL_TEXT_LEN_MAX];

            nRet = RegQueryValueExA
                (
                hKey3,
                "ThreadingModel",
                NULL,
                NULL,
                szData,
                &cbData
                );
            if (nRet == ERROR_SUCCESS)
                cmModel = RegStrToCompModel(szData, cbData);

            if (cmModel == cmBoth)
                {
                // Some objects marked as "Both" ASP treats as
                // "Apartment". These objects should be marked in
                // the registry as "ASPComponentNonAgile"

                nRet = RegQueryValueExA
                    (
                    hKey3,
                    "ASPComponentNonAgile",
                    NULL,
                    NULL,
                    szData,
                    &cbData
                    );

                // If the key is found pretend it's "apartment"
                if (nRet == ERROR_SUCCESS)
                    cmModel = cmApartment;
                }
            }
        else
            {
            // if there is no InprocServer32 key,
            // then it must be a localserver or remote server.
            fInProc = FALSE;
            }
        }

    // clean up registry keys
    if (hKey3)
        RegCloseKey(hKey3);
    if (hKey2)
        RegCloseKey(hKey2);
    if (hKey1)
        RegCloseKey(hKey1);

    // return values
    if (pcmModel)
        *pcmModel = Glob(fTrackThreadingModel) ? cmModel : cmUnknown;
    if (pfInProc)
        *pfInProc = fInProc;

    return hr;
    }

/*===================================================================
FIsIntrinsic

Checks if the given IDispatch * points to an ASP intrinsic.

Parameters:
    pdisp       pointer to check

Returns:
    TRUE if Intrinsic
===================================================================*/
BOOL FIsIntrinsic
(
IDispatch *pdisp
)
    {
    if (!pdisp)
        return FALSE; // null dispatch pointer - not an intrinsic

    IUnknown *punk = NULL;
    if (FAILED(pdisp->QueryInterface(IID_IDenaliIntrinsic, (void **)&punk)))
        return FALSE;

    Assert(punk);
    punk->Release();
    return TRUE;
    }

/*===================================================================
FIsSimpleVariant

Checks if the given VARIANT is a simple one

Parameters:
    pvar       variant to check

Returns:
    TRUE if [for sure] simple, FALSE if [possibly] not
===================================================================*/
inline FIsSimpleVariant(VARIANT *pvar)
    {
    switch (V_VT(pvar))
        {
    case VT_BSTR:
    case VT_I2:
    case VT_I4:
    case VT_BOOL:
    case VT_DATE:
    case VT_R4:
    case VT_R8:
        return TRUE;
        }
    return FALSE;
    }


/*===================================================================
  C  C o m p o n e n t  O b j e c t
===================================================================*/

/*===================================================================
CComponentObject::CComponentObject

CComponentObject constructor

Parameters:
    CompScope scScope       Object scope
    CompType  ctType        Object type
    CompModel cmModel       Object threading model

Returns:
===================================================================*/
CComponentObject::CComponentObject
(
CompScope scScope,
CompType  ctType,
CompModel cmModel
)
    :
    m_csScope(scScope), m_ctType(ctType), m_cmModel(cmModel),
    m_fAgile(FALSE),
    m_fOnPageInfoCached(FALSE),
    m_fOnPageStarted(FALSE),
    m_fFailedToInstantiate(FALSE), m_fInstantiatedTagged(FALSE),
    m_fInPtrCache(FALSE),
    m_fVariant(FALSE),
    m_fNameAllocated(FALSE),
    m_dwGIPCookie(NULL_GIP_COOKIE),
    m_pDisp(NULL), m_pUnknown(NULL),
    m_pCompNext(NULL), m_pCompPrev(NULL)
    {
    }

#ifdef DBG
/*===================================================================
CComponentObject::AssertValid

Test to make sure that this is currently correctly formed
and assert if it is not.

Returns:
===================================================================*/
void CComponentObject::AssertValid() const
    {
    Assert(m_ctType != ctUnknown);
    }
#endif

/*===================================================================
CComponentObject::~CComponentObject

CComponentObject destructor
Releases interface pointers

Parameters:

Returns:
===================================================================*/
CComponentObject::~CComponentObject()
    {
    // Release all interface pointers
    Clear();

    // Name used in hash (from CLinkElem)
    if (m_fNameAllocated)
        {
        Assert(m_pKey);
        free(m_pKey);
        }
    }

/*===================================================================
CComponentObject::Init

Initialize CLinkElem portion with the object name
Needed to implement string hash

Parameters:
    LPWSTR pwszName      object name
    DWORD  cbName        name length in bytes

Returns:
    HRESULT
===================================================================*/
HRESULT CComponentObject::Init
(
LPWSTR pwszName,
DWORD  cbName
)
    {
    Assert(pwszName);
    Assert(*pwszName != L'\0');
    Assert(cbName == (wcslen(pwszName) * sizeof(WCHAR)));

    // required buffer length
    DWORD cbBuffer = cbName + sizeof(WCHAR);
    WCHAR *pwszNameBuffer = (WCHAR *)m_rgbNameBuffer;

    if (cbBuffer > sizeof(m_rgbNameBuffer))
        {
        // the name doesn't fit into the member buffer -> allocate
        pwszNameBuffer = (WCHAR *)malloc(cbBuffer);
        if (!pwszNameBuffer)
            return E_OUTOFMEMORY;
        m_fNameAllocated = TRUE;
        }

    memcpy(pwszNameBuffer, pwszName, cbBuffer);

    // init link with name as the key (length excludes null term)
    return CLinkElem::Init(pwszNameBuffer, cbName);
    }

/*===================================================================
CComponentObject::ReleaseAll

Releases all interface pointers

Parameters:

Returns:
    HRESULT
===================================================================*/
HRESULT CComponentObject::ReleaseAll()
    {
    // Release all other present interface pointers
    if (m_pDisp)
        {
        m_pDisp->Release();
        m_pDisp = NULL;
        }
    if (m_pUnknown)
        {
        m_pUnknown->Release();
        m_pUnknown = NULL;
        }

    // Variant
    if (m_fVariant)
        {
        VariantClear(&m_Variant);
        m_fVariant = FALSE;
        }

    if (m_dwGIPCookie != NULL_GIP_COOKIE)
        {
        g_GIPAPI.Revoke(m_dwGIPCookie);
        m_dwGIPCookie = NULL_GIP_COOKIE;
        }

    return S_OK;
    }

/*===================================================================
CComponentObject::Clear

Clears out data leaving link alone

Parameters:

Returns:
    HRESULT
===================================================================*/
HRESULT CComponentObject::Clear()
    {
    // Release all pointers
    TRY
        ReleaseAll();
    CATCH(nExcept)
        Assert(FALSE);
        m_pDisp = NULL;
        m_pUnknown = NULL;
        m_fVariant = FALSE;
        m_dwGIPCookie = NULL_GIP_COOKIE;
    END_TRY

    // Invalidate cached OnPageInfo
    m_fOnPageInfoCached = FALSE;
    m_fOnPageStarted = FALSE;

    // Mark it as unknown
    m_csScope = csUnknown;
    m_ctType  = ctUnknown;
    m_cmModel = cmUnknown;
    m_fAgile = FALSE;

    return S_OK;
    }

/*===================================================================
CComponentObject::Instantiate

Create object instance if it's not there already
Calls TryInstantiate() from within TRY CATCH

Parameters:
    CHitObj *pHitObj    Hit object for error reporting

Returns:
    HRESULT
===================================================================*/
HRESULT CComponentObject::Instantiate
(
CHitObj *pHitObj
)
    {
    HRESULT hr = S_OK;

    if (Glob(fExceptionCatchEnable))
        {
        TRY
            hr = TryInstantiate(pHitObj);
        CATCH(nExcept)
            HandleErrorMissingFilename(IDE_SCRIPT_OBJ_INSTANTIATE_FAILED,
                                       pHitObj,
                                       TRUE,
                                       GetName(),
                                       nExcept);
            hr = nExcept;
        END_TRY
        }
    else
        {
        hr = TryInstantiate(pHitObj);
        }

    if (FAILED(hr))
        {
        // Something failed -- need to clean-up
        ReleaseAll();

        // mark as "failed to instantiate"
        m_fFailedToInstantiate = TRUE;
        }

    return hr;
    }

/*===================================================================
CComponentObject::TryInstantiate

Create object instance if it's not there already
Called by Instantiate() from within TRY CATCH

Parameters:
    CHitObj *pHitObj    Hit object for error reporting


Returns:
    HRESULT
===================================================================*/
HRESULT CComponentObject::TryInstantiate
(
CHitObj *pHitObj
)
    {
    // Check if the object already exist
    if (m_pUnknown)
        return S_OK;

    if (m_fFailedToInstantiate)
        return E_FAIL;  // already tried once

    if (m_cmModel == cmUnknown && m_ClsId != CLSID_NULL)
        {
        CompModel cmModel;  // needed because m_cmModel is a bit fld
        HRESULT hr = CompModelFromCLSID(m_ClsId, &cmModel);
		if (FAILED(hr))
			return hr;
        m_cmModel = cmModel;
        }

    HRESULT hr = ViperCreateInstance
        (
        m_ClsId,
        IID_IUnknown,
        (void **)&m_pUnknown
        );

    // If we failed because we incorrectly cached the clsid
    // (could happen for tagged objects) try to get updated
    // cls id and retry
    if (m_ctType == ctTagged && FAILED(hr))
        {
        if (g_TypelibCache.UpdateMappedCLSID(&m_ClsId) == S_OK)
            {
            hr = ViperCreateInstance
                (
                m_ClsId,
                IID_IUnknown,
                (void **)&m_pUnknown
                );
            }
        }

    if (SUCCEEDED(hr))
        {
        if (Glob(fTrackThreadingModel) && m_cmModel == cmBoth)
            m_fAgile = TRUE;
        else
            m_fAgile = ViperCoObjectAggregatesFTM(m_pUnknown);

        hr = m_pUnknown->QueryInterface
            (
            IID_IDispatch,
            (void **)&m_pDisp
            );
        }

    // Check if application level object that
    // restricts threading -> use Global Interface Cookie

    if (SUCCEEDED(hr)
        && (m_csScope == csAppln || m_csScope == csSession)
        && !m_fAgile)
        {
        return ConvertToGIPCookie();
        }

    if (SUCCEEDED(hr) && !m_fOnPageInfoCached)
        {
        // don't really care if the following fails
        GetOnPageInfo();
        }

    return hr;
    }

/*===================================================================
CComponentObject::SetPropertyValue

Sets value from a Variant
Checks agility and possible deadlocks
Does GIP conversion

Parameters:
    VARIANT *pVariant       [in]  Value to set

Returns:
    HRESULT
===================================================================*/
HRESULT CComponentObject::SetPropertyValue
(
VARIANT *pVariant
)
    {
    Assert(m_ctType == ctProperty);

    HRESULT hr = S_OK;

    // Copy the variant value
    VariantInit(&m_Variant);
    m_fVariant = TRUE;

    hr = VariantCopyInd(&m_Variant, pVariant);
    if (FAILED(hr))
        return hr;

    // Get IDispatch pointer
    if (V_VT(&m_Variant) == VT_DISPATCH)
        {
        m_pDisp = V_DISPATCH(&m_Variant);
        }
    else
        {
        m_pDisp = NULL;
        }

    if (!m_pDisp)
        {
        m_fAgile = TRUE; // not VT_DISPATCH VARIANTs are agile
        return S_OK;
        }

    m_pDisp->AddRef();

    // Query (and cache) OnPageInfo inside TRY CATCH
    if (Glob(fExceptionCatchEnable))
        {
        TRY
            hr = GetOnPageInfo();
        CATCH(nExcept)
            hr = E_UNEXPECTED;
        END_TRY
        }
    else
        {
        hr = GetOnPageInfo();
        }

    // Don't really care if failed
    hr = S_OK;

    // Check if the assigned object is agile
    m_fAgile = ViperCoObjectAggregatesFTM(m_pDisp);

    if (Glob(fTrackThreadingModel) && !m_fAgile)
        {
        // doesn't mean it really isn't. could be one of
        // our objects marked as 'both'
        CComponentObject *pObjCopyOf = NULL;

        hr = CPageComponentManager::FindComponentWithoutContext
            (
            m_pDisp,
            &pObjCopyOf
            );

        if (hr == S_OK)
            {
            m_fAgile = pObjCopyOf->FAgile();
            }

        // end of getting of agile flag from the original object
        hr = S_OK; // even if object was not found
        }

    // Decide whether to use GIP and if invalid assignment
    // Applies only to non-agile application objects

    if (!m_fAgile && (m_csScope == csAppln || m_csScope == csSession))
        {
        if (!ViperCoObjectIsaProxy(m_pDisp) && (m_csScope == csAppln)) // deadlocking?
            {
            m_pDisp->Release();
            m_pDisp = NULL;
            VariantClear(&m_Variant);
            hr = RPC_E_WRONG_THREAD; // to tell the caller the error
            }
        else
            {
            // use GIP
            hr = ConvertToGIPCookie();
            }
        }

    return hr;
    }

/*===================================================================
CComponentObject::ConvertToGIPCookie

Convert Object to be GIP cookie. Release all pointers

Parameters:

Returns:
    HRESULT
===================================================================*/
HRESULT CComponentObject::ConvertToGIPCookie()
    {
    Assert(m_pDisp);  // has to have dispatch pointer

    if (!FIsWinNT())
        {
        // No GIPs on Win95
        // On Win95 everything is on the same thread
        // -> it is ok for the objects stay as pointers
        return S_OK;
        }

    DWORD dwCookie = NULL_GIP_COOKIE;
    HRESULT hr = g_GIPAPI.Register(m_pDisp, IID_IDispatch, &dwCookie);

    if (SUCCEEDED(hr))
        {
        Assert(dwCookie != NULL_GIP_COOKIE);

        // Release all pointeres
        ReleaseAll();

        // Store the cookie instead
        m_dwGIPCookie = dwCookie;
        }

    return hr;
    }

/*===================================================================
CComponentObject::GetOnPageInfo

Query dispatch ids for OnStartPage and OnEndPage
Calls static QueryOnPageInfo

Parameters:

Returns:
    HRESULT
===================================================================*/
HRESULT CComponentObject::GetOnPageInfo()
    {
    Assert(m_pDisp);

    HRESULT hr = QueryOnPageInfo(m_pDisp, &m_OnPageInfo);

    if (SUCCEEDED(hr))
        m_fOnPageInfoCached = TRUE;

    return hr;
    }

/*===================================================================
CComponentObject::GetAddRefdIDispatch

Get AddRef()'d Dispatch *
Handles the Global Interface Ole Cookies

Parameters:
    Dispatch **ppdisp    output

Returns:
    HRESULT
===================================================================*/
HRESULT CComponentObject::GetAddRefdIDispatch
(
IDispatch **ppdisp
)
    {
    Assert(ppdisp);

    if (m_pDisp)
        {
        *ppdisp = m_pDisp;
        (*ppdisp)->AddRef();
        return S_OK;
        }

    // try to restore from cookie
    if (m_dwGIPCookie != NULL_GIP_COOKIE)
        {
        // Even if IUnknown * needs to be returned,
        // Ask for IDispatch *, because IDispatch * is the one
        // that was put in by CoGetInterfaceFromGlobal()

        HRESULT hr = g_GIPAPI.Get
            (
            m_dwGIPCookie,
            IID_IDispatch,
            (void **)ppdisp
            );

        if (SUCCEEDED(hr))
            return S_OK;
        }

    *ppdisp = NULL;
    return E_NOINTERFACE;
    }

/*===================================================================
CComponentObject::GetAddRefdIUnknown

Get AddRef()'d IUnknown *
Handles the Global Interface Ole Cookies

Parameters:
    IUnknown **ppunk    output

Returns:
    HRESULT
===================================================================*/
HRESULT CComponentObject::GetAddRefdIUnknown
(
IUnknown **ppunk
)
    {
    Assert(ppunk);

    if (m_pUnknown)
        {
        *ppunk = m_pUnknown;
        (*ppunk)->AddRef();
        return S_OK;
        }

    // Use IDispatch (from cookie)

    IDispatch *pDisp = NULL;
    if (SUCCEEDED(GetAddRefdIDispatch(&pDisp)))
        {
        *ppunk = pDisp;  // IDispatch implements IUnknown
        return S_OK;
        }

    *ppunk = NULL;
    return E_NOINTERFACE;
    }

/*===================================================================
CComponentObject::GetVariant

Get object's values as variant
Handles the Global Interface Ole Cookies

Parameters:
    VARIANT *pVar       [out]  Variant filled in with object value

Returns:
    HRESULT
===================================================================*/
HRESULT CComponentObject::GetVariant
(
VARIANT *pVar
)
    {
    HRESULT hr = S_OK;

    VariantInit(pVar); // default variant empty

    if (m_fVariant)
        {
        // already has variant
        hr = VariantCopyInd(pVar, &m_Variant);
        }
    else if (m_pDisp)
        {
        // create variant from IDispatch*
        m_pDisp->AddRef();

        V_VT(pVar) = VT_DISPATCH;
        V_DISPATCH(pVar) = m_pDisp;
        }
    else if (m_dwGIPCookie != NULL_GIP_COOKIE)
        {
        // create variant from cookie
        IDispatch *pDisp = NULL;
        hr = g_GIPAPI.Get(m_dwGIPCookie, IID_IDispatch, (void **)&pDisp);

        if (SUCCEEDED(hr))
            {
            V_VT(pVar) = VT_DISPATCH;
            V_DISPATCH(pVar) = pDisp;
            }
        }
    else
        {
        // nowhere to get the VARIANT value from
        hr = E_POINTER;
        }

    return hr;
    }


/*===================================================================
  C  P a g e  O b j e c t
===================================================================*/

/*===================================================================
CPageObject::CPageObject

CPageObject constructor

Parameters:

Returns:
===================================================================*/
CPageObject::CPageObject()
    : m_pDisp(NULL),
      m_fStartPageCalled(FALSE), m_fEndPageCalled(FALSE)
    {
    }

#ifdef DBG
/*===================================================================
CPageObject::AssertValid

Test to make sure that this is currently correctly formed
and assert if it is not.

Returns:
===================================================================*/
void CPageObject::AssertValid() const
    {
    Assert(m_pDisp);
    }
#endif

/*===================================================================
CPageObject::~CPageObject

CPageObject destructor

Parameters:

Returns:
===================================================================*/
CPageObject::~CPageObject()
    {
    // Release interface pointer
    if (m_pDisp)
        {
        m_pDisp->Release();
        m_pDisp = NULL;
        }
    }

/*===================================================================
CPageObject::Init

Initialize CLinkElem portion with the IDispatch pointer
Needed to implement string hash

Parameters:
    IDispatch   *pDisp          dispatch pointer (AddRef()ed)
    COnPageInfo *pOnPageInfo    OnStartPage, OnEndPage Ids

Returns:
    HRESULT
===================================================================*/
HRESULT CPageObject::Init
(
IDispatch   *pDisp,
const COnPageInfo &OnPageInfo
)
    {
    Assert(pDisp);

    m_pDisp = pDisp;
    m_OnPageInfo = OnPageInfo;

    m_fStartPageCalled = FALSE;
    m_fEndPageCalled   = FALSE;

    return S_OK;
    }

/*===================================================================
CPageObject::InvokeMethod

Invokes OnPageStart() or OnPageEnd()

Parameters:
    DWORD iMethod                   which method
    CScriptingContext *pContext     scripting context (for OnStart)
    CHitObj *pHitObj                HitObj for errors

Returns:
    HRESULT
===================================================================*/
HRESULT CPageObject::InvokeMethod
(
DWORD iMethod,
CScriptingContext *pContext,
CHitObj *pHitObj
)
    {
    BOOL fOnStart = (iMethod == ONPAGEINFO_ONSTARTPAGE);

    // check if method exists
    if (m_OnPageInfo.m_rgDispIds[iMethod] == DISPID_UNKNOWN)
        return S_OK;

    // two OnStart in a row - BAD
    Assert(!(fOnStart && m_fStartPageCalled));

    // two OnEnd in a row - BAD
    Assert(!(!fOnStart && m_fEndPageCalled));

    Assert(m_pDisp);

    HRESULT hr = S_OK;

    if (Glob(fExceptionCatchEnable))
        {
        // Call method inside TRY CATCH
        TRY
            hr = TryInvokeMethod
                (
                m_OnPageInfo.m_rgDispIds[iMethod],
                fOnStart,
                pContext,
                pHitObj
                );
        CATCH(nExcept)
            if (fOnStart)
                ExceptionId
                    (
                    IID_IObjectCover,
                    IDE_COVER,
                    IDE_COVER_ON_START_PAGE_GPF
                    );
            else
                HandleErrorMissingFilename
                    (
                    IDE_COVER_ON_END_PAGE_GPF,
                    pHitObj
                    );
            hr = E_UNEXPECTED;
        END_TRY
        }
    else
        {
        // don't CATCH
        hr = TryInvokeMethod
            (
            m_OnPageInfo.m_rgDispIds[iMethod],
            fOnStart,
            pContext,
            pHitObj
            );
        }

    if (fOnStart)
        m_fStartPageCalled = TRUE;
    else
        m_fEndPageCalled = TRUE;

    return hr;
    }

/*===================================================================
CPageObject::TryInvokeMethod

Invokes OnPageStart() or OnPageEnd()

Parameters:
    DISPID     DispId           method's DISPID
    BOOL       fOnStart         TRUE if invoking OnStart
    IDispatch *pDispContext     scripting context (for OnStart)
    CHitObj   *pHitObj          HitObj for errors

Returns:
    HRESULT
===================================================================*/
HRESULT CPageObject::TryInvokeMethod
(
DISPID     DispId,
BOOL       fOnStart,
IDispatch *pDispContext,
CHitObj   *pHitObj
)
    {
    EXCEPINFO   ExcepInfo;
    DISPPARAMS  DispParams;
    VARIANT     varResult;
    VARIANT     varParam;
    UINT        nArgErr;

    memset(&DispParams, 0, sizeof(DISPPARAMS));
    memset(&ExcepInfo, 0, sizeof(EXCEPINFO));

    if (fOnStart)
        {
        VariantInit(&varParam);
        V_VT(&varParam) = VT_DISPATCH;
        V_DISPATCH(&varParam) = pDispContext;

        DispParams.rgvarg = &varParam;
        DispParams.cArgs = 1;
        }

    VariantInit(&varResult);

    // Invoke it

    HRESULT hr = m_pDisp->Invoke
        (
        DispId,          // Call method
        IID_NULL,        // REFIID - Reserved, must be NULL
        NULL,            // Locale id
        DISPATCH_METHOD, // Calling a method, not a property
        &DispParams,     // pass arguments
        &varResult,      // return value
        &ExcepInfo,      // exeption info on failure
        &nArgErr
        );

    // Ignore errors indicating that this method doesnt exist.
    if (FAILED(hr))
        {
        if (hr == E_NOINTERFACE         ||
            hr == DISP_E_MEMBERNOTFOUND ||
            hr == DISP_E_UNKNOWNNAME)
            {
            // the above errors really aren't
            hr = S_OK;
            }
        }

    /*
     * NOTE: The OnStartPage method is always called while the
     * script is running, so we use ExceptionId and let the
     * scripting engine report the error.  OnEndPage is always
     * called after the engine is gone, so we use HandleError.
     */
    if (FAILED(hr))
        {
        if (ExcepInfo.bstrSource && ExcepInfo.bstrDescription)
            {
            // User supplied error
            Exception
                (
                IID_IObjectCover,
                ExcepInfo.bstrSource,
                ExcepInfo.bstrDescription
                );
            }
        else if (fOnStart)
            {
            // Standard on-start error
            ExceptionId
                (
                IID_IObjectCover,
                IDE_COVER,
                IDE_COVER_ON_START_PAGE_FAILED,
                hr
                );
            }
        else
            {
            // Standard on-end error
            HandleErrorMissingFilename
                (
                IDE_COVER_ON_END_PAGE_FAILED,
                pHitObj
                );
            }
        }

    return hr;
    }

/*===================================================================
  C  C o m p o n e n t  C o l l e c t i o n
===================================================================*/

/*===================================================================
CComponentCollection::CComponentCollection

CComponentCollection constructor

Parameters:

Returns:
===================================================================*/
CComponentCollection::CComponentCollection()
    :
    m_csScope(csUnknown),
    m_fUseTaggedArray(FALSE), m_fUsePropArray(FALSE),
    m_fHasComProperties(FALSE),
    m_cAllTagged(0), m_cInstTagged(0),
    m_cProperties(0), m_cUnnamed(0),
    m_pCompFirst(NULL)
    {
    }

#ifdef DBG
/*===================================================================
CComponentCollection::AssertValid

Test to make sure that this is currently correctly formed
and assert if it is not.

Returns:
===================================================================*/
void CComponentCollection::AssertValid() const
    {
    Assert(m_csScope != csUnknown);
    m_htTaggedObjects.AssertValid();
    m_htTaggedObjects.AssertValid();
    m_htidIUnknownPtrs.AssertValid();
    }
#endif

/*===================================================================
CComponentCollection::~CComponentCollection

CComponentCollection destructor
Deletes all the objects

Parameters:

Returns:
===================================================================*/
CComponentCollection::~CComponentCollection()
    {
    UnInit();
    }

/*===================================================================
CComponentCollection::Init

Sets collection scope
Initializes hash tables

Parameters:

Returns:
    HRESULT
===================================================================*/
HRESULT CComponentCollection::Init
(
CompScope scScope
)
    {
    HRESULT hr = S_OK;

    m_csScope = scScope;

    hr = m_htTaggedObjects.Init(HT_TAGGED_OBJECTS_BUCKETS_MAX);
    if (FAILED(hr))
        return hr;

    hr = m_htProperties.Init(HT_PROPERTIES_BUCKETS_MAX);
    if (FAILED(hr))
        return hr;

    hr = m_htidIUnknownPtrs.Init(HT_IUNKNOWN_PTRS_BUCKETS_MAX);
    if (FAILED(hr))
        return hr;

    return S_OK;
    }

/*===================================================================
CComponentCollection::UnInit

Deletes all the objects

Parameters:

Returns:
    S_OK
===================================================================*/
HRESULT CComponentCollection::UnInit()
    {
    // clear out pointer arrays
    m_rgpvTaggedObjects.Clear();
    m_rgpvProperties.Clear();
    m_fUseTaggedArray = FALSE;
    m_fUsePropArray = FALSE;

    // clear out name hash tables
    m_htTaggedObjects.UnInit();
    m_htProperties.UnInit();

    // clear out pointers hash table
    m_htidIUnknownPtrs.UnInit();

    // delete all member component objects
    if (m_pCompFirst)
        {
        CComponentObject *pObj = m_pCompFirst;
        while (pObj)
            {
            CComponentObject *pNext = pObj->m_pCompNext;
            delete pObj;
            pObj = pNext;
            }
        m_pCompFirst = NULL;
        }

    // reset the counters
    m_cAllTagged = 0;
    m_cInstTagged = 0;
    m_cProperties = 0;
    m_cUnnamed = 0;
    m_fHasComProperties = FALSE;

    return S_OK;
    }

/*===================================================================
CComponentCollection::AddComponentToNameHash

Adds an object to the proper hash table

Parameters:
    CComponentObject *pObj      object to add
    LPWSTR            pwszName  object's name (hash)
    DWORD             cbName    name length in bytes

Returns:
    HRESULT
===================================================================*/
HRESULT CComponentCollection::AddComponentToNameHash
(
CComponentObject *pObj,
LPWSTR            pwszName,
DWORD             cbName
)
    {
    Assert(pwszName);
    Assert(cbName == (wcslen(pwszName) * sizeof(WCHAR)));

    // determine which hash table
    CHashTableStr *pHashTable;

    if (pObj->m_ctType == ctTagged)
        pHashTable = &m_htTaggedObjects;
    else if (pObj->m_ctType == ctProperty)
        pHashTable = &m_htProperties;
    else
        return S_OK; // nowhere to add, OK

    // Initialize object's CLinkElem
    HRESULT hr = pObj->Init(pwszName, cbName);
    if (FAILED(hr))
        return hr;

    // Add to hash table
    CLinkElem *pAddedElem = pHashTable->AddElem(pObj);
    if (!pAddedElem)
        return E_FAIL;  // couldn't add

    if (pObj != static_cast<CComponentObject *>(pAddedElem))
        return E_FAIL;  // another object with the same name
                        // already there

    return S_OK;
    }

/*===================================================================
CComponentCollection::AddComponentToPtrHash

Adds an object to the IUnkown * hash table

Parameters:
    CComponentObject *pObj      object to add

Returns:
    HRESULT
===================================================================*/
HRESULT CComponentCollection::AddComponentToPtrHash
(
CComponentObject *pObj
)
    {
    // If we don't track the threading model, we don't care
    // to add objects to cache by IUnknown * - no need to look them up
    if (!Glob(fTrackThreadingModel))
        return S_OK;

    void *ptr = pObj->m_pUnknown;
    if (!ptr)
        return S_OK; // uninstatiated

    if (FAILED(m_htidIUnknownPtrs.AddObject((DWORD_PTR)ptr, pObj)))
        return E_FAIL;

    pObj->m_fInPtrCache = TRUE;
    return S_OK;
    }

/*===================================================================
ComponentCollection::FindComponentObjectByName

Find tagged object by name

Parameters:
    LPWSTR             pwszName   object's name
    DWORD              cbName     name length
    CComponentObject **ppObj      found object

Returns:
    HRESULT
        (S_FALSE if no error - not found)
===================================================================*/
HRESULT CComponentCollection::FindComponentObjectByName
(
LPWSTR pwszName,
DWORD  cbName,
CComponentObject **ppObj
)
    {
    Assert(pwszName);
    Assert(cbName == (wcslen(pwszName) * sizeof(WCHAR)));

    CLinkElem *pElem = m_htTaggedObjects.FindElem(pwszName, cbName);
    if (!pElem)
        {
        *ppObj = NULL;
        return S_FALSE;
        }

    *ppObj = static_cast<CComponentObject *>(pElem);
    return S_OK;
    }

/*===================================================================
ComponentCollection::FindComponentPropertyByName

Find property by name

Parameters:
    LPWSTR             pwszName   object's name
    DWORD              cbName     name length
    CComponentObject **ppObj      found object

Returns:
    HRESULT
        (S_FALSE if no error - not found)
===================================================================*/
HRESULT CComponentCollection::FindComponentPropertyByName
(
LPWSTR pwszName,
DWORD  cbName,
CComponentObject **ppObj
)
    {
    Assert(pwszName);
    Assert(cbName == (wcslen(pwszName) * sizeof(WCHAR)));

    CLinkElem *pElem = m_htProperties.FindElem(pwszName, cbName);
    if (!pElem)
        {
        *ppObj = NULL;
        return S_FALSE;
        }

    *ppObj = static_cast<CComponentObject *>(pElem);
    return S_OK;
    }

/*===================================================================
ComponentCollection::FindComponentByIUnknownPtr

Find property by IUnknown *

Parameters:
    IUnknown          *pUnk    find by this pointer
    CComponentObject **ppObj   found object

Returns:
    HRESULT
        (S_FALSE if no error - not found)
===================================================================*/

HRESULT CComponentCollection::FindComponentByIUnknownPtr
(
IUnknown *pUnk,
CComponentObject **ppObj
)
    {
    void *pv;
    if (m_htidIUnknownPtrs.FindObject((DWORD_PTR)pUnk, &pv) != S_OK)
        {
        *ppObj = NULL;
        return S_FALSE;
        }

    *ppObj = reinterpret_cast<CComponentObject *>(pv);
    return S_OK;
    }

/*===================================================================
CComponentCollection::AddTagged

Adds a tagged object to the collection. Does not instanciate it yet.

Parameters:
    LPWSTR     pwszName     Object name
    CLSID     &ClsId        Class ID
    CompModel  cmModel      Object model

Returns:
    HRESULT
===================================================================*/
HRESULT CComponentCollection::AddTagged
(
LPWSTR    pwszName,
const CLSID &ClsId,
CompModel cmModel
)
    {
    HRESULT hr = S_OK;

    DWORD cbName = CbWStr(pwszName);    // do strlen once

    if (m_htTaggedObjects.FindElem(pwszName, cbName))
        return E_FAIL;  // duplicate name

    CComponentObject *pObj = new CComponentObject
        (
        m_csScope,
        ctTagged,
        cmModel
        );

    if (pObj == NULL)
        return E_OUTOFMEMORY;

    pObj->m_ClsId = ClsId;

    hr = AddComponentToList(pObj);
    if (FAILED(hr))
        return hr;

    hr = AddComponentToNameHash(pObj, pwszName, cbName);
    if (FAILED(hr))
        return hr;

    if (m_fUseTaggedArray)
        m_rgpvTaggedObjects.Append(pObj);

    m_cAllTagged++;
    return S_OK;
    }

/*===================================================================
CComponentCollection::AddProperty

Adds a property object to the collection.
If property with the same name exists, it changes the value

Parameters:
    LPWSTR             pwszName   Object name
    VARIANT            pVariant   Property value
    CComponentObject **ppObj      [out] Property object could
                                        be NULL if not requested

Returns:
    HRESULT
===================================================================*/
HRESULT CComponentCollection::AddProperty
(
LPWSTR   pwszName,
VARIANT *pVariant,
CComponentObject **ppObj
)
    {
    if (ppObj)
        *ppObj = NULL;

    HRESULT hr = S_OK;

    CComponentObject *pObj = NULL;

    DWORD cbName = CbWStr(pwszName);    // do strlen once

    // Find the existing object first
    CLinkElem *pElem = m_htProperties.FindElem(pwszName, cbName);

    if (pElem)
        {
        // Object already exists - use it
        pObj = static_cast<CComponentObject *>(pElem);
        Assert(pObj->m_ctType == ctProperty);

        // Clear out the object from any data
        hr = pObj->Clear();
        if (FAILED(hr))
            return hr;

        // Reinitialize object
        pObj->m_csScope = m_csScope;
        pObj->m_ctType  = ctProperty;
        pObj->m_cmModel = cmUnknown;
        }
    else
        {
        // Create new object
        pObj = new CComponentObject(m_csScope, ctProperty, cmUnknown);
        if (pObj == NULL)
            return E_OUTOFMEMORY;

        // Add the object to the list
        hr = AddComponentToList(pObj);
        if (FAILED(hr))
            return hr;

        // Add the object to the hash
        hr = AddComponentToNameHash(pObj, pwszName, cbName);
        if (FAILED(hr))
            return hr;

        // Add to properties array if needed
        if (m_fUsePropArray)
            m_rgpvProperties.Append(pObj);

        m_cProperties++;
        }

    // Assign value
    hr = pObj->SetPropertyValue(pVariant);

    if (SUCCEEDED(hr))
        {
        // check if simple variant
        if (!FIsSimpleVariant(&pObj->m_Variant))
            m_fHasComProperties = TRUE;
        }

    // Return object ptr if requested
    if (SUCCEEDED(hr))
        {
        if (ppObj)
            *ppObj = pObj;
        }

    return hr;
    }

/*===================================================================
CComponentCollection::AddUnnamed

Add object to be instantiated using Server.CreateObject

Parameters:
    CLSID             &ClsId    Class ID
    CompModel          cmModel  Object model
    CComponentObject **ppObj    Object Added

Returns:
    HRESULT
===================================================================*/
HRESULT CComponentCollection::AddUnnamed
(
const CLSID &ClsId,
CompModel cmModel,
CComponentObject **ppObj
)
    {
    HRESULT hr = S_OK;

    if (cmModel == cmUnknown)
        {
        hr = CompModelFromCLSID(ClsId, &cmModel);
        if (FAILED(hr))
            return hr;
        }

    CComponentObject *pObj = new CComponentObject
        (
        m_csScope,
        ctUnnamed,
        cmModel
        );

    if (pObj == NULL)
        return E_OUTOFMEMORY;

    pObj->m_ClsId = ClsId;

    hr = AddComponentToList(pObj);
    if (FAILED(hr))
        return hr;

    *ppObj = pObj;
    m_cUnnamed++;
    return S_OK;
    }

/*===================================================================
CComponentCollection::GetTagged

Finds tagged object by name

Parameters:
    LPWSTR   pwszName           Object name
    CComponentObject **ppObj    [out] Object Found

Returns:
    HRESULT
===================================================================*/
HRESULT CComponentCollection::GetTagged
(
LPWSTR pwszName,
CComponentObject **ppObj
)
    {
    Assert(ppObj);
    *ppObj = NULL;

    CComponentObject *pObj = NULL;
    HRESULT hr = FindComponentObjectByName
        (
        pwszName,
        CbWStr(pwszName),
        &pObj
        );

    if (FAILED(hr))
        return hr;

    if (pObj && pObj->m_ctType != ctTagged)
        pObj = NULL;

    if (pObj)
        *ppObj = pObj;
    else
        hr = TYPE_E_ELEMENTNOTFOUND;

    return hr;
    }

/*===================================================================
CComponentCollection::GetProperty

Finds property object by name

Parameters:
    LPWSTR   pwszName           Property name
    CComponentObject **ppObj    [out] Object Found

Returns:
    HRESULT
===================================================================*/
HRESULT CComponentCollection::GetProperty
(
LPWSTR pwszName,
CComponentObject **ppObj
)
    {
    Assert(ppObj);
    *ppObj = NULL;

    CComponentObject *pObj = NULL;
    HRESULT hr = FindComponentPropertyByName
        (
        pwszName,
        CbWStr(pwszName),
        &pObj
        );

    if (FAILED(hr))
        return hr;

    if (pObj)
        *ppObj = pObj;
    else
        hr = TYPE_E_ELEMENTNOTFOUND;

    return hr;
    }

/*===================================================================
CComponentCollection::GetNameByIndex

Find name of a tagged objects or property by index

Parameters:
    CompType ctType       tagged or property
    int      index        index (1-based)
    LPWSTR  *ppwszName    [out] name (NOT allocated)

Returns:
    HRESULT
===================================================================*/
HRESULT CComponentCollection::GetNameByIndex
(
CompType ctType,
int index,
LPWSTR *ppwszName
)
    {
    CPtrArray *pPtrArray;

    if (ctType == ctTagged)
        {
        if (!m_fUseTaggedArray)
            StartUsingTaggedObjectsArray();
        pPtrArray = &m_rgpvTaggedObjects;
        }
    else if (ctType == ctProperty)
        {
        if (!m_fUsePropArray)
            StartUsingPropertiesArray();
        pPtrArray = &m_rgpvProperties;
        }
    else
        {
        Assert(FALSE);
        *ppwszName = NULL;
        return E_FAIL;
        }

    if (index >= 1 && index <= pPtrArray->Count())
        {
        CComponentObject *pObj = (CComponentObject *)pPtrArray->Get(index-1);
        if (pObj)
            {
            Assert(pObj->GetType() == ctType);
            *ppwszName = pObj->GetName();
            if (*ppwszName)
                return S_OK;
            }
        }

    *ppwszName = NULL;
    return E_FAIL;
    }

/*===================================================================
CComponentCollection::RemoveComponent

Remove a known component.
Slow method for a non-recent objects.

Parameters:
    CComponentObject *pObj      -- object to remove

Returns:
    HRESULT
===================================================================*/
HRESULT CComponentCollection::RemoveComponent
(
CComponentObject *pObj
)
    {
    Assert(pObj);

    // Remove from by-name hash tables and arrays
    if (pObj->m_ctType == ctTagged)
        {
        // tagged cannot be removed
        Assert(FALSE);
        return E_FAIL;
        }
    else if (pObj->m_ctType == ctProperty)
        {
        // hash table
        if (m_htProperties.DeleteElem(pObj->GetName(), CbWStr(pObj->GetName())))
            {
            m_cProperties--;
            }

        // array
        if (m_fUsePropArray)
            {
            m_rgpvProperties.Remove(pObj);
            }
        }
    else
        {
        Assert(pObj->m_ctType == ctUnnamed);
        m_cUnnamed--;
        }

    // Remove from the 'by pointer hash table'
    if (pObj->m_fInPtrCache)
        {
        void *ptr = pObj->m_pUnknown;
        if (ptr)
            m_htidIUnknownPtrs.RemoveObject((DWORD_PTR)ptr);
        pObj->m_fInPtrCache = FALSE;
        }

    // Remove from the list
    RemoveComponentFromList(pObj);

    // Remove
    delete pObj;

    return S_OK;
    }

/*===================================================================
CComponentCollection::RemovePropery

Remove a property by name.

Parameters:
    LPWSTR pwszName -- property name

Returns:
    HRESULT
===================================================================*/
HRESULT CComponentCollection::RemoveProperty
(
LPWSTR pwszName
)
    {
    CComponentObject *pObj = NULL;
    HRESULT hr = FindComponentPropertyByName
        (
        pwszName,
        CbWStr(pwszName),
        &pObj
        );

    if (FAILED(hr))
        return hr;

    if (pObj)
        hr = RemoveComponent(pObj);

    return hr;
    }

/*===================================================================
CComponentCollection::RemoveAllProperties

Remove all properties.  Faster than iterating.

Parameters:

Returns:
    HRESULT
===================================================================*/
HRESULT CComponentCollection::RemoveAllProperties()
    {
    // Clear out the properties array
    if (m_fUsePropArray)
        {
        m_rgpvProperties.Clear();
        m_fUsePropArray = FALSE;
        }

    // Walk the object list to remove properties
    CComponentObject *pObj = m_pCompFirst;
    while (pObj)
    {
        CComponentObject *pNextObj = pObj->m_pCompNext;

        if (pObj->m_ctType == ctProperty)
            {
            // remove from the hash table
            m_htProperties.DeleteElem(pObj->GetName(), CbWStr(pObj->GetName()));
            // properties are not in the 'by pointer hash table'
            Assert(!pObj->m_fInPtrCache);
            // remove from the list
            RemoveComponentFromList(pObj);
            // remove
            delete pObj;
            }

        pObj = pNextObj;
    }

    m_cProperties = 0;
    m_fHasComProperties = FALSE;

    return S_OK;
    }

/*===================================================================
CComponentCollection::StartUsingTaggedObjectsArray

Fill in the tagged objects array for access by index for the
first time

Parameters:

Returns:
    HRESULT
===================================================================*/
HRESULT CComponentCollection::StartUsingTaggedObjectsArray()
    {
    if (m_fUseTaggedArray)
        return S_OK;

    m_rgpvTaggedObjects.Clear();

    CComponentObject *pObj = m_pCompFirst;
    while (pObj)
        {
        if (pObj->GetType() == ctTagged)
            m_rgpvTaggedObjects.Append(pObj);
        pObj = pObj->m_pCompNext;
        }

    m_fUseTaggedArray = TRUE;
    return S_OK;
    }

/*===================================================================
CComponentCollection::StartUsingPropertiesArray

Fill in the properties array for access by index for the
first time

Parameters:

Returns:
    HRESULT
===================================================================*/
HRESULT CComponentCollection::StartUsingPropertiesArray()
    {
    if (m_fUsePropArray)
        return S_OK;

    m_rgpvProperties.Clear();

    CComponentObject *pObj = m_pCompFirst;
    while (pObj)
        {
        if (pObj->GetType() == ctProperty)
            m_rgpvProperties.Prepend(pObj); // backwards
        pObj = pObj->m_pCompNext;
        }

    m_fUsePropArray = TRUE;
    return S_OK;
    }


/*===================================================================
  C  P a g e  C o m p o n e n t  M a n a g e r
===================================================================*/

/*===================================================================
CPageComponentManager::CPageComponentManager

CPageComponentManager constructor

Parameters:

Returns:
===================================================================*/
CPageComponentManager::CPageComponentManager()
    : m_pHitObj(NULL)
    {
    }

#ifdef DBG
/*===================================================================
CPageComponentManager::AssertValid()

Test to make sure that this is currently correctly formed
and assert if it is not.

Returns:
===================================================================*/
void CPageComponentManager::AssertValid() const
    {
    Assert(m_pHitObj);
    m_pHitObj->AssertValid();
    m_htidPageObjects.AssertValid();
    }
#endif

/*===================================================================
CPageComponentManager::~CPageComponentManager

CPageComponentManager destructor
Deletes all page objects

Parameters:

Returns:
===================================================================*/
CPageComponentManager::~CPageComponentManager()
    {
    // delete all page objects
    m_htidPageObjects.IterateObjects(DeletePageObjectCB);
    }

/*===================================================================
CPageComponentManager::DeletePageObjectCB

Static callback from hash table iterator to delete a CPageObject

Parameters:
    pvObj       CPageObject* to delete passed as void*

Returns:
    iccContinue
===================================================================*/
IteratorCallbackCode CPageComponentManager::DeletePageObjectCB
(
void *pvObj,
void *,
void *
)
    {
    Assert(pvObj);
    CPageObject *pObj = reinterpret_cast<CPageObject *>(pvObj);
    delete pObj;
    return iccContinue;
    }

/*===================================================================
CPageComponentManager::Init

Sets collection scope (to page)
Initializes hash tables

Parameters:
    CHitObj *pHitObj        this page

Returns:
    HRESULT
===================================================================*/
HRESULT CPageComponentManager::Init
(
CHitObj *pHitObj
)
    {
    HRESULT hr;

    // Init hash table of Page Objects
    hr = m_htidPageObjects.Init(HT_PAGE_OBJECTS_BUCKETS_MAX);
    if (FAILED(hr))
        return hr;

    // remember pHitObj
    m_pHitObj = pHitObj;

    return S_OK;
    }

/*===================================================================
CPageComponentManager::OnStartPage

Adds new page object. Ignores objects withount page info
(OnEndPage is done for all objects at the end of page)

Parameters:
    CComponentObject  *pCompObj     object to do OnStartPage
    CScriptingContext *pContext     arg to OnStart
    COnPageInfo *pOnPageInfo        pre-queried ids (optional)
    BOOL        *pfStarted          returned flag

Returns:
    HRESULT
===================================================================*/
HRESULT CPageComponentManager::OnStartPage
(
CComponentObject  *pCompObj,
CScriptingContext *pContext,
const COnPageInfo *pOnPageInfo,
BOOL              *pfStarted
)
    {
    IDispatch  *pDisp = pCompObj->m_pDisp;
    HRESULT hr = S_OK;

    if(pDisp == NULL)
        {
        Assert(pCompObj->m_dwGIPCookie != NULL_GIP_COOKIE);
        // try to restore from cookie
        hr = g_GIPAPI.Get
            (
            pCompObj->m_dwGIPCookie,
            IID_IDispatch,
            (void **)&pDisp
            );

        if (FAILED(hr))
            return hr;
        }
	else
		pDisp->AddRef();

    Assert(pDisp);

    Assert(pfStarted);
    *pfStarted = FALSE;

    // check if onpageinfo passed and the methods aren't defined
    if (pOnPageInfo && !pOnPageInfo->FHasAnyMethod())
		{
		pDisp->Release();
        return S_OK;
		}

    // check if already in the PageObject Hash
    if (m_htidPageObjects.FindObject((DWORD_PTR)pDisp) == S_OK)
        {
		pDisp->Release();
        return S_OK;
        }

    COnPageInfo OnPageInfo;

    if (pOnPageInfo)
        {
        OnPageInfo = *pOnPageInfo;
        }
    else
        {
        // dynamically create OnPageInfo if not passed
        if (Glob(fExceptionCatchEnable))
            {
            TRY
                hr = QueryOnPageInfo(pDisp, &OnPageInfo);
            CATCH(nExcept)
                HandleErrorMissingFilename(IDE_SCRIPT_OBJ_ONPAGE_QI_FAILED,
                                           m_pHitObj,
                                           TRUE,
                                           pCompObj->GetName(),
                                           nExcept);
                hr = nExcept;
            END_TRY
            }
        else
            {
            hr = QueryOnPageInfo(pDisp, &OnPageInfo);
            }

        if (FAILED(hr))
            {
			pDisp->Release();
            return hr;
            }

        // check if any of the methods is defined
        if (!OnPageInfo.FHasAnyMethod())
            {
			pDisp->Release();
            return S_OK;
            }
        }

    // create object
    CPageObject *pPageObj = new CPageObject;
    if (!pPageObj)
        {
		pDisp->Release();
        return E_OUTOFMEMORY;
        }

    // init LinkElem
    hr = pPageObj->Init(pDisp, OnPageInfo);   // this eats our previous AddRef()
    if (SUCCEEDED(hr))
        {
        // add to hash table
        hr = m_htidPageObjects.AddObject((DWORD_PTR)pDisp, pPageObj);
        }

    // cleanup if failed
    if (FAILED(hr) && pPageObj)
        {
        pDisp->Release();   // Init failed, so remove our AddRef()
        delete pPageObj;
        return hr;
        }

    *pfStarted = TRUE;

    return pPageObj->InvokeMethod
        (
        ONPAGEINFO_ONSTARTPAGE,
        pContext,
        m_pHitObj
        );
    }

/*===================================================================
PageComponentManager::OnEndPageAllObjects

Does OnEndPage() for all objects that need it
(OnStartPage() is on demand basis)

Parameters:

Returns:
    HRESULT
===================================================================*/
HRESULT CPageComponentManager::OnEndPageAllObjects()
    {
    HRESULT hrGlobal = S_OK;

    m_htidPageObjects.IterateObjects
        (
        OnEndPageObjectCB,
        m_pHitObj,
        &hrGlobal
        );

    return hrGlobal;
    }

/*===================================================================
CPageComponentManager::OnEndPageObjectCB

Static callback from hash table iterator to execute OnEndPage
for a CPageObject

Parameters:
    pvObj       CPageObject* to delete passed as void*

Returns:
    iccContinue
===================================================================*/
IteratorCallbackCode CPageComponentManager::OnEndPageObjectCB
(
void *pvObj,
void *pvHitObj,
void *pvhr
)
    {
    Assert(pvObj);
    Assert(pvHitObj);
    Assert(pvhr);

    CPageObject *pObj = reinterpret_cast<CPageObject *>(pvObj);

    HRESULT hr = pObj->InvokeMethod
        (
        ONPAGEINFO_ONENDPAGE,
        NULL,
        reinterpret_cast<CHitObj *>(pvHitObj)
        );

    if (FAILED(hr))
        *(reinterpret_cast<HRESULT *>(pvhr)) = hr;

    return iccContinue;
    }

/*===================================================================
CPageComponentManager::GetPageCollection

Queries HitObj for the Page's Component Collection

Parameters:
    CComponentCollection **ppCollection (out)

Returns:
    HRESULT
===================================================================*/
HRESULT CPageComponentManager::GetPageCollection
(
CComponentCollection **ppCollection
)
    {
    Assert(m_pHitObj);

    *ppCollection = NULL;

    return m_pHitObj->GetPageComponentCollection(ppCollection);
    }

/*===================================================================
CPageComponentManager::GetSessionCollection

Queries HitObj for the Session's Component Collection

Parameters:
    CComponentCollection **ppCollection (out)

Returns:
    HRESULT
===================================================================*/
HRESULT CPageComponentManager::GetSessionCollection
(
CComponentCollection **ppCollection
)
    {
    Assert(m_pHitObj);

    *ppCollection = NULL;

    return m_pHitObj->GetSessionComponentCollection(ppCollection);
    }

/*===================================================================
CPageComponentManager::GetApplnCollection

Queries HitObj for the Application's Component Collection

Parameters:
    CComponentCollection **ppCollection (out)

Returns:
    HRESULT
===================================================================*/
HRESULT CPageComponentManager::GetApplnCollection
(
CComponentCollection **ppCollection
)
    {
    Assert(m_pHitObj);

    *ppCollection = NULL;

    return m_pHitObj->GetApplnComponentCollection(ppCollection);
    }

/*===================================================================
CPageComponentManager::GetCollectionByScope

Gets the collection corresponding to the scope

Parameters:
    CompScope              csScope      (in) desired scope
    CComponentCollection **ppCollection (out)

Returns:
    HRESULT
===================================================================*/
HRESULT CPageComponentManager::GetCollectionByScope
(
CompScope scScope,
CComponentCollection **ppCollection
)
    {
    HRESULT hr = S_OK;

    switch (scScope)
        {
        case csPage:
            hr = GetPageCollection(ppCollection);
            break;
        case csSession:
            hr = GetSessionCollection(ppCollection);
            break;
        case csAppln:
            hr = GetApplnCollection(ppCollection);
            break;
        default:
            hr = E_UNEXPECTED;
            break;
        }

    if (FAILED(hr))
        *ppCollection = NULL;
    else if (*ppCollection == NULL)
        hr = E_POINTER; // to make sure we fail if no collection
    return hr;
    }

/*===================================================================
CPageComponentManager::FindScopedComponentByName

Finds object by name. Searches multiple collections if
the scope is unknown.
Internal private method used in GetScoped...()

Parameters:
    CompScope             csScope       Scope (could be csUnknown)
    LPWSTR                pwszName      Object name
    DWORD                 cbName        name length
    BOOL                  fProperty     TRUE = property,
                                        FALSE = tagged

    CComponentObject     **ppObj        (out) Object found
    CComponentCollection **ppCollection (out) Collection where found
                                              (optional)

Returns:
    HRESULT
        (S_FALSE if no error - not found)
===================================================================*/
HRESULT CPageComponentManager::FindScopedComponentByName
(
CompScope csScope,
LPWSTR pwszName,
DWORD  cbName,
BOOL   fProperty,
CComponentObject **ppObj,
CComponentCollection **ppCollection
)
    {
    int cMaxTry = (csScope == csUnknown) ? 3 : 1;
    int cTry = 0;
    *ppObj = NULL;

    while (*ppObj == NULL && cTry < cMaxTry)
        {
        HRESULT hr = S_OK;
        CComponentCollection *pCollection = NULL;

        switch (++cTry)
            {
            case 1: // page (or explicit scope) first
                if (csScope == csUnknown)
                    hr = GetPageCollection(&pCollection);
                else  // explicit scope
                    hr = GetCollectionByScope(csScope, &pCollection);
                break;
            case 2: // session
                hr = GetSessionCollection(&pCollection);
                break;
            case 3: // application
                hr = GetApplnCollection(&pCollection);
                break;
            }
        if (FAILED(hr) || !pCollection)
            continue;   // couldn't get the collection

        Assert(cbName == (wcslen(pwszName) * sizeof(WCHAR)));

        // find the object
        if (fProperty)
            {
            hr = pCollection->FindComponentPropertyByName
                (
                pwszName,
                cbName,
                ppObj
                );
            }
        else
            {
            hr = pCollection->FindComponentObjectByName
                (
                pwszName,
                cbName,
                ppObj
                );
            }

        if (hr != S_OK)
            *ppObj = NULL;

        // remember where found
        if (*ppObj && ppCollection)
            *ppCollection = pCollection;
        }

    return (*ppObj ? S_OK : S_FALSE);
    }

/*===================================================================
CPageComponentManager::AddScopedTagged

Adds a tagged object to the collection. Does not instantiate it yet.

Parameters:
    CompScope csScope      Object scope (which collection)
    LPWSTR    pwszName     Object name
    CLSID    &ClsId        Class ID
    CompModel cmModel      Object model

Returns:
    HRESULT
===================================================================*/
HRESULT CPageComponentManager::AddScopedTagged
(
CompScope csScope,
LPWSTR    pwszName,
const CLSID &ClsId,
CompModel cmModel
)
    {
    CComponentCollection *pCollection;
    HRESULT hr = GetCollectionByScope(csScope, &pCollection);
    if (FAILED(hr))
        return hr;
    return pCollection->AddTagged(pwszName, ClsId, cmModel);
    }

/*===================================================================
CPageComponentManager::AddScopedProperty

Adds a property object to the collection.
If property with the same name exists, it changes the value

Parameters:
    CompScope          csScope    Object scope (which collection)
    LPWSTR             pwszName   Object name
    VARIANT            pVariant   Property value
    CComponentObject **ppObj      [out] Property object could
                                        be NULL if not requested

Returns:
    HRESULT
===================================================================*/
HRESULT CPageComponentManager::AddScopedProperty
(
CompScope csScope,
LPWSTR pwszName,
VARIANT *pVariant,
CComponentObject **ppObj
)
    {
    CComponentCollection *pCollection;
    HRESULT hr = GetCollectionByScope(csScope, &pCollection);
    if (FAILED(hr))
        {
        if (ppObj)
            *ppObj = NULL;
        return hr;
        }
    return pCollection->AddProperty(pwszName, pVariant, ppObj);
    }

/*===================================================================
CPageComponentManager::AddScopedUnnamedInstantiated

Server.CreateObject
Also does OnStartPage (adds created pDisp as CPageObject)

Parameters:
    csScope     Object scope (which collection)
    ClsId       Class ID
    cmModel     Object model
    pOnPageInfo DispIds for OnStartPage/OnEndPage (can be NULL)
    ppObj       [out] Object Added

Returns:
    HRESULT
===================================================================*/
HRESULT CPageComponentManager::AddScopedUnnamedInstantiated
(
CompScope csScope,
const CLSID &ClsId,
CompModel cmModel,
COnPageInfo *pOnPageInfo,
CComponentObject **ppObj
)
    {
    CComponentCollection *pCollection;
    HRESULT hr = GetCollectionByScope(csScope, &pCollection);
    if (FAILED(hr))
        return hr;
    hr = pCollection->AddUnnamed(ClsId, cmModel, ppObj);
    if (FAILED(hr))
        return hr;

    CComponentObject *pObj = *ppObj;

    // remember passed OnPageInfo
    if (pOnPageInfo)
        {
        pObj->m_OnPageInfo = *pOnPageInfo;
        pObj->m_fOnPageInfoCached = TRUE;
        }

    // create it
    hr = pObj->Instantiate(m_pHitObj);
    if (FAILED(hr))
        return hr;

    // add to pointer cash
    pCollection->AddComponentToPtrHash(pObj);

    // add as page object when needed
    if (csScope == csPage
        && (pObj->m_pDisp || (pObj->m_dwGIPCookie != NULL_GIP_COOKIE))
        && m_pHitObj && m_pHitObj->FIsBrowserRequest())
        {
        BOOL fStarted = FALSE;

        hr = OnStartPage
            (
            pObj,
            m_pHitObj->PScriptingContextGet(),
            pObj->GetCachedOnPageInfo(),
            &fStarted
            );

        if (fStarted)
            pObj->m_fOnPageStarted = TRUE;
        }

    return hr;
    }

/*===================================================================
CPageComponentManager::GetScopedObjectInstantiated

Finds component object (tagged) by name.
Scope could be csUnknown.
Instantiates tagged objects.
Also does OnStartPage (adds created pDisp as CPageObject)

Parameters:
    CompScope          csScope        Scope (could be csUnknown)
    LPWSTR             pwszName       Object name
    DWORD              cbName         Object name length (in bytes)
    CComponentObject **ppObj          Object found
    BOOL              *pfNewInstance  [out] TRUE if just instantiated

Returns:
    HRESULT
===================================================================*/
HRESULT CPageComponentManager::GetScopedObjectInstantiated
(
CompScope csScope,
LPWSTR pwszName,
DWORD  cbName,
CComponentObject **ppObj,
BOOL *pfNewInstance
)
    {
    HRESULT hr;

    Assert(pfNewInstance);
    *pfNewInstance = FALSE;

    CComponentCollection *pCollection;
    hr = FindScopedComponentByName
        (
        csScope,
        pwszName,
        cbName,
        FALSE,
        ppObj,
        &pCollection
        );
    if (FAILED(hr))
        return hr;

    CComponentObject *pObj = *ppObj;
    if (!pObj)   // not failed, but not found either
        return TYPE_E_ELEMENTNOTFOUND;

    if (pObj->m_ctType != ctTagged)
        return S_OK;

    // For tagged only - instantiate and do OnStartPage()

    // For application level objects instantiation must be
    // done within critical section

    BOOL fApplnLocked = FALSE;

    Assert(m_pHitObj);

    if (!pObj->m_fInstantiatedTagged &&          // uninstantiated
        pObj->m_csScope == csAppln   &&          // application scope
        m_pHitObj->PAppln()->FFirstRequestRun()) // after GLOBAL.ASA
        {
        // Lock
        m_pHitObj->PAppln()->Lock();

        // check if the object is still uninstantiated
        if (!pObj->m_fInstantiatedTagged)
            {
            // yes, still uninstantiated - keep the lock
            fApplnLocked = TRUE;
            }
        else
            {
            // object instantiated while we waited - don't keep lock
            m_pHitObj->PAppln()->UnLock();
            }
        }

    // Instantiate tagged if needed
    if (!pObj->m_fInstantiatedTagged)
        {
        if (pObj->m_csScope == csAppln)
            {
            // For applicatin scoped objects, instantiate from MTA
            hr = CallMTACallback
                (
                CPageComponentManager::InstantiateObjectFromMTA,
                pObj,
                m_pHitObj
                );
            }
        else
            {
            hr = pObj->Instantiate(m_pHitObj);
            }

        if (SUCCEEDED(hr))
            {
            // keep count
            pCollection->m_cInstTagged++;
            // add to pointer cash
            pCollection->AddComponentToPtrHash(pObj);
            // return flag
            *pfNewInstance = TRUE;
            }

        // Flag as instantiated even if failed
        pObj->m_fInstantiatedTagged = TRUE;
        }

    // Remove the lock kept while instantiating appln level object
    if (fApplnLocked)
        m_pHitObj->PAppln()->UnLock();

    // Return if [instantiation] failed
    if (FAILED(hr))
        {
        *ppObj = NULL;
        return hr;
        }

    // Add as page object when needed
    if (pObj->m_csScope != csAppln
        && (pObj->m_pDisp || (pObj->m_dwGIPCookie != NULL_GIP_COOKIE))
        && m_pHitObj && m_pHitObj->FIsBrowserRequest())
        {
        BOOL fStarted;
        OnStartPage     // don't care if failed
            (
            pObj,
            m_pHitObj->PScriptingContextGet(),
            pObj->GetCachedOnPageInfo(),
            &fStarted
            );
        }

    return hr;
    }

/*===================================================================
CPageComponentManager::InstantiateObjectFromMTA

Static callback called by CallMTACallback() to
instantiate aplication scoped objects

Parameters:
    void *pvObj       ComponentObject
    void *pvHitObj    HitObj

Returns:
    HRESULT
===================================================================*/
HRESULT __stdcall CPageComponentManager::InstantiateObjectFromMTA
(
void *pvObj,
void *pvHitObj
)
    {
    Assert(pvHitObj);
    Assert(pvObj);

    CHitObj *pHitObj = (CHitObj *)pvHitObj;
    CComponentObject *pObj = (CComponentObject *)pvObj;

    return pObj->Instantiate(pHitObj);
    }

/*===================================================================
CPageComponentManager::GetScopedProperty

Find property component by name.
Also does OnStartPage (adds created pDisp as CPageObject)

Parameters:
    CompScope          csScope      Scope (could not be csUnknown)
    LPWSTR             pwszName     Object name
    CComponentObject **ppObj        Object found

Returns:
    HRESULT
===================================================================*/
HRESULT CPageComponentManager::GetScopedProperty
(
CompScope csScope,
LPWSTR pwszName,
CComponentObject **ppObj
)
    {
    HRESULT hr;

    hr = FindScopedComponentByName
        (
        csScope,
        pwszName,
        CbWStr(pwszName),
        TRUE,
        ppObj
        );
    if (FAILED(hr))
        return hr;

    CComponentObject *pObj = *ppObj;
    if (!pObj)   // not failed, but not found either
        return TYPE_E_ELEMENTNOTFOUND;

    // Add as page object if IDispatch * is there
    // as VT_DISPATCH property
    if (pObj->m_csScope != csAppln
        && (pObj->m_pDisp || (pObj->m_dwGIPCookie != NULL_GIP_COOKIE))
        && m_pHitObj && m_pHitObj->FIsBrowserRequest())
        {
        BOOL fStarted;
        hr = OnStartPage
            (
            pObj,
            m_pHitObj->PScriptingContextGet(),
            pObj->GetCachedOnPageInfo(),
            &fStarted
            );
        }

    return hr;
    }

/*===================================================================
CPageComponentManager::FindAnyScopeComponentByIUnknown

Find component by its IUnknown *.

Parameters:
    IUnknown          *pUnk    find by this pointer
    CComponentObject **ppObj   found object

Returns:
    HRESULT
        (S_FALSE if no error - not found)
===================================================================*/
HRESULT CPageComponentManager::FindAnyScopeComponentByIUnknown
(
IUnknown *pUnk,
CComponentObject **ppObj
)
    {
    int cTry = 0;
    *ppObj = NULL;

    while (*ppObj == NULL && cTry < 3)
        {
        HRESULT hr = S_OK;
        CComponentCollection *pCollection = NULL;

        switch (++cTry)
            {
            case 1: // page first
                hr = GetPageCollection(&pCollection);
                break;
            case 2: // session
                hr = GetSessionCollection(&pCollection);
                break;
            case 3: // application
                hr = GetApplnCollection(&pCollection);
                break;
            }
        if (FAILED(hr) || !pCollection)
            continue;   // couldn't get the collection

        // find the object
        hr = pCollection->FindComponentByIUnknownPtr(pUnk, ppObj);
        if (hr != S_OK)
            *ppObj = NULL;
        }

    return (*ppObj ? S_OK : S_FALSE);
    }

/*===================================================================
CPageComponentManager::FindAnyScopeComponentByIDispatch

Find component by its IDispatch *.
Uses FindAnyScopeComponentByIUnknown.

Parameters:
    IDispatch         *pDisp   find by this pointer
    CComponentObject **ppObj   found object

Returns:
    HRESULT
        (S_FALSE if no error - not found)
===================================================================*/
HRESULT CPageComponentManager::FindAnyScopeComponentByIDispatch
(
IDispatch *pDisp,
CComponentObject **ppObj
)
    {
    IUnknown *pUnk = NULL;
    HRESULT hr = pDisp->QueryInterface(IID_IUnknown, (void **)&pUnk);

    if (SUCCEEDED(hr) && !pUnk)
        hr = E_FAIL;

    if (FAILED(hr))
        {
        *ppObj = NULL;
        return hr;
        }

    return FindAnyScopeComponentByIUnknown(pUnk, ppObj);
    }

/*===================================================================
CPageComponentManager::FindComponentWithoutContext

The same as FindAnyScopeComponentByIDispatch -
    but static - gets context from Viper

Uses FindAnyScopeComponentByIUnknown.

Parameters:
    IDispatch         *pDisp   find by this pointer
    CComponentObject **ppObj   found object

Returns:
    HRESULT
        (S_FALSE if no error - not found)
===================================================================*/
HRESULT CPageComponentManager::FindComponentWithoutContext
(
IDispatch *pDisp,
CComponentObject **ppObj
)
    {
    // Get HitObj from Viper Context
    CHitObj *pHitObj = NULL;
    ViperGetHitObjFromContext(&pHitObj);
    if (!pHitObj)
        return E_FAIL;

    // Get page component manager
    CPageComponentManager *pPCM = pHitObj->PPageComponentManager();
    if (!pPCM)
        return E_FAIL;

    // Call the page component manager to find the object
    return pPCM->FindAnyScopeComponentByIUnknown(pDisp, ppObj);
    }

/*===================================================================
CPageComponentManager::RemoveComponent

Remove component -- the early release logic

Parameters:
    IDispatch         *pDisp   find by this pointer
    CComponentObject **ppObj   found object

Returns:
    HRESULT
===================================================================*/
 HRESULT CPageComponentManager::RemoveComponent
 (
 CComponentObject *pObj
 )
    {
    Assert(pObj);

    CComponentCollection *pCollection;
    HRESULT hr = GetCollectionByScope(pObj->m_csScope, &pCollection);
    if (FAILED(hr))
        return hr;

    return pCollection->RemoveComponent(pObj);
    }

/*===================================================================
  C  C o m p o n e n t  I t e r a t o r
===================================================================*/

/*===================================================================
CComponentIterator::CComponentIterator

CComponentIterator constructor

Parameters:
    CHitObj *pHitObj    page to init with (optional)

Returns:
===================================================================*/
CComponentIterator::CComponentIterator(CHitObj *pHitObj)
    : m_fInited(FALSE), m_fFinished(FALSE), m_pHitObj(NULL),
      m_pLastObj(NULL), m_csLastScope(csUnknown)
    {
    if (pHitObj)
        Init(pHitObj);
    }

/*===================================================================
CComponentIterator::~CComponentIterator

CComponentIterator destructor

Parameters:

Returns:
===================================================================*/
CComponentIterator::~CComponentIterator()
    {
    }

/*===================================================================
CComponentIterator::Init

Start iterating

Parameters:
    CHitObj *pHitObj    page

Returns:
    HRESULT
===================================================================*/
HRESULT CComponentIterator::Init
(
CHitObj *pHitObj
)
    {
    Assert(pHitObj);
    pHitObj->AssertValid();

    m_pHitObj = pHitObj;
    m_fInited = TRUE;
    m_fFinished = FALSE;

    m_pLastObj = NULL;
    m_csLastScope = csUnknown;

    return S_OK;
    }

/*===================================================================
CComponentIterator::WStrNextComponentName

The iteration function

Parameters:

Returns:
    Next component name or NULL if done
===================================================================*/
LPWSTR CComponentIterator::WStrNextComponentName()
    {
    Assert(m_fInited);

    if (m_fFinished)
        return NULL;

    Assert(m_pHitObj);

    CompScope csScope = m_csLastScope;
    CComponentObject *pObj = m_pLastObj ?
        static_cast<CComponentObject *>(m_pLastObj->m_pNext) : NULL;

    while (!m_fFinished)
        {
        // try the current scope

        if (pObj)
            {
            Assert(pObj->m_ctType == ctTagged);
            Assert(pObj->GetName());

            m_pLastObj = pObj;
            m_csLastScope = csScope;
            return pObj->GetName();
            }

        // couldn't find in the current scope - try next scope
        CComponentCollection *pCol = NULL;

        switch (csScope)
            {
            case csUnknown:
                csScope = csPage;
                m_pHitObj->GetPageComponentCollection(&pCol);
                break;
            case csPage:
                csScope = csSession;
                m_pHitObj->GetSessionComponentCollection(&pCol);
                break;
            case csSession:
                csScope = csAppln;
                m_pHitObj->GetApplnComponentCollection(&pCol);
                break;
            case csAppln:
            default:
                csScope = csUnknown;
                m_fFinished = TRUE;
                break;
            }

        // start at the beginning of the new collection

        if (pCol)
            pObj = static_cast<CComponentObject *>(pCol->m_htTaggedObjects.Head());
        }

    // finished
    return NULL;
    }

/*===================================================================
  C  V a r i a n t s I t e r a t o r
===================================================================*/

/*===================================================================
CVariantsIterator::CVariantsIterator

CVariantsIterator constructor by application

Parameters:
    CAppln  *pAppln       collection to init with
    DWORD    ctCollType   type of components to list iteration

Returns:
===================================================================*/
CVariantsIterator::CVariantsIterator
(
CAppln *pAppln,
DWORD ctColType
)
    : m_pCompColl(NULL), m_pAppln(NULL), m_pSession(NULL)
    {
    Assert(pAppln);
    pAppln->AddRef();

    m_cRefs = 1;
    m_pCompColl = pAppln->PCompCol();
    m_pAppln = pAppln;
    m_ctColType = ctColType;
    m_dwIndex = 0;
    }

/*===================================================================
CVariantsIterator::CVariantsIterator

CVariantsIterator constructor by session

Parameters:
    CSession *pSession        collection to init with
    DWORD     ctCollType      type of components to list iteration

Returns:
===================================================================*/
CVariantsIterator::CVariantsIterator
(
CSession *pSession,
DWORD ctColType
)
    : m_pCompColl(NULL), m_pAppln(NULL), m_pSession(NULL)
    {
    Assert(pSession);
    pSession->AddRef();

    m_cRefs = 1;
    m_pCompColl = pSession->PCompCol();
    m_ctColType = ctColType;
    m_pSession = pSession;
    m_dwIndex = 0;
    }

/*===================================================================
CVariantsIterator::~CVariantsIterator

CVariantsIterator destructor

Parameters:

Returns:
===================================================================*/
CVariantsIterator::~CVariantsIterator()
    {
    if (m_pSession)
        m_pSession->Release();
    if (m_pAppln)
        m_pAppln->Release();
    }

/*===================================================================
CVariantsIterator::QueryInterface

CVariantsIterator QI

Parameters:
    GUID&    iid
    VOID **  ppvObj

Returns: HRESULT
===================================================================*/
STDMETHODIMP CVariantsIterator::QueryInterface
(
const GUID &iid,
void **ppvObj
)
    {
    if (iid == IID_IUnknown || iid == IID_IEnumVARIANT)
        {
        AddRef();
        *ppvObj = this;
        return S_OK;
        }

    *ppvObj = NULL;
    return E_NOINTERFACE;
    }

/*===================================================================
CVariantsIterator::AddRef

CVariantsIterator AddRef

Parameters:

Returns: ULONG
===================================================================*/
STDMETHODIMP_(ULONG) CVariantsIterator::AddRef()
    {
    return ++m_cRefs;
    }

/*===================================================================
CVariantsIterator::Release

CVariantsIterator Release

Parameters:

Returns:
===================================================================*/
STDMETHODIMP_(ULONG) CVariantsIterator::Release()
    {
    if (--m_cRefs > 0)
        return m_cRefs;

    delete this;
    return 0;
    }

/*===================================================================
CVariantsIterator::Clone

CVariantsIterator Clone

Parameters:

Returns:
===================================================================*/
STDMETHODIMP CVariantsIterator::Clone
(
IEnumVARIANT **ppEnumReturn
)
    {
    CVariantsIterator *pNewIterator = NULL;
    if (m_pSession)
        {
        pNewIterator = new CVariantsIterator(m_pSession, m_ctColType);
        }
    else if (m_pAppln)
        {
        pNewIterator = new CVariantsIterator(m_pAppln, m_ctColType);
        }
    else
        {
        Assert(FALSE);  // better be either Appln or Session
        return E_FAIL;
        }

    if (pNewIterator == NULL)
        return E_OUTOFMEMORY;

    // new iterator should point to same location as this.
    pNewIterator->m_dwIndex = m_dwIndex;

    *ppEnumReturn = pNewIterator;
    return S_OK;
    }

/*===================================================================
CVariantsIterator::Next

CVariantsIterator Next

Parameters:

Returns:
===================================================================*/
STDMETHODIMP CVariantsIterator::Next
(
unsigned long cElementsRequested,
VARIANT *rgVariant,
unsigned long *pcElementsFetched
)
    {
    // give a valid pointer value to 'pcElementsFetched'
    unsigned long cElementsFetched;
    if (pcElementsFetched == NULL)
        pcElementsFetched = &cElementsFetched;

    if (cElementsRequested == 0)
        {
        if (pcElementsFetched)
            *pcElementsFetched = 0;
        return S_OK;
        }

    DWORD cMax = 0;
    if (m_ctColType == ctTagged)
        {
        cMax = m_pCompColl ? m_pCompColl->m_cAllTagged : 0;
        }
    else if (m_ctColType == ctProperty)
        {
        cMax = m_pCompColl ? m_pCompColl->m_cProperties : 0;
        }
    else
        {
        // Should always be either tagged object or property
        Assert(FALSE);
        return E_FAIL;
        }

    // Loop through the collection until either we reach the end or
    // cElements becomes zero
    //
    unsigned long cElements = cElementsRequested;
    *pcElementsFetched = 0;

    while (cElements > 0 && m_dwIndex < cMax)
        {
        LPWSTR pwszName = NULL;

        if (m_pAppln) 
            m_pAppln->Lock();

        m_pCompColl->GetNameByIndex(m_ctColType, ++m_dwIndex, &pwszName);

        if (!pwszName) {
            if (m_pAppln)
                m_pAppln->UnLock();
            continue;
        }

        BSTR bstrT = SysAllocString(pwszName);

        if (m_pAppln)
            m_pAppln->UnLock();

        if (!bstrT)
            return E_OUTOFMEMORY;

        V_VT(rgVariant) = VT_BSTR;
        V_BSTR(rgVariant) = bstrT;
		++rgVariant;

        --cElements;
        ++(*pcElementsFetched);
        }

    // initialize the remaining variants
    while (cElements-- > 0)
        VariantInit(rgVariant++);

    return (*pcElementsFetched == cElementsRequested)? S_OK : S_FALSE;
    }

/*===================================================================
CVariantsIterator::Skip

CVariantsIterator Skip

Parameters:

Returns:
===================================================================*/
STDMETHODIMP CVariantsIterator::Skip
(
unsigned long cElements
)
    {
    /* Adjust the index by cElements or
     * until we hit the max element
     */
    DWORD cMax = 0;

    // We iterate over different arrays depending on the collection type
    if (m_ctColType == ctTagged)
        {
        cMax = m_pCompColl ? m_pCompColl->m_cAllTagged : 0;
        }
    else if (m_ctColType == ctProperty)
        {
        cMax = m_pCompColl ? m_pCompColl->m_cProperties : 0;
        }
    else
        {
        // Should always be either tagged object or property
        Assert(FALSE);
        return E_FAIL;
        }

	m_dwIndex += cElements;
    return (m_dwIndex < cMax)? S_OK : S_FALSE;
    }

/*===================================================================
CVariantsIterator::Reset

CVariantsIterator Reset

Parameters:

Returns:
===================================================================*/
STDMETHODIMP CVariantsIterator::Reset()
    {
    m_dwIndex = 0;
    return NO_ERROR;
    }