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

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

Component: Application Object Manager

File: Applmgr.cpp

Owner: PramodD

This is the Application Manager source file.
===================================================================*/
#include "denpre.h"
#pragma hdrstop

#include "activdbg.h"
#include "mtacb.h"
#include "debugger.h"
#include "memchk.h"

PTRACE_LOG          CAppln::gm_pTraceLog = NULL;
CApplnMgr           g_ApplnMgr;
CApplnCleanupMgr    g_ApplnCleanupMgr;
DWORD               g_nApplications = 0;
DWORD               g_nApplicationsRestarting = 0;
DWORD               g_nApplicationsRestarted = 0;
LONG                g_nAppRestartThreads = 0;

#define DENALI_FILE_NOTIFY_FILTER 0

#pragma warning (disable: 4355)  // ignore: "'this' used in base member init

/*===================================================================
   S c r i p t E n g i n e C l e a n u p

Node type for linked list of script engines to cleanup
===================================================================*/

struct CScriptEngineCleanupElem : CDblLink
    {
    CActiveScriptEngine *m_pEngine;
    CScriptEngineCleanupElem(CActiveScriptEngine *pEngine) : m_pEngine(pEngine)
        {
        m_pEngine->AddRef();
        }

    ~CScriptEngineCleanupElem()
        {
        m_pEngine->FinalRelease();
        }
    };

/*===================================================================
   C A p p l n V a r i a n t s
===================================================================*/

/*===================================================================
CApplnVariants::CApplnVariants

Constructor

Parameters:

Returns:
===================================================================*/
CApplnVariants::CApplnVariants()
    :
    m_cRefs(1),
    m_pAppln(NULL),
    m_ctColType(ctUnknown),
    m_ISupportErrImp(this, this, IID_IVariantDictionary)
    {
    CDispatch::Init(IID_IVariantDictionary);
    }

/*===================================================================
CApplnVariants::~CApplnVariants

Constructor

Parameters:

Returns:
===================================================================*/
CApplnVariants::~CApplnVariants()
    {
    Assert(!m_pAppln);
    }

/*===================================================================
CApplnVariants::Init

Init ApplnVariants

Parameters:
    CAppln   *pAppln            application
    CompType  ctColType         component collection type

Returns:
    HRESULT
===================================================================*/
HRESULT CApplnVariants::Init
(
CAppln *pAppln,
CompType ctColType
)
    {
    Assert(pAppln);
    pAppln->AddRef();

    Assert(!m_pAppln);

    m_pAppln    = pAppln;
    m_ctColType = ctColType;
    return S_OK;
    }

/*===================================================================
CApplnVariants::UnInit

UnInit ApplnVariants

Parameters:

Returns:
    HRESULT
===================================================================*/
HRESULT CApplnVariants::UnInit()
    {
    if (m_pAppln)
        {
        m_pAppln->Release();
        m_pAppln = NULL;
        }
    return S_OK;
    }

/*===================================================================
CApplnVariants::QueryInterface
CApplnVariants::AddRef
CApplnVariants::Release

IUnknown members for CApplnVariants object.
===================================================================*/
STDMETHODIMP CApplnVariants::QueryInterface
(
REFIID iid,
void **ppvObj
)
    {
    if (iid == IID_IUnknown || iid == IID_IDispatch ||
        iid == IID_IVariantDictionary)
        {
        AddRef();
        *ppvObj = this;
        return S_OK;
        }
    else if (iid == IID_ISupportErrorInfo)
        {
        m_ISupportErrImp.AddRef();
        *ppvObj = &m_ISupportErrImp;
        return S_OK;
        }

    *ppvObj = NULL;
    return E_NOINTERFACE;
    }

STDMETHODIMP_(ULONG) CApplnVariants::AddRef()
    {
    return InterlockedIncrement((LPLONG)&m_cRefs);
    }

STDMETHODIMP_(ULONG) CApplnVariants::Release()
    {
    if (InterlockedDecrement((LPLONG)&m_cRefs) > 0)
        return m_cRefs;

    delete this;
    return 0;
    }

/*===================================================================
CApplnVariants::ObjectNameFromVariant

Gets name from variant. Resolves operations by index.
Allocates memory for name.

Parameters:
    vKey        VARIANT
    ppwszName   [out] allocated name
    fVerify     flag - check existance if named

Returns:
    HRESULT
===================================================================*/
HRESULT CApplnVariants::ObjectNameFromVariant
(
VARIANT &vKey,
WCHAR **ppwszName,
BOOL fVerify
)
    {
    *ppwszName = NULL;

    if (!m_pAppln->PCompCol())
        return E_FAIL;

    VARIANT *pvarKey = &vKey;
    VARIANT varKeyCopy;
    VariantInit(&varKeyCopy);
    if (V_VT(pvarKey) != VT_BSTR && V_VT(pvarKey) != VT_I2 && V_VT(pvarKey) != VT_I4)
        {
        if (FAILED(VariantResolveDispatch(&varKeyCopy, &vKey, IID_IVariantDictionary, IDE_APPLICATION)))
            {
            ExceptionId(IID_IVariantDictionary, IDE_APPLICATION, IDE_EXPECTING_STR);
            VariantClear(&varKeyCopy);
            return E_FAIL;
            }
        pvarKey = &varKeyCopy;
        }

    LPWSTR pwszName = NULL;

    switch (V_VT(pvarKey))
        {
        case VT_BSTR:
            {
            pwszName = V_BSTR(pvarKey);

            if (fVerify && pwszName)
                {
                CComponentObject *pObj = NULL;

                Assert(m_pAppln);
                Assert(m_pAppln->PCompCol());

                if (m_ctColType == ctTagged)
                    m_pAppln->PCompCol()->GetTagged(pwszName, &pObj);
                else
                    m_pAppln->PCompCol()->GetProperty(pwszName, &pObj);

                if (!pObj || pObj->GetType() != m_ctColType)
                    pwszName = NULL; // as if not found
                }
            break;
            }

        case VT_I1:  case VT_I2:               case VT_I8:
        case VT_UI1: case VT_UI2: case VT_UI4: case VT_UI8:
        case VT_R4:  case VT_R8:
            // Coerce all integral types to VT_I4
            if (FAILED(VariantChangeType(pvarKey, pvarKey, 0, VT_I4)))
                return E_FAIL;

        // fallthru to VT_I4

        case VT_I4:
            {
            int i;
            // Look up the object by index
            i = V_I4(pvarKey);

            if (i > 0)
                {
                Assert(m_pAppln);
                Assert(m_pAppln->PCompCol());

                m_pAppln->PCompCol()->GetNameByIndex
                    (
                    m_ctColType,
                    i,
                    &pwszName
                    );
                }
            break;
            }
        }

    if (pwszName)
    {
        *ppwszName = StringDupW(pwszName);        
    }

    VariantClear(&varKeyCopy);
    return S_OK;
    
    }

/*===================================================================
CApplnVariants::get_Item

Function called from DispInvoke to get keys from the collection.

Parameters:
    vKey        VARIANT [in], which parameter to get the value of - integers access collection as an array
    pvarReturn    VARIANT *, [out] value of the requested parameter

Returns:
    S_OK on success, E_FAIL on failure.
===================================================================*/
HRESULT CApplnVariants::get_Item
(
VARIANT varKey,
VARIANT *pVar
)
    {
    if (!m_pAppln || FAILED(m_pAppln->CheckForTombstone()))
        return E_FAIL;

    // Initialize return value
    VariantInit(pVar);

    if (!m_pAppln->PCompCol())
        return S_OK;

    // Get HitObj from Viper if Tagged Variants
    CHitObj *pHitObj = NULL;
    if (m_ctColType == ctTagged)
        {
        ViperGetHitObjFromContext(&pHitObj);
        if (!pHitObj)
            return S_OK; // return emtpy variant
        }

    m_pAppln->Lock();

    // Get name
    WCHAR *pwszName = NULL;
    HRESULT hr = ObjectNameFromVariant(varKey, &pwszName);
    if (!pwszName)
        {
        m_pAppln->UnLock();
        return hr;
        }

    // Find object by name
    CComponentObject *pObj = NULL;

    if (m_ctColType == ctTagged)
        {
        Assert(pHitObj);
        // need to go through HitObj for instantiation
        pHitObj->GetComponent(csAppln, pwszName, CbWStr(pwszName), &pObj);
        if (pObj && (pObj->GetType() != ctTagged))
            pObj = NULL;
        }
    else
        {
        m_pAppln->PCompCol()->GetProperty(pwszName, &pObj);
        }

    if (pObj)
        pObj->GetVariant(pVar);

    m_pAppln->UnLock();

    free(pwszName);
    return S_OK;
    }

/*===================================================================
CApplnVariants::put_Item

OLE automation put for Item property

Parameters:
    varKey     key
    Var        value

Returns:
    S_OK on success, E_FAIL on failure.
===================================================================*/
HRESULT CApplnVariants::put_Item
(
VARIANT varKey,
VARIANT Var
)
    {
    if (!m_pAppln || FAILED(m_pAppln->CheckForTombstone()))
        return E_FAIL;

    if (m_ctColType == ctTagged)
        {
        ExceptionId(IID_IVariantDictionary, IDE_APPLICATION,
                    IDE_CANT_MOD_STATICOBJECTS);
        return E_FAIL;
        }

    Assert(m_ctColType == ctProperty);

    if (!m_pAppln->PCompCol())
        return E_FAIL;

    m_pAppln->Lock();

    // Resolve the variant
    VARIANT varResolved;
    HRESULT hr = VariantResolveDispatch
        (
        &varResolved,
        &Var,
        IID_IApplicationObject,
        IDE_APPLICATION
        );
    if (FAILED(hr))
        {
        m_pAppln->UnLock();
        return hr;      // exception already raised
        }

    // Get name
    WCHAR *pwszName = NULL;
    hr = ObjectNameFromVariant(varKey, &pwszName);
    if (pwszName)
        {
        // Set the property
        if (m_pAppln->PCompCol())
            hr = m_pAppln->PCompCol()->AddProperty(pwszName, &varResolved);
        else
            hr = E_FAIL; // not likely if application not UnInited
        }

    VariantClear(&varResolved);
    m_pAppln->UnLock();

    if (hr == RPC_E_WRONG_THREAD)
        {
        // We use RPC_E_WRONG_THREAD to indicate bad model object
        ExceptionId(IID_IApplicationObject,
                    IDE_APPLICATION, IDE_APPLICATION_CANT_STORE_OBJECT);
         hr = E_FAIL;
        }

    free(pwszName);
    return hr;
    }

/*===================================================================
CApplnVariants::putref_Item

OLE automation putref for Item property

Parameters:
    varKey     key
    Var        value

Returns:
    S_OK on success, E_FAIL on failure.
===================================================================*/
HRESULT CApplnVariants::putref_Item
(
VARIANT varKey,
VARIANT Var
)
    {
    if (!m_pAppln || FAILED(m_pAppln->CheckForTombstone()))
        return E_FAIL;

    if (m_ctColType == ctTagged)
        {
        ExceptionId(IID_IVariantDictionary, IDE_APPLICATION,
                    IDE_CANT_MOD_STATICOBJECTS);
        return E_FAIL;
        }

    if (FIsIntrinsic(&Var))
        {
        ExceptionId(IID_IVariantDictionary, IDE_APPLICATION, IDE_APPLICATION_CANT_STORE_INTRINSIC);
        return E_FAIL;
        }

    Assert(m_ctColType == ctProperty);

    if (!m_pAppln->PCompCol())
        return E_FAIL;

    m_pAppln->Lock();

    // Get name
    WCHAR *pwszName = NULL;
    HRESULT hr = ObjectNameFromVariant(varKey, &pwszName);
    if (pwszName)
        {
        // Set the property
        if (m_pAppln->PCompCol())
            hr = m_pAppln->PCompCol()->AddProperty(pwszName, &Var);
        else
            hr = E_FAIL; // not likely if application not UnInited
        }

    m_pAppln->UnLock();

    if (hr == RPC_E_WRONG_THREAD)
        {
        // We use RPC_E_WRONG_THREAD to indicate bad model object
        ExceptionId(IID_IApplicationObject,
                    IDE_APPLICATION, IDE_APPLICATION_CANT_STORE_OBJECT);
         hr = E_FAIL;
        }

    if (pwszName)
        free(pwszName);
    return hr;
    }

/*===================================================================
CApplnVariants::get_Key

Function called from DispInvoke to get values from the collection.

Parameters:
    vKey        VARIANT [in], which parameter to get the value of - integers access collection as an array
    pvarReturn    VARIANT *, [out] value of the requested parameter

Returns:
    S_OK on success, E_FAIL on failure.
===================================================================*/
HRESULT CApplnVariants::get_Key
(
VARIANT varKey,
VARIANT *pVar
)
    {
    if (!m_pAppln || FAILED(m_pAppln->CheckForTombstone()))
        return E_FAIL;

    VariantInit(pVar);

    if (!m_pAppln->PCompCol())
        return S_OK;

    m_pAppln->Lock();

    // Get name
    WCHAR *pwszName = NULL;
    HRESULT hr = ObjectNameFromVariant(varKey, &pwszName, TRUE);

    m_pAppln->UnLock();

    if (!pwszName)
        return hr;

    // Return BSTr
    BSTR bstrT = SysAllocString(pwszName);
    free(pwszName);

    if (!bstrT)
        return E_OUTOFMEMORY;

    V_VT(pVar) = VT_BSTR;
    V_BSTR(pVar) = bstrT;
    return S_OK;
    }

/*===================================================================
CApplnVariants::get_Count

Parameters:
    pcValues - count is stored in *pcValues
===================================================================*/
STDMETHODIMP CApplnVariants::get_Count
(
int *pcValues
)
    {
    if (!m_pAppln || FAILED(m_pAppln->CheckForTombstone()))
        return E_FAIL;

    if (m_ctColType == ctTagged)
        *pcValues = m_pAppln->m_pApplCompCol->GetTaggedObjectCount();
    else
        *pcValues = m_pAppln->m_pApplCompCol->GetPropertyCount();

    return S_OK;
    }

/*===================================================================
CApplnVariants::get__NewEnum

Return a new enumerator
===================================================================*/
HRESULT CApplnVariants::get__NewEnum
(
IUnknown **ppEnumReturn
)
    {
    if (!m_pAppln || FAILED(m_pAppln->CheckForTombstone()))
        return E_FAIL;

    *ppEnumReturn = NULL;

    CVariantsIterator *pIterator = new CVariantsIterator(m_pAppln, m_ctColType);
    if (pIterator == NULL)
        {
        ExceptionId(IID_IVariantDictionary, IDE_SESSION, IDE_OOM);
        return E_OUTOFMEMORY;
        }

    *ppEnumReturn = pIterator;
    return S_OK;
    }

/*===================================================================
CApplnVariants::Remove

Remove item

Parameters:
    varKey     key

Returns:
    S_OK on success, E_FAIL on failure.
===================================================================*/
HRESULT CApplnVariants::Remove
(
VARIANT varKey
)
    {
    if (!m_pAppln || FAILED(m_pAppln->CheckForTombstone()))
        return E_FAIL;

    if (m_ctColType == ctTagged)
        {
        ExceptionId(IID_IVariantDictionary, IDE_APPLICATION,
                    IDE_CANT_MOD_STATICOBJECTS);
        return E_FAIL;
        }

    Assert(m_ctColType == ctProperty);

    m_pAppln->Lock();

    // Get name
    WCHAR *pwszName = NULL;
    ObjectNameFromVariant(varKey, &pwszName);
    if (pwszName)
        {
        CComponentCollection *pCompCol = m_pAppln->PCompCol();

        // Set the property
        if (pCompCol)
            pCompCol->RemoveProperty(pwszName);

        free(pwszName);
        }

    m_pAppln->UnLock();
    return S_OK;
    }

/*===================================================================
CApplnVariants::RemoveAll

Remove all items

Parameters:
    varKey     key

Returns:
    S_OK on success, E_FAIL on failure.
===================================================================*/
HRESULT CApplnVariants::RemoveAll()
    {
    if (!m_pAppln || FAILED(m_pAppln->CheckForTombstone()))
        return E_FAIL;

    if (m_ctColType == ctTagged)
        {
        ExceptionId(IID_IVariantDictionary, IDE_APPLICATION,
                    IDE_CANT_MOD_STATICOBJECTS);
        return E_FAIL;
        }

    Assert(m_ctColType == ctProperty);

    m_pAppln->Lock();

    CComponentCollection *pCompCol = m_pAppln->PCompCol();
        
    if (pCompCol)
        {
        pCompCol->RemoveAllProperties();
        }

    m_pAppln->UnLock();
    return S_OK;
    }


/*===================================================================
  C A p p l n
===================================================================*/

/*===================================================================
CAppln::CAppln

Constructor

Parameters:
    NONE

Returns:
    NONE
===================================================================*/

CAppln::CAppln()
    :
    m_fInited(FALSE),
    m_fFirstRequestRan(FALSE),
    m_fGlobalChanged(FALSE),
    m_fDeleteInProgress(FALSE),
    m_fTombstone(FALSE),
    m_fDebuggable(FALSE),
    m_fNotificationAdded(FALSE),
    m_fUseImpersonationHandle(FALSE),
    m_cRefs(1),
    m_pszMetabaseKey(NULL),
    m_pszApplnPath(NULL),
    m_pszGlobalAsa(NULL),
    m_pGlobalTemplate(NULL),
    m_cSessions(0),
    m_cRequests(0),
    m_pSessionMgr(NULL),
    m_pApplCompCol(NULL),
    m_pProperties(NULL),
    m_pTaggedObjects(NULL),
    m_pAppRoot(NULL),
    m_pActivity(NULL),
    m_dwLockThreadID(INVALID_THREADID),
    m_cLockRefCount(0),
    m_hUserImpersonation(NULL),
    m_pdispGlobTypeLibWrapper(NULL),
    m_pServicesConfig(NULL)
{
    
    // COM stuff
    m_ISuppErrImp.Init(static_cast<IApplicationObject *>(this), 
                        static_cast<IApplicationObject *>(this), 
                        IID_IApplicationObject);
    CDispatch::Init(IID_IApplicationObject);

    IF_DEBUG(APPLICATION) {
		WriteRefTraceLog(gm_pTraceLog, m_cRefs, this);
    }
}

/*===================================================================
CAppln::~CAppln

Destructor

Parameters:
    NONE

Returns:
    NONE
===================================================================*/
CAppln::~CAppln()
    {
    Assert(m_fTombstone);  // must be tombstoned before destructor
    Assert(m_cRefs == 0);  // must have 0 ref count

    #ifdef DBG_NOTIFICATION
    DBGPRINTF((DBG_CONTEXT, "Deleting application %p\n", this));
    #endif // DBG_NOTIFICATION
    }

/*===================================================================
HRESULT CAppln::Init

Initialize object

Parameters:
    char *pszApplnKey           application's metabase key
    char *pszApplnPath          application's directory path
    CIsapiReqInfo  *pIReq       Isapi Req Info
    HANDLE hUserImpersonation   impersonation handle

Returns:
    S_OK              Success
    E_FAIL            Failure
    E_OUTOFMEMORY    Out of memory failure
===================================================================*/
HRESULT CAppln::Init
(
TCHAR *pszApplnKey,
TCHAR *pszApplnPath,
CIsapiReqInfo *pIReq,
HANDLE hUserImpersonation
)
    {
    HRESULT         hr = S_OK;
    CMBCSToWChar    convStr;

    InterlockedIncrement((LPLONG)&g_nApplications);

    Assert(pszApplnKey);
    Assert(pszApplnPath);

    void *pHashKey = NULL;
    DWORD dwHashKeyLength = 0;
    DWORD cch;

    // Debugging variables (These are placed here for possible cleanup)
    IDebugApplicationNode *pVirtualServerRoot = NULL;
    CFileNode *pFileNode = NULL;

    // Critical sections created together --
    // they are deleted in the destructor based on m_fInited flag

    ErrInitCriticalSection(&m_csInternalLock, hr);
    if (SUCCEEDED(hr))
        {
        ErrInitCriticalSection(&m_csApplnLock, hr);
        if (FAILED(hr))
            DeleteCriticalSection(&m_csInternalLock);
        }

    if (FAILED(hr))
    	{
    	DBGPRINTF((DBG_CONTEXT, "New Application Failed to acquire Critical Section, hr = %08x\n", hr));
        return hr;
        }

    // Remember (copy of) metabase key

    cch = _tcslen(pszApplnKey);
    m_pszMetabaseKey = new TCHAR[(cch+1) * sizeof(TCHAR)];
    if (!m_pszMetabaseKey)
    	goto LCleanupOOM;
    memcpy(m_pszMetabaseKey, pszApplnKey, (cch+1)*sizeof(TCHAR));

    pHashKey = m_pszMetabaseKey;
    dwHashKeyLength = cch * sizeof(TCHAR);

    // Remember (copy of) appln path
    cch = _tcslen(pszApplnPath);
    m_pszApplnPath = new TCHAR[(cch+1) * sizeof(TCHAR)];
    if (!m_pszApplnPath)
        goto LCleanupOOM;
    memcpy(m_pszApplnPath, pszApplnPath, (cch+1)*sizeof(TCHAR));

    // Get virtual path of appln & remember what it is
    TCHAR szApplnVRoot[256];
    if (FAILED(FindApplicationPath(pIReq, szApplnVRoot, sizeof szApplnVRoot)))
    	{
        DBGWARN((DBG_CONTEXT, "New Application Failed to FindApplicationPath(), hr = %#08x\n", hr));
        goto LCleanup;
        }

    if ((m_pszApplnVRoot = new TCHAR [(_tcslen(szApplnVRoot) + 1)*sizeof(TCHAR)]) == NULL)
        goto LCleanupOOM;
    _tcscpy(m_pszApplnVRoot, szApplnVRoot);

    // Initialize link element with key
    Assert(pHashKey);
    Assert(dwHashKeyLength);

    if (FAILED(CLinkElem::Init(pHashKey, dwHashKeyLength)))
    	goto LCleanupOOM;

    // Setup impersonation

    m_fNotificationAdded = FALSE;
    if (FIsWinNT() && pszApplnPath &&
        pszApplnPath[0] ==_T('\\') && pszApplnPath[1] == _T('\\'))
        {
        m_fUseImpersonationHandle = DuplicateToken
            (
            hUserImpersonation,
            SecurityImpersonation,
            &m_hUserImpersonation
            );
        }

    m_cSessions = 0;
    m_cRequests = 0;

    // Create and init app config
    m_pAppConfig = new CAppConfig();
    if (!m_pAppConfig)
		goto LCleanupOOM;

    hr = m_pAppConfig->Init(pIReq, this);
    if (FAILED(hr))
        {
		DBGWARN((DBG_CONTEXT, "New Application Failed: Could not Init the AppConfig, hr = %#08x\n", hr));
        goto LCleanup;
        }

    // Create and init application level component collection
    m_pApplCompCol = new CComponentCollection;
    if (!m_pApplCompCol)
        goto LCleanupOOM;

    hr = m_pApplCompCol->Init(csAppln);
    if (FAILED(hr))
        {
		DBGWARN((DBG_CONTEXT, "New Application Failed: Could not Init the Component Collection, hr = %#08x\n", hr));
        goto LCleanup;
        }

    // initialize application properties collection
    m_pProperties = new CApplnVariants;
    if (!m_pProperties)
        goto LCleanupOOM;
    hr = m_pProperties->Init(this, ctProperty);
    if (FAILED(hr))
        {
		DBGWARN((DBG_CONTEXT, "New Application Failed: Could not Init the Application Properties, hr = %#08x\n", hr));
        goto LCleanup;
        }

    // initialize application tagged object collection
    m_pTaggedObjects = new CApplnVariants;
    if (!m_pTaggedObjects)
        goto LCleanupOOM;
    hr = m_pTaggedObjects->Init(this, ctTagged);
    if (FAILED(hr))
        {
		DBGWARN((DBG_CONTEXT, "New Application Failed: Could not Init the Application Tagged Objects, hr = %#08x\n", hr));
        goto LCleanup;
        }

    // Initialize the CServicesConfig Object

    hr = InitServicesConfig();
    if (FAILED(hr)) {
		DBGWARN((DBG_CONTEXT, "New Application Failed: Could not Init ServicesConfig, hr = %#08x\n", hr));
		goto LCleanup;
    }

    // Debugging support  - Create an application node
    // If PDM does not exist it means debugger not installed or it's Win 95
    //
    if (g_pPDM)
        {
        // Debugging directories are shown as:
        //
        //      <virtual web server>
        //         <application name>
        //              <path to ASP>
        //
        // Get a pointer to the document node containing the virtual web server.
        if (FAILED(hr = GetServerDebugRoot(pIReq, &pVirtualServerRoot)))
        	{
        	DBGWARN((DBG_CONTEXT, "New Application Failed: Could not GetServerDebugRoot(), hr = %#08x\n", hr));
            goto LCleanup;
            }

        // Create a node for this application
        if (FAILED(hr = g_pDebugApp->CreateApplicationNode(&m_pAppRoot)))
        	{
        	DBGWARN((DBG_CONTEXT, "New Application Failed: Could not CreateApplicationNode(), hr = %#08x\n", hr));
            goto LCleanup;
            }

        // Create a doc provider for the node
        if ((pFileNode = new CFileNode) == NULL)
            goto LCleanupOOM;

        // Name the application
        TCHAR szDebugApp[256];
        TCHAR *pchEnd = strcpyEx(szDebugApp, m_pszApplnVRoot);
        if (! QueryAppConfig()->fAllowDebugging()) {
#if UNICODE
            CwchLoadStringOfId(
#else
            CchLoadStringOfId(
#endif
            IDS_DEBUGGING_DISABLED, pchEnd, DIFF(&szDebugApp[sizeof (szDebugApp)/sizeof(TCHAR)] - pchEnd));
            m_fDebuggable = FALSE;
        }
        else
            m_fDebuggable = TRUE;
        Assert (_tcslen(szDebugApp) < (sizeof(szDebugApp)/sizeof(TCHAR)));

        WCHAR   *pswzDebugApp;
#if UNICODE
        pswzDebugApp = szDebugApp;
#else
        if (FAILED(hr = convStr.Init(szDebugApp))) {
        	DBGWARN((DBG_CONTEXT, "New Application Failed: Cannot convert szDebugApp to UNICODE, hr = %#08x\n", hr));
            goto LCleanup;
        }
        pswzDebugApp = convStr.GetString();
#endif

        if (FAILED(hr = pFileNode->Init(pswzDebugApp)))
        	{
        	DBGWARN((DBG_CONTEXT, "New Application Failed: Cannot Init CFileNode, hr = %#08x\n", hr));
            goto LCleanup;
            }

        if (FAILED(hr = m_pAppRoot->SetDocumentProvider(pFileNode)))
        	{
        	DBGWARN((DBG_CONTEXT, "New Application Failed: SetDocumentProvider failed, hr = %#08x\n", hr));
            goto LCleanup;
            }

        // Attach to the UI
        if (FAILED(hr = m_pAppRoot->Attach(pVirtualServerRoot)))
        	{
        	DBGWARN((DBG_CONTEXT, "New Application Failed: Could not Attach to debugger, hr = %#08x\n", hr));
            goto LCleanup;
            }

        // If this application had a previous incarnation (changed global.asa
        // or debugging being flipped on in midstream), then there may be some
        // documents in the cache which should be added to the debugger now.
        if (m_fDebuggable)
            {
            g_TemplateCache.AddApplicationToDebuggerUI(this);

            // create the global debug activity if it hasn't been created already
            if (g_pDebugActivity == NULL) {
                g_pDebugActivity = new CViperActivity;
                if (g_pDebugActivity == NULL) {
                    hr = E_OUTOFMEMORY;
                    goto LCleanup;
                }
                if (FAILED(hr = g_pDebugActivity->Init(m_pServicesConfig))) {
				    DBGWARN((DBG_CONTEXT, "New Application Failed: Could not create global debug activity, hr = %#08x\n", hr));
				    goto LCleanup;
                }
            }

            // In DEBUG mode: all requests run on the same thread
            if (FAILED(hr = BindToActivity(g_pDebugActivity)))
				{
				DBGWARN((DBG_CONTEXT, "New Application Failed: Could not bind application to debugging activity, hr = %#08x\n", hr));
				goto LCleanup;
				}
            }
        }

    // For Win95: all requests run on the same thread

    if (!FIsWinNT())
        {
        if (FAILED(hr = BindToActivity()))
			{
			DBGWARN((DBG_CONTEXT, "New Application Failed: Could not bind application to Win95 activity, hr = %#08x\n", hr));
			goto LCleanup;
			}
        }

    // Create and init session manager
    m_pSessionMgr = new CSessionMgr;
    if (!m_pSessionMgr)
        goto LCleanupOOM;

    hr = m_pSessionMgr->Init(this);
    if (FAILED(hr)) {
		DBGWARN((DBG_CONTEXT, "New Application Failed: Could not Init session manager, hr = %#08x\n", hr));
		goto LCleanup;
		}

LCleanup:
    // Release interfaces
    if (pFileNode)
        pFileNode->Release();

    if (pVirtualServerRoot)
        pVirtualServerRoot->Release();

    if (SUCCEEDED(hr))
        m_fInited = TRUE;

    return hr;

LCleanupOOM:
	hr = E_OUTOFMEMORY;
	DBGERROR((DBG_CONTEXT, "New Application Failed: E_OUTOFMEMORY\n"));
	goto LCleanup;
    }

/*===================================================================
CAppln::InitServicesConfig

Initializes the Application scoped CServicesConfig object
===================================================================*/
HRESULT CAppln::InitServicesConfig()
{
    HRESULT                         hr;
    IServiceThreadPoolConfig        *pIThreadPool = NULL;
    IServiceSynchronizationConfig   *pISyncConfig = NULL;
    IServicePartitionConfig         *pIPartitionConfig = NULL;
    IServiceSxsConfig               *pISxsConfig = NULL;

	hr = CoCreateInstance(CLSID_CServiceConfig, 
                          NULL, 
                          CLSCTX_INPROC_SERVER, 
                          IID_IUnknown, 
                          (void **)&m_pServicesConfig); 
    if (FAILED(hr)) {
		DBGWARN((DBG_CONTEXT, "New Application Failed: InitServicesConfig() - Could not CCI ServicesConfig, hr = %#08x\n", hr));
		goto LCleanup;
    }

    hr = m_pServicesConfig->QueryInterface(IID_IServiceThreadPoolConfig, (void **)&pIThreadPool);
    if (FAILED(hr)) {
		DBGWARN((DBG_CONTEXT, "New Application Failed: InitServicesConfig() - Could not QI for IServiceThreadPoolConfig, hr = %#08x\n", hr));
		goto LCleanup;
    }

    hr = pIThreadPool->SelectThreadPool(m_pAppConfig->fExecuteInMTA() ? CSC_MTAThreadPool : CSC_STAThreadPool);
    if (FAILED(hr)) {
		DBGWARN((DBG_CONTEXT, "New Application Failed: InitServicesConfig() - Failed to SelectThreadPool, hr = %#08x\n", hr));
		goto LCleanup;
    }

    hr = m_pServicesConfig->QueryInterface(IID_IServiceSynchronizationConfig, (void **)&pISyncConfig);
    if (FAILED(hr)) {
		DBGWARN((DBG_CONTEXT, "New Application Failed: InitServicesConfig() - Could not QI for IServiceSynchronizationConfig, hr = %#08x\n", hr));
		goto LCleanup;
    }

    hr = pISyncConfig->ConfigureSynchronization (CSC_IfContainerIsSynchronized);
    if (FAILED(hr)) {
		DBGWARN((DBG_CONTEXT, "New Application Failed: InitServicesConfig() - Could not ConfigureSynchronization, hr = %#08x\n", hr));
		goto LCleanup;
    }

    if (m_pAppConfig->fUsePartition() && m_pAppConfig->PPartitionGUID()) {

        hr = m_pServicesConfig->QueryInterface(IID_IServicePartitionConfig, (void **)&pIPartitionConfig);
        if (FAILED(hr)) {
		    DBGWARN((DBG_CONTEXT, "New Application Failed: InitServicesConfig() - Could not QI for IServicePartitionConfig, hr = %#08x\n", hr));
		    goto LCleanup;
        }

        hr = pIPartitionConfig->PartitionConfig(CSC_NewPartition);
        if (FAILED(hr)) {
		    DBGWARN((DBG_CONTEXT, "New Application Failed: InitServicesConfig() - Could not set PartitionConfig, hr = %#08x\n", hr));
		    goto LCleanup;
        }

        hr = pIPartitionConfig->PartitionID(*m_pAppConfig->PPartitionGUID());
        if (FAILED(hr)) {
		    DBGWARN((DBG_CONTEXT, "New Application Failed: InitServicesConfig() - Could not set PartitionID, hr = %#08x\n", hr));
		    goto LCleanup;
        }
    }

    if (m_pAppConfig->fSxsEnabled()) {

        hr = m_pServicesConfig->QueryInterface(IID_IServiceSxsConfig, (void **)&pISxsConfig);
        if (FAILED(hr)) {
		    DBGWARN((DBG_CONTEXT, "New Application Failed: InitServicesConfig() - Could not QI for IServiceSxsConfig, hr = %#08x\n", hr));
		    goto LCleanup;
        }

        hr = pISxsConfig->SxsConfig(CSC_NewSxs);
        if (FAILED(hr)) {
		    DBGWARN((DBG_CONTEXT, "New Application Failed: InitServicesConfig() - Could not set SxsConfig, hr = %#08x\n", hr));
		    goto LCleanup;
        }

        LPWSTR  pwszApplnPath;
#if UNICODE
        pwszApplnPath = m_pszApplnPath;
#else
        CMBCSToWChar    convPath;

        hr = convPath.Init(m_pszApplnPath);
        if (FAILED(hr)) {
		    DBGWARN((DBG_CONTEXT, "New Application Failed: InitServicesConfig() - Could not convert ApplnPath to UNICODE, hr = %#08x\n", hr));
		    goto LCleanup;
        }

        pwszApplnPath = convPath.GetString();
#endif
        hr = pISxsConfig->SxsDirectory(pwszApplnPath);
        if (FAILED(hr)) {
		    DBGWARN((DBG_CONTEXT, "New Application Failed: InitServicesConfig() - Could not set SxsDirectory, hr = %#08x\n", hr));
		    goto LCleanup;
        }

        if (m_pAppConfig->szSxsName() && *m_pAppConfig->szSxsName()) {
            CMBCSToWChar    convName;

            hr = convName.Init(m_pAppConfig->szSxsName());
            if (FAILED(hr)) {
    		    DBGWARN((DBG_CONTEXT, "New Application Failed: InitServicesConfig() - Could not convert SxsName to UNICODE, hr = %#08x\n", hr));
	    	    goto LCleanup;
            }
            
            hr = pISxsConfig->SxsName(convName.GetString());
            if (FAILED(hr)) {
		        DBGWARN((DBG_CONTEXT, "New Application Failed: InitServicesConfig() - Could not set SxsName, hr = %#08x\n", hr));
		        goto LCleanup;
            }
        }
    }

LCleanup:

    if (pIThreadPool)
        pIThreadPool->Release();
           
    if (pISyncConfig)
        pISyncConfig->Release();
           
    if (pIPartitionConfig)
        pIPartitionConfig->Release();
           
    if (pISxsConfig)
        pISxsConfig->Release();

    return hr;
}

/*===================================================================
CAppln::Restart

Restart an application. (used for when global.asa changes or
debugging enable metabase key changes)
===================================================================*/
HRESULT CAppln::Restart(BOOL  fForceRestart /* = FALSE*/)
    {
    AddRef();  // keep addref'd while restarting

    DBGPRINTF((DBG_CONTEXT, "Restarting  application %S, %p\n", m_pszMetabaseKey, this));
    
    g_ApplnMgr.Lock();

    // If   already restarted or
    //      in the tombstone state or
    //      restart not allowed
    //      shutting down -> don't restart
    if (m_fGlobalChanged || 
        m_fTombstone || 
        (!m_pAppConfig->fEnableApplicationRestart() && !fForceRestart) ||
        IsShutDownInProgress())
        {
        // Give back the lock and refcount
        // since we don't need them
        g_ApplnMgr.UnLock();
        Release();
        return S_OK;
        }

    // remove the application from the global hash

    CLinkElem *pLinkElem = g_ApplnMgr.DeleteElem
        (
        m_pszMetabaseKey,
        _tcslen(m_pszMetabaseKey) * sizeof(TCHAR)
        );
    Assert(pLinkElem);
    Assert(static_cast<CAppln *>(pLinkElem) == this);

    // Unlock
    g_ApplnMgr.UnLock();

    // Indicate to Delete All Sessions
    m_fGlobalChanged = TRUE;

    // Increment the count of restarting applications
    InterlockedIncrement((LPLONG)&g_nApplicationsRestarting);

    // Increment the count of restarted applications
    InterlockedIncrement((LPLONG)&g_nApplicationsRestarted);

    m_pSessionMgr->UnScheduleSessionKiller();

    // cleanup the directory monitor entries

    if (FIsWinNT())
    {
        Lock(); // Place critical Section around access to m_rgpvDME
        while ((m_rgpvDME).Count())
        {
            static_cast<CDirMonitorEntry *>(m_rgpvDME[0])->Release();
            (m_rgpvDME).Remove(0);
        }
        m_rgpvDME.Clear();
        UnLock();
    }

    // add this application to the CleanupManager...
    g_ApplnCleanupMgr.AddAppln(this);

    return S_OK;
}

/*===================================================================
CAppln::ApplnCleanupProc

Called by the g_ApplnCleanupMgr thread to complete cleanup

===================================================================*/
DWORD __stdcall CAppln::ApplnCleanupProc(VOID  *pArg)
{
    CAppln *pAppln = (CAppln *)pArg;

    DBGPRINTF((DBG_CONTEXT, "[ApplnCleanupProc] enterred for %S, %p\n", pAppln->m_pszMetabaseKey, pArg));
    
    // Let the requests to drain while trying to delete sessions
    while (!IsShutDownInProgress() && (pAppln->m_cRequests || pAppln->m_cSessions))
        {
        if (pAppln->m_cSessions)
            pAppln->m_pSessionMgr->DeleteAllSessions(FALSE);

        if (pAppln->m_cSessions || pAppln->m_cRequests)
            Sleep(200);
        }

    g_ApplnMgr.DeleteApplicationIfExpired(pAppln);

    // Decrement the count of restarting applications
    InterlockedDecrement((LPLONG)&g_nApplicationsRestarting);

    // Decrement the count of restart threads
    InterlockedDecrement(&g_nAppRestartThreads);

    // tell the cleanup manager to check for more work
    g_ApplnCleanupMgr.Wakeup();

    pAppln->Release();

    return 0;
}

/*===================================================================
CAppln::UnInit

Convert to tombstone state

Parameters:
    NONE

Returns:
    HRESULT (S_OK)
===================================================================*/
HRESULT CAppln::UnInit()
    {
    Assert(!m_fTombstone);  // don't do it twice

#ifdef DBG_NOTIFICATION
#if UNICODE
    DBGPRINTF((DBG_CONTEXT, "Uniniting  application %S, %p\n", m_pszApplnPath, this));
#else
    DBGPRINTF((DBG_CONTEXT, "Uniniting  application %s, %p\n", m_pszApplnPath, this));
#endif
#endif // DBG_NOTIFICATION

    // Flush the global.asa from the script engine cache
    if (m_pszGlobalAsa)
        {
        g_ScriptManager.FlushCache(m_pszGlobalAsa);
        }

    if (m_pGlobalTemplate)
        {
        // Keep template (and inc file) cache locked while releasing
        // GLOBAL.ASA template so that it wouldn't step onto Flush logic
        //
        // NOTE: CTemplate::End potentially queues global.asa for cleanup on
        //       our thread!  CleanupEngines() must therefore be called
        //       *after* this step.
        //
        LockTemplateAndIncFileCaches();
        m_pGlobalTemplate->End();
        UnLockTemplateAndIncFileCaches();

        m_pGlobalTemplate = NULL;
        }

    //If NT, remove this app from any file/appln mappings it may be in
    if (FIsWinNT())
        {
        g_FileAppMap.Lock();
        int i = m_rgpvFileAppln.Count();
        while (i > 0)
            {

            #ifdef DBG_NOTIFICATION
            DBGPRINTF((DBG_CONTEXT, "Removing application from File/App mapping\n"));
            #endif // DBG_NOTIFICATION

            static_cast<CFileApplnList *>(m_rgpvFileAppln[0])->RemoveApplication(this);
            m_rgpvFileAppln.Remove(0);
            i--;
            }
        g_FileAppMap.UnLock();
        m_rgpvFileAppln.Clear();

        Lock(); // Protecting m_rqpvDME with a critical section
        m_rgpvDME.Clear();
        UnLock();

        // If debuggable application, clean up pending scripts
        if (m_fDebuggable)
            g_ApplnMgr.CleanupEngines();
        }

    // Free the properties collection
    if (m_pProperties)
        {
        m_pProperties->UnInit();
        m_pProperties->Release();
        m_pProperties = NULL;
        }

    // Free the tagged objects collection
    if (m_pTaggedObjects)
        {
        m_pTaggedObjects->UnInit();
        m_pTaggedObjects->Release();
        m_pTaggedObjects = NULL;
        }

    // Before we close down, debuggable templates need to be made non-debuggable
    if (m_fDebuggable)
        g_TemplateCache.RemoveApplicationFromDebuggerUI(this);

    if (m_pAppRoot)
        {
        m_pAppRoot->Detach();
        m_pAppRoot->Close();
        m_pAppRoot->Release();
        m_pAppRoot = NULL;
        }

    if (m_pApplCompCol)
        {
        delete m_pApplCompCol;
        m_pApplCompCol = NULL;
        }

    if (m_pActivity)
        {
        delete m_pActivity;
        m_pActivity = NULL;
        }

    if (m_pSessionMgr)
        {
        delete m_pSessionMgr;
        m_pSessionMgr = NULL;
        }

    if (m_pAppConfig)
        {
        /*
         * BUG 89144: Uninit AppConfig but do it from the MTA
         * When AppConfig is inited, it is done on a WAM thread.  WAM
         * threads are MTA threads.  At that time we register an event
         * sink to get Metabase change notifications.  Now, during shutdown,
         * we are running on an ASP worker thread, which is an STA thread.
         * That means we will get an RPC_E_WRONGTHREAD error shutting down.  The
         * fix is to make the uninit call happen on an MTA thread.
         */
        HRESULT hr;
        HRESULT AppConfigUnInit(void *pV1, void *pV2);

		HANDLE hCompletionEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
        hr = CallMTACallback
            (
            AppConfigUnInit,
            m_pAppConfig,
            hCompletionEvent
            );
        Assert(SUCCEEDED(hr));
		WaitForSingleObject(hCompletionEvent, INFINITE);

		CloseHandle(hCompletionEvent);
        delete m_pAppConfig;
        m_pAppConfig = NULL;
        }

    if (m_pdispGlobTypeLibWrapper)
        {
        m_pdispGlobTypeLibWrapper->Release();
        m_pdispGlobTypeLibWrapper = NULL;
        }

    if (m_pszGlobalAsa)
        {
        // If there was a change notification on global.asa
        // then flush the template now.
        // UNDONE: flush correct global.asa
        if (m_fGlobalChanged)
            g_TemplateCache.Flush(m_pszGlobalAsa, MATCH_ALL_INSTANCE_IDS);

        delete [] m_pszGlobalAsa;
        m_pszGlobalAsa = NULL;
        }

    if (m_pszMetabaseKey)
        {
        delete [] m_pszMetabaseKey;
        m_pszMetabaseKey = NULL;
        }

    if (m_pszApplnPath)
        {
        delete [] m_pszApplnPath;
        m_pszApplnPath = NULL;
        }

    if (m_pszApplnVRoot)
        {
        delete [] m_pszApplnVRoot;
        m_pszApplnVRoot = NULL;
        }

    if (FIsWinNT() && m_fUseImpersonationHandle)
        {
        CloseHandle(m_hUserImpersonation);
        m_hUserImpersonation = NULL;
        }

    if (m_fInited)
        {
        DeleteCriticalSection(&m_csInternalLock);
        DeleteCriticalSection(&m_csApplnLock);
        }

    if (m_pServicesConfig)
        m_pServicesConfig->Release();

    // Mark this application as Tombstone

    m_fTombstone = TRUE;

    InterlockedDecrement((LPLONG)&g_nApplications);

	// Disconnennect from proxies (in case we are shutting down or will shortly shut down)

	CoDisconnectObject(static_cast<IApplicationObject *>(this), 0);

    return S_OK;
    }

/*===================================================================
CAppln::BindToActivity

Creates application level activity either
    as a clone of the given activity or
    as a brand new activity

Must be called within critical section. Does not lock itself.

Parameters:
    CViperActivity *pActivity       activity to clone (could be NULL)

Returns:
    NONE
===================================================================*/
HRESULT CAppln::BindToActivity
(
CViperActivity *pActivity
)
    {
    if (m_pActivity)
        {
        // multiple requests to bind to new activity are ok
        if (!pActivity)
            return S_OK;
        // but not to clone from an existing activity
        Assert(FALSE);
        return E_FAIL;
        }

    m_pActivity = new CViperActivity;
    if (!m_pActivity)
        return E_OUTOFMEMORY;

    HRESULT hr;

    if (pActivity)
        hr = m_pActivity->InitClone(pActivity);
    else
        hr = m_pActivity->Init(m_pServicesConfig);

    if (FAILED(hr))
        {
        delete m_pActivity;
        m_pActivity = NULL;
        }

    return hr;
    }

/*===================================================================
CAppln::SetGlobalAsa

Remembers GLOBAL.ASA file path for this application

Parameters:
    const char *pszGlobalAsa    path to (copy and) remember

Returns:
    HRESULT
===================================================================*/
HRESULT CAppln::SetGlobalAsa
(
const TCHAR *pszGlobalAsa
)
    {
    // remove existing
    if (m_pszGlobalAsa)
        {
        delete [] m_pszGlobalAsa;
        m_pszGlobalAsa = NULL;
        }

    // store new
    if (pszGlobalAsa)
        {
        DWORD cch = _tcslen(pszGlobalAsa);
        DWORD cb = (cch + 1) * sizeof(TCHAR);

        m_pszGlobalAsa = new TCHAR[cch+1];
        if (!m_pszGlobalAsa)
            return E_OUTOFMEMORY;

        memcpy(m_pszGlobalAsa, pszGlobalAsa, cb);
        }

    return S_OK;
    }

/*===================================================================
CAppln::AddDirMonitorEntry

Remembers change notifcation monitor entries for this application

Parameters:
    pDirMonitorEntry    pointer to DME

Returns:
    S_OK if the monitor entry was added to the list
===================================================================*/
HRESULT CAppln::AddDirMonitorEntry(CDirMonitorEntry *pDirMonitorEntry)
    {
    DBG_ASSERT(m_fInited);
    DBG_ASSERT(pDirMonitorEntry);

    HRESULT hr = S_OK;

   // Add the DME to the list
   Lock(); // Protect m_rgpvDME by critical section
   if (FAILED(hr = m_rgpvDME.Append(pDirMonitorEntry)))
        {
        pDirMonitorEntry->Release();
        }
   UnLock();
    return hr;

    }

/*===================================================================
CAppln::AddFileApplnEntry

Remembers change notifcation monitor entries for this application

Parameters:
    pFileAppln    pointer to FileApplnEntry

Returns:
    S_OK if the monitor entry was added to the list
    S_FALSE if the monitor entry was alread in the list
===================================================================*/
HRESULT CAppln::AddFileApplnEntry(CFileApplnList *pFileAppln)
    {
    DBG_ASSERT(m_fInited);
    DBG_ASSERT(pFileAppln);

    HRESULT hr = S_OK;
    int index;

    // See if the file/application entry is alreay in the list
    hr = m_rgpvFileAppln.Find(pFileAppln, &index);
    if (hr == S_FALSE)
        {
       // Add the file/application entry to the list
       hr = m_rgpvFileAppln.Append(pFileAppln);
        }
    else
        {
        // The file/application entry was already in the list
        hr = S_FALSE;
        }
    return hr;

    }

/*===================================================================
CAppln::QueryInterface

QueryInterface() -- IApplicationObject implementation.

Parameters:
    REFIID riid
    void **ppv

Returns:
    HRESULT
===================================================================*/
STDMETHODIMP CAppln::QueryInterface
(
REFIID riid,
void **ppv
)
    {
    *ppv = NULL;

    if (IID_IUnknown           == riid ||
        IID_IDispatch          == riid ||
        IID_IApplicationObject == riid ||
        IID_IDenaliIntrinsic   == riid)
        {
        *ppv = static_cast<IApplicationObject *>(this);
        }
    else if (IID_ISupportErrorInfo == riid)
        {
        *ppv = &m_ISuppErrImp;
        }
    else if (IID_IMarshal == riid)
        {
        *ppv = static_cast<IMarshal *>(this); 
        }
    if (*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return S_OK;
        }

    return E_NOINTERFACE;
    }

/*===================================================================
CAppln::AddRef

AddRef() -- IUnknown implementation.

Parameters:

Returns:
    Ref count
===================================================================*/
STDMETHODIMP_(ULONG) CAppln::AddRef() {
    DWORD cRefs = InterlockedIncrement((LPLONG)&m_cRefs);
    IF_DEBUG(APPLICATION) {
		WriteRefTraceLog(gm_pTraceLog, cRefs, this);
    }
    return cRefs;
}

/*===================================================================
CAppln::Release

Release() -- IUnknown implementation.

Parameters:

Returns:
    Ref count
===================================================================*/
STDMETHODIMP_(ULONG) CAppln::Release()
    {
    DWORD cRefs = InterlockedDecrement((LPLONG)&m_cRefs);
    IF_DEBUG(APPLICATION) {
		WriteRefTraceLog(gm_pTraceLog, cRefs, this);
    }
    if (cRefs)
        return cRefs;

    delete this;
    return 0;
    }

/*===================================================================
CAppln::CheckForTombstone

Tombstone stub for IApplicationObject methods. If the object is
tombstone, does ExceptionId and fails.

Parameters:

Returns:
    HRESULT     E_FAIL  if Tombstone
                S_OK if not
===================================================================*/
HRESULT CAppln::CheckForTombstone()
    {
    if (!m_fTombstone)
        return S_OK;

    ExceptionId
        (
        IID_IApplicationObject,
        IDE_APPLICATION,
        IDE_INTRINSIC_OUT_OF_SCOPE
        );
    return E_FAIL;
    }
/*===================================================================
CAppln::Lock

IApplicationObject method.

Will allow the user to lock the application intrinsic for the
purpose of adding/deleting values.

Parameters:
    NONE

Returns:
    HRESULT        S_OK on success
                E_FAIL otherwise
===================================================================*/
STDMETHODIMP CAppln::Lock()
    {
    if (FAILED(CheckForTombstone()))
        return E_FAIL;

    Assert(m_fInited);

    DWORD dwId = GetCurrentThreadId();

    // If this thread already has the lock, increment lock ref count

    if (m_dwLockThreadID == dwId)
        {
        m_cLockRefCount++;
        }
    else
        {
        EnterCriticalSection(&m_csApplnLock);
        m_cLockRefCount = 1;
        m_dwLockThreadID = dwId;
        }

    return S_OK;
    }

/*===================================================================
CAppln::UnLock

IApplicationObject method.

Will allow the user to unlock the application intrinsic only
if it has been locked by this user.

Parameters:
    NONE

Returns:
    HRESULT        S_OK
===================================================================*/
STDMETHODIMP CAppln::UnLock()
    {
    if (FAILED(CheckForTombstone()))
        return E_FAIL;

    if (m_dwLockThreadID == GetCurrentThreadId())
        {
        if (--m_cLockRefCount == 0)
            {
            // Unlock the application
            m_dwLockThreadID = INVALID_THREADID;
            LeaveCriticalSection(&m_csApplnLock);
            }
        }

    return S_OK;
    }

/*===================================================================
CAppln::UnLockAfterRequest

Remove any application locks left by the user script

Parameters:
    NONE

Returns:
    HRESULT        S_OK
===================================================================*/
HRESULT CAppln::UnLockAfterRequest()
    {
    Assert(!m_fTombstone);

    if (m_cLockRefCount > 0 && m_dwLockThreadID == GetCurrentThreadId())
        {
        m_cLockRefCount = 0;
        m_dwLockThreadID = INVALID_THREADID;
        LeaveCriticalSection(&m_csApplnLock);
        }
    return S_OK;
    }

/*===================================================================
CAppln::get_Value

IApplicationObject method.

Will allow the user to retreive a application state variable,
the variable will come as a named pair, bstr is the name and
var is the value or object to be returned for that name

Parameters:
    BSTR FAR *     bstrName    Name of the variable to get
    VARIANT *    pVar         Value/object to get for the variable

Returns:
    HRESULT        S_OK on success
===================================================================*/
STDMETHODIMP CAppln::get_Value
(
BSTR bstrName,
VARIANT *pVar
)
    {
    if (FAILED(CheckForTombstone()))
        return E_FAIL;

    if (bstrName == NULL)
        {
        ExceptionId(IID_IApplicationObject,
                    IDE_APPLICATION, IDE_EXPECTING_STR);
        return E_FAIL;
        }

    VariantInit(pVar); // default variant empty

    WCHAR *pwszName;
    STACK_BUFFER(rgbName, 42);
    WSTR_STACK_DUP(bstrName, &rgbName, &pwszName);

    if (pwszName == NULL)
        return S_OK; // no name - no value - no error
    //_wcsupr(pwszName);

    Assert(m_pApplCompCol);

    HRESULT           hr   = S_OK;
    CComponentObject *pObj = NULL;

    // Lock the application
    Lock();

    hr = m_pApplCompCol->GetProperty(pwszName, &pObj);

    if (SUCCEEDED(hr))
        {
        Assert(pObj);
        hr = pObj->GetVariant(pVar);
        }

    // UnLock the application
    UnLock();

    return S_OK;
    }

/*===================================================================
CAppln::putref_Value

IApplicationObject method.

Will allow the user to assign a application state variable to be saved
the variable will come as a named pair, bstr is the name and
var is the value or object to be stored for that name

Parameters:
    BSTR     bstrName    Name of the variable to set
    VARIANT Var            Value/object to set for the variable

Returns:
    HRESULT        S_OK on success
===================================================================*/
STDMETHODIMP CAppln::putref_Value
(
BSTR bstrName,
VARIANT Var
)
    {
    if (FAILED(CheckForTombstone()))
        return E_FAIL;

    if (FIsIntrinsic(&Var))
        {
        ExceptionId(IID_IApplicationObject, IDE_APPLICATION,
                    IDE_APPLICATION_CANT_STORE_INTRINSIC);
        return E_FAIL;
        }

    if (bstrName == NULL)
        {
        ExceptionId(IID_IApplicationObject,
                    IDE_APPLICATION, IDE_EXPECTING_STR);
        return E_FAIL;
        }

    HRESULT hr;

    Assert(m_pApplCompCol);

    // Prepare property name
    WCHAR *pwszName;
    STACK_BUFFER(rgbName, 42);
    WSTR_STACK_DUP(bstrName, &rgbName, &pwszName);

    if (pwszName == NULL)
        {
        ExceptionId(IID_IApplicationObject,
                    IDE_APPLICATION, IDE_EXPECTING_STR);
        return E_FAIL;
        }
    //_wcsupr(pwszName);

    // Lock the application
    Lock();

    hr = m_pApplCompCol->AddProperty(pwszName, &Var);

    // Unlock the application
    UnLock();

    if (hr == RPC_E_WRONG_THREAD)
        {
        // We use RPC_E_WRONG_THREAD to indicate bad model object
        ExceptionId(IID_IApplicationObject,
                    IDE_APPLICATION, IDE_APPLICATION_CANT_STORE_OBJECT);
         hr = E_FAIL;
        }

    return hr;
    }

/*===================================================================
CAppln::put_Value

IApplicationObject method.

Implement property put by dereferencing variants before
calling putref.

Parameters:
    BSTR FAR *     bstrName    Name of the variable to set
    VARIANT     Var            Value/object to set for the variable

Returns:
    HRESULT        S_OK on success
===================================================================*/
STDMETHODIMP CAppln::put_Value
(
BSTR bstrName,
VARIANT Var
)
    {
    if (FAILED(CheckForTombstone()))
        return E_FAIL;

    if (bstrName == NULL)
        {
        ExceptionId(IID_IApplicationObject,
                    IDE_APPLICATION, IDE_EXPECTING_STR);
        return E_FAIL;
        }

    HRESULT hr;

    Assert(m_pApplCompCol);

    // Prepare property name
    WCHAR *pwszName;
    STACK_BUFFER(rgbName, 42);
    WSTR_STACK_DUP(bstrName, &rgbName, &pwszName);

    if (pwszName == NULL)
        {
        ExceptionId(IID_IApplicationObject,
                    IDE_APPLICATION, IDE_EXPECTING_STR);
        return E_FAIL;
        }
    //_wcsupr(pwszName);

    // Lock the application
    Lock();

    VARIANT varResolved;
    hr = VariantResolveDispatch(&varResolved, &Var,
                                IID_IApplicationObject,
                                IDE_APPLICATION);
    if (SUCCEEDED(hr))
        {
        hr = m_pApplCompCol->AddProperty(pwszName, &varResolved);
        VariantClear(&varResolved);
        }

    // Unlock the application
    UnLock();

    if (hr == RPC_E_WRONG_THREAD)
        {
        // We use RPC_E_WRONG_THREAD to indicate bad model object
        ExceptionId(IID_IApplicationObject,
                    IDE_APPLICATION, IDE_APPLICATION_CANT_STORE_OBJECT);
         hr = E_FAIL;
        }

    return hr;
    }

/*===================================================================
CAppln::get_Contents

Return the application contents dictionary
===================================================================*/

STDMETHODIMP CAppln::get_Contents(IVariantDictionary **ppDictReturn)
    {
    if (FAILED(CheckForTombstone()) || !m_pProperties)
        return E_FAIL;

    return m_pProperties->QueryInterface(IID_IVariantDictionary, reinterpret_cast<void **>(ppDictReturn));
    }

/*===================================================================
CAppln::get_StaticObjects

Return the application static objects dictionary
===================================================================*/
STDMETHODIMP CAppln::get_StaticObjects(IVariantDictionary **ppDictReturn)
    {
    if (FAILED(CheckForTombstone()) || !m_pTaggedObjects)
        return E_FAIL;

    return m_pTaggedObjects->QueryInterface(IID_IVariantDictionary, reinterpret_cast<void **>(ppDictReturn));
    }

/*===================================================================
CAppln::UpdateConfig

Updates configuration from metabase if needed
===================================================================*/
HRESULT CAppln::UpdateConfig(CIsapiReqInfo  *pIReq, BOOL *pfRestart, BOOL *pfFlushAll)
    {
    BOOL fRestart = FALSE;
    BOOL fFlushAll = FALSE;

    if (m_pAppConfig->fNeedUpdate())
        {
        InternalLock();

        if (m_pAppConfig->fNeedUpdate()) // still need update?
            {
            BOOL fAllowedDebugging   = m_pAppConfig->fAllowDebugging();
            BOOL fAllowedClientDebug = m_pAppConfig->fAllowClientDebug();
            BOOL fAllowedRestart     = m_pAppConfig->fEnableApplicationRestart();
            BOOL fParentPathsEnabled = m_pAppConfig->fEnableParentPaths();
            UINT uLastCodePage       = m_pAppConfig->uCodePage();
            LCID uLastLCID           = m_pAppConfig->uLCID();
            BOOL fPrevSxsEnabled     = m_pAppConfig->fSxsEnabled();
            BOOL fPrevUsePartition   = m_pAppConfig->fUsePartition();
            BOOL fPrevUseTracker     = m_pAppConfig->fTrackerEnabled();

            BOOL fRestartEnabledUpdated = m_pAppConfig->fRestartEnabledUpdated();
			char szLastDefaultEngine[64];
			strncpy(szLastDefaultEngine, m_pAppConfig->szScriptLanguage(), sizeof szLastDefaultEngine);
			szLastDefaultEngine[sizeof(szLastDefaultEngine) - 1] = '\0';

            m_pAppConfig->Update(pIReq);

            BOOL fAllowDebugging     = m_pAppConfig->fAllowDebugging();
            BOOL fAllowClientDebug   = m_pAppConfig->fAllowClientDebug();
            BOOL fAllowRestart       = m_pAppConfig->fEnableApplicationRestart();
            BOOL fEnableParentPaths  = m_pAppConfig->fEnableParentPaths();
            UINT uCodePage           = m_pAppConfig->uCodePage();
            LCID uLCID               = m_pAppConfig->uLCID();
            BOOL fCurSxsEnabled      = m_pAppConfig->fSxsEnabled();
            BOOL fCurUsePartition    = m_pAppConfig->fUsePartition();
            BOOL fCurUseTracker      = m_pAppConfig->fTrackerEnabled();

			const char *szNewDefaultEngine = m_pAppConfig->szScriptLanguage();

            fFlushAll = strcmpi(szLastDefaultEngine, szNewDefaultEngine) != 0
                        || (fParentPathsEnabled != fEnableParentPaths)
                        || (uLastCodePage != uCodePage)
                        || (uLastLCID != uLCID);
            
            fRestart = (fAllowDebugging != fAllowedDebugging) ||
                       (fAllowClientDebug != fAllowedClientDebug) ||
                       ((fAllowRestart  != fAllowedRestart) && fAllowRestart) ||
                       ((fAllowRestart == fAllowedRestart) && fRestartEnabledUpdated) ||
                       (fCurSxsEnabled != fPrevSxsEnabled) ||
                       (fCurUsePartition != fPrevUsePartition) ||
                       (fCurUseTracker != fPrevUseTracker) ||
                       fFlushAll;
            }

        InternalUnLock();
        }

    if (pfRestart)
        *pfRestart = fRestart;

    if (pfFlushAll)
        *pfFlushAll = fFlushAll;

    return S_OK;
    }

/*===================================================================
CAppln::FPathMonitored()

Checks the list of DMEs in application to see if the specified path
is already being monitored.

===================================================================*/
CASPDirMonitorEntry  *CAppln::FPathMonitored(LPCTSTR  pszPath)
{
    int i;

    Lock(); // Protect m_rqpvDME by a critical section
    int cDMEs = m_rgpvDME.Count();
    for (i=0; i < cDMEs; i++) {
        CASPDirMonitorEntry  *pDME = static_cast<CASPDirMonitorEntry *>(m_rgpvDME[i]);
        if (pDME == NULL)
            break;
        if (pDME->FPathMonitored(pszPath))
        {
            UnLock();
            return pDME;
        }
    }

    UnLock();
    return NULL;
}

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

Test to make sure that the CAppln object is currently correctly
formed and assert if it is not.

Returns:
    Nothing

Side effects:
    None.
===================================================================*/
void CAppln::AssertValid() const
    {
    Assert(m_fInited);

    Assert(m_pSessionMgr);

    Assert(m_pApplCompCol);
    m_pApplCompCol->AssertValid();
    }
#endif // DBG


/*===================================================================
  C  A p p l n  M g r
===================================================================*/

/*===================================================================
CApplnMgr::CApplnMgr

Application Manager constructor.

Parameters:
    NONE

Returns:
    NONE
===================================================================*/
CApplnMgr::CApplnMgr()
    : m_fInited(FALSE),
      m_fHashTableInited(FALSE), m_fCriticalSectionInited(FALSE)
    {
    }

/*===================================================================
CApplnMgr::~CApplnMgr

Application Manager destructor.

Parameters:
    NONE

Returns:
    NONE
===================================================================*/
CApplnMgr::~CApplnMgr()
    {
    if (!m_fInited)
        UnInit();
    }

/*===================================================================
HRESULT CApplnMgr::Init

Initializes the Appln Manager.

Parameters:
    NONE

Returns:
    S_OK            Success
    E_FAIL            Failure
    E_OUTOFMEMORY    Out of memory
===================================================================*/
HRESULT CApplnMgr::Init( void )
    {
    HRESULT hr = S_OK;

    Assert(!m_fInited);

    // Init hash table

    hr = CHashTable::Init(NUM_APPLMGR_HASHING_BUCKETS);
    if (FAILED(hr))
        return hr;
    m_fHashTableInited = TRUE;

    // Init critical section

    ErrInitCriticalSection(&m_csLock, hr);
    if (FAILED(hr))
        return(hr);
    m_fCriticalSectionInited = TRUE;

    m_fInited = TRUE;

    return g_ApplnCleanupMgr.Init();
    }

/*===================================================================
HRESULT CApplnMgr::UnInit

UnInitializes the Appln Manager.

Parameters:
    NONE

Returns:
    S_OK        Success
    E_FAIL        Failure
===================================================================*/
HRESULT CApplnMgr::UnInit( void )
    {
    if (m_fHashTableInited)
        {
        CHashTable::UnInit();
        m_fHashTableInited = FALSE;
        }

    if (m_fCriticalSectionInited)
        {
        DeleteCriticalSection(&m_csLock);
        m_fCriticalSectionInited = FALSE;
        }

    m_fInited = FALSE;
    return g_ApplnCleanupMgr.UnInit();
    }

/*===================================================================
CApplnMgr::AddAppln

Adds a CAppln element to link list / hash table.
User has to check if Appln already exists before calling this.
Critical sectioning is in CHitObj::BrowserRequestInit().

Parameters:
    char   *pszApplnKey         Application metabase key
    char   *pszApplnPath        Application directory path
    CIsapiReqInfo   *pIReq
    HANDLE  hUserImpersonation  impersonation handle

    CAppln **ppAppln            [out] Application created

Returns:
    HRESULT
===================================================================*/
HRESULT CApplnMgr::AddAppln
(
TCHAR    *pszApplnKey,
TCHAR    *pszApplnPath,
CIsapiReqInfo   *pIReq,
HANDLE   hUserImpersonation,
CAppln **ppAppln
)
    {
    *ppAppln = NULL;   // return NULL if failed

    // Create CAppln object

    CAppln *pAppln = new CAppln;

    if (!pAppln)
        return E_OUTOFMEMORY;

    // Init CAppln object

    HRESULT hr;

    hr = pAppln->Init
        (
        pszApplnKey,
        pszApplnPath,
        pIReq,
        hUserImpersonation
        );

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

    // Add to hash table

    if (!CHashTable::AddElem(pAppln))
        {
        pAppln->UnInit();
        pAppln->Release();
        return E_FAIL;
        }

    *ppAppln = pAppln;
    return S_OK;
    }

/*===================================================================
CApplnMgr::FindAppln

Finds CAppln in hash table
Critical sectioning must be done outside

Parameters:
    char   *pszApplnKey         Application metabase key
    CAppln **ppAppln            [out] Application found

Returns:
    S_OK            if found
    S_FALSE         if not found
===================================================================*/
HRESULT CApplnMgr::FindAppln
(
TCHAR *pszApplnKey,
CAppln **ppAppln
)
    {
    CLinkElem *pLinkElem = CHashTable::FindElem
        (
        pszApplnKey,
        _tcslen(pszApplnKey)*sizeof(TCHAR)
        );

    if (!pLinkElem)
        {
        *ppAppln = NULL;
        return S_FALSE;
        }

    *ppAppln = static_cast<CAppln *>(pLinkElem);
    return S_OK;
    }

/*===================================================================
CApplnMgr::AddEngine

When a change notification occurs for a file being debugged,
we need to delete its associated scripting engine.  The naive
approach of Releasing the engine during notification won't work
because the engine is on the wrong thread.  Instead of marshaling
to the thread (which raises possibilities of deadlock or starving
the notification thread if debugging is happening on the debug
thread), the engines are added to a queue in the application.
When a request is serviced for debugging (which is now in the
correct thread context), the application object first flushes
this list by releasing the engines
===================================================================*/
HRESULT CApplnMgr::AddEngine(CActiveScriptEngine *pEngine)
    {
    CScriptEngineCleanupElem *pScriptElem = new CScriptEngineCleanupElem(pEngine);
    if (pScriptElem == NULL)
        return E_OUTOFMEMORY;

    pScriptElem->AppendTo(m_listEngineCleanup);
    return S_OK;
    }

/*===================================================================
CApplnMgr::CleanupEngines()

Call Release all engine cleanup list.
===================================================================*/
void CApplnMgr::CleanupEngines()
    {
    while (! m_listEngineCleanup.FIsEmpty())
        delete m_listEngineCleanup.PNext();
    }


/*===================================================================
CApplnMgr::DeleteApplicationIfExpired

Removes CAppln object if exprired
Critical sectioning must be done outside

Parameters:
    CAppln *pAppln      application to delete

Returns:
    NONE
===================================================================*/
HRESULT CApplnMgr::DeleteApplicationIfExpired
(
CAppln *pAppln
)
    {
    if (!pAppln->m_fGlobalChanged)
        return S_OK;

    if (pAppln->m_cSessions || pAppln->m_cRequests)
        return S_OK;

    if (pAppln->m_fDeleteInProgress)
        return S_OK;

    pAppln->m_fDeleteInProgress = TRUE;

    HRESULT hr = S_OK;

    // Queue it up for deletion
    CHitObj *pHitObj = new CHitObj;
    if (!pHitObj)
        hr = E_OUTOFMEMORY;

    if (SUCCEEDED(hr))
        {
        pHitObj->ApplicationCleanupInit(pAppln);

        // Ask Viper to queue this request
        hr = pHitObj->PostViperAsyncCall();
        }

    // cleanup
    if (FAILED(hr) && pHitObj)
        delete pHitObj;

    return hr;
    }

/*===================================================================
CApplnMgr::DeleteAllApplications

Removes CAppln objects from the application manager link list
and hash table.

Parameters:

Returns:
    HRESULT
===================================================================*/
HRESULT CApplnMgr::DeleteAllApplications()
    {
    HRESULT hr = S_OK;

    Lock();

    CLinkElem *pLink = CHashTable::Head();
    CHashTable::ReInit();

    while (pLink)
        {
        CAppln *pAppln = static_cast<CAppln *>(pLink);
        pLink = pLink->m_pNext;

        if (pAppln->m_fDeleteInProgress)
            continue;

        pAppln->m_fDeleteInProgress = TRUE;


        // Queue it up for deletion
        CHitObj *pHitObj = new CHitObj;
        if (!pHitObj)
            {
            hr = E_OUTOFMEMORY;
            break;
            }

        // If NT, Unregister for notifications
        if (FIsWinNT())
            {
            while ((pAppln->m_rgpvDME).Count())
                {
                static_cast<CDirMonitorEntry *>(pAppln->m_rgpvDME[0])->Release();
                (pAppln->m_rgpvDME).Remove(0);
                }
            pAppln->m_rgpvDME.Clear();
            }

        pHitObj->ApplicationCleanupInit(pAppln);

        // Ask Viper to queue this request
        hr = pHitObj->PostViperAsyncCall();
        if (FAILED(hr))
            {
            delete pHitObj;
            break;
            }

        }

    UnLock();
    return hr;
    }

/*===================================================================
CApplnMgr::RestartAllChagnedApplications

Restarts CAppln objects from the application manager link list
We walk the list recording which applications are dependent
on files that have changed since they were compiled. Once we
have the list, we restart each of the applications.

This is a fall back when we may have missed a change notification,
for instance when we had insufficient buffer to record all the changes
that occured.

Parameters:

Returns:
    HRESULT
===================================================================*/
HRESULT CApplnMgr::RestartApplications(BOOL fRestartAllApplications)
    {
    HRESULT hr = S_OK;

    Lock();

    CLinkElem *pLink = CHashTable::Head();

    // Find out which applications need restarting

    while (pLink)
    {
        CAppln *pAppln = static_cast<CAppln *>(pLink);
        pLink = pLink->m_pNext;
        if (!pAppln->FTombstone() && (fRestartAllApplications || (pAppln->m_pGlobalTemplate != NULL && pAppln->m_pGlobalTemplate->FTemplateObsolete())))
        {
            pAppln->Restart();
        }
    }

    UnLock();

    return hr;
}

/*===================================================================
  C  A p p l n  C l e a n u p  M g r
===================================================================*/

/*===================================================================
CApplnMgr::CApplnCleanupMgr

Application Cleanup Manager constructor.

Parameters:
    NONE

Returns:
    NONE
===================================================================*/
CApplnCleanupMgr::CApplnCleanupMgr()
    : m_fInited(FALSE),
      m_fCriticalSectionInited(FALSE),
      m_fThreadAlive(FALSE),
      m_hAppToCleanup(INVALID_HANDLE_VALUE)
    {
    m_List.m_pPrev = &m_List;
    m_List.m_pNext = &m_List;
    }

/*===================================================================
CApplnCleanupMgr::~CApplnCleanupMgr

Application Cleanup Manager destructor.

Parameters:
    NONE

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

/*===================================================================
HRESULT CApplnCleanupMgr::Init

Initializes the Appln Cleanup Manager.

Parameters:
    NONE

Returns:
    S_OK            Success
    E_FAIL            Failure
    E_OUTOFMEMORY    Out of memory
===================================================================*/
HRESULT CApplnCleanupMgr::Init( void )
    {
    HRESULT hr = S_OK;

    Assert(!m_fInited);

    // Create delete app event

    m_hAppToCleanup = IIS_CREATE_EVENT(
                              "CApplnCleanupMgr::m_hAppToCleanup",
                              this,
                              FALSE,
                              FALSE
                              );

    if (!m_hAppToCleanup)
        return E_FAIL;

    // Init critical section

    ErrInitCriticalSection(&m_csLock, hr);
    if (FAILED(hr))
        return(hr);
    m_fCriticalSectionInited = TRUE;

    HANDLE  hThread = CreateThread(NULL, 0, CApplnCleanupMgr::ApplnCleanupThread, 0, 0, NULL);

    if (!hThread) {
        return E_FAIL;
    }

    CloseHandle(hThread);

    m_fInited = TRUE;

    return S_OK;
    }

/*===================================================================
HRESULT CApplnCleanupMgr::UnInit

UnInitializes the Appln Cleanup Manager.

Parameters:
    NONE

Returns:
    S_OK        Success
    E_FAIL        Failure
===================================================================*/
HRESULT CApplnCleanupMgr::UnInit( void )
{
    // set fInited to FALSE here so that the cleanup thread
    // can safely detect that we're shutting down.

    m_fInited = FALSE;

    if (m_hAppToCleanup != INVALID_HANDLE_VALUE) {
        // Set the event one last time so that the thread
        // wakes up, sees that shutdown is occurring and
        // exits.
        SetEvent(m_hAppToCleanup);
        CloseHandle(m_hAppToCleanup);
        m_hAppToCleanup = INVALID_HANDLE_VALUE;
    }

    // we'll wait for the thread to finish its work

    while(m_fThreadAlive) {
        Sleep(200);
    }

    if (m_fCriticalSectionInited) {
        DeleteCriticalSection(&m_csLock);
        m_fCriticalSectionInited = FALSE;
    }

    return S_OK;
}

/*===================================================================
CApplnCleanupMgr::AddAppln

Adds a CAppln element to link list / hash table.


Parameters:
    CAppln *pAppln            Application to cleanup

Returns:
    HRESULT
===================================================================*/
HRESULT CApplnCleanupMgr::AddAppln
(
CAppln *pAppln
)
{
    HRESULT     hr = S_OK;

#if UNICODE
    DBGPRINTF((DBG_CONTEXT, "[CApplnCleanupMgr] Adding App (%S)\n",pAppln->GetApplnPath(SOURCEPATHTYPE_VIRTUAL)));
#else
    DBGPRINTF((DBG_CONTEXT, "[CApplnCleanupMgr] Adding App (%s)\n",pAppln->GetApplnPath(SOURCEPATHTYPE_VIRTUAL)));
#endif

    Lock();

    AddElem(pAppln);

    UnLock();

    if (SUCCEEDED(hr)) {
        Wakeup();
    }

    return hr;
}

/*===================================================================
CApplnCleanupMgr::ApplnCleanupThread

The thread that does the work to cleanup applications

Parameters:

Returns:
    HRESULT
===================================================================*/
DWORD __stdcall CApplnCleanupMgr::ApplnCleanupThread(VOID  *pArg)
{
    g_ApplnCleanupMgr.ApplnCleanupDoWork();

    return 0;
}

/*===================================================================
CApplnCleanupMgr::ApplnCleanupDoWork

Proc that actually does the work

Parameters:

Returns:
    HRESULT
===================================================================*/
void CApplnCleanupMgr::ApplnCleanupDoWork()
{
    m_fThreadAlive = TRUE;

    // this thread will be in a constant loop checking for work

    while(1) {

        // hold the lock while in this loop.  This shouldn't hold it
        // for long as there are no long running operations in this loop.
        // If a thread can't be created and the application cleanup
        // must occur on this thread, then the lock is released.

        Lock();

        // This loop will execute while there is work and there aren't too many
        // threads active or we're in shutdown.  The theory here is that in the
        // non-shutdown case, let's not spin up more than 4 threads at a time to
        // do the cleanup.  If in shutdown, create as many threads as necessary.

        while(Head() && ((g_nAppRestartThreads < 4) || (IsShutDownInProgress()))) {

            CAppln *pAppln = static_cast<CAppln *>(Head());

            RemoveElem(Head());

#if UNICODE
    DBGPRINTF((DBG_CONTEXT, "[CApplnCleanupMgr] Cleanup Thread working on (%S)\n",pAppln->GetApplnPath(SOURCEPATHTYPE_VIRTUAL)));
#else
    DBGPRINTF((DBG_CONTEXT, "[CApplnCleanupMgr] Cleanup Thread working on (%s)\n",pAppln->GetApplnPath(SOURCEPATHTYPE_VIRTUAL)));
#endif

            HANDLE  hThread = CreateThread(NULL, 0, CAppln::ApplnCleanupProc, pAppln, 0, NULL);

            // failed to create a thread to do the work.  Cleanup the app right here.  
            // Unlock the cleanup manager while we are doing this.

            if (hThread == NULL) {
                UnLock();
                pAppln->ApplnCleanupProc(this);
                Lock();
            }
            else {
                InterlockedIncrement(&g_nAppRestartThreads);
                CloseHandle(hThread);
            }
        }

        UnLock();

        WaitForSingleObject(m_hAppToCleanup, INFINITE);

        // check the work queue under lock here to prevent the
        // 'if' condition below from kicking in and preventing
        // the remaining cleanup during shutdown

        Lock();
        if (Head()) {
            UnLock();
            continue;
        }
        UnLock();

        // check to see if shutdown is occurring...

        if (m_fInited == FALSE) {
            Assert(Head() == NULL);
            m_fThreadAlive = FALSE;
            return;
        }
    }

    return;
}

#define            WSTR_NULL       L"\0"

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

/*===================================================================
CApplnIterator::CApplnIterator

Constructor

Parameters:
    NONE

Returns:
    NONE
===================================================================*/

CApplnIterator::CApplnIterator()
    : m_pApplnMgr(NULL), m_pCurr(NULL), m_fEnded(FALSE)
    {
    }

/*===================================================================
CApplnIterator::~CApplnIterator

Destructor.

Parameters:
    NONE

Returns:
    NONE
===================================================================*/

CApplnIterator::~CApplnIterator( void )
    {
    if (m_pApplnMgr != NULL)
        Stop();
    }

/*===================================================================
HRESULT CApplnIterator::Start

Starts iterator on the Appln Manager.

Parameters:
    CApplnMgr * pApplnMgr   Appln Manager
                            (if NULL g_ApplnManager is assumed)

Returns:
    S_OK        Success
    E_FAIL        Failure
===================================================================*/

HRESULT CApplnIterator::Start
(
CApplnMgr *pApplnMgr
)
    {
    m_pApplnMgr = pApplnMgr ? m_pApplnMgr : &g_ApplnMgr;

    m_pApplnMgr->Lock();

    m_pCurr  = NULL;
    m_fEnded = FALSE;

    return S_OK;
    }

/*===================================================================
HRESULT CApplnIterator::Stop

Stops iterator on the Appln Manager.

Parameters:
    NONE

Returns:
    S_OK        Success
    E_FAIL        Failure
===================================================================*/

HRESULT CApplnIterator::Stop()
    {
    if (m_pApplnMgr)
        {
        m_pApplnMgr->UnLock();
        m_pApplnMgr = NULL;
        }

    m_pCurr  = NULL;
    m_fEnded = FALSE;

    return S_OK;
    }

/*===================================================================
HRESULT CApplnIterator::Next

Iterates to the next Appln.

Parameters:
    NONE

Returns:
    Appln * or NULL
===================================================================*/

CAppln *CApplnIterator::Next( void )
    {
    if (m_pApplnMgr == NULL || m_fEnded)
        return NULL;  // didn't start or already ended

    CLinkElem *pT = m_pCurr ? m_pCurr->m_pNext : m_pApplnMgr->Head();
    if (pT)
        {
        m_pCurr = static_cast<CAppln *>(pT);
        return m_pCurr;
        }

    m_fEnded = TRUE;
    return NULL;
    }