//
// Copyright (c) 1999-2001 Microsoft Corporation. All rights reserved.
//
// Implementation of CDirectMusicScript.
//

#include "stdinc.h"
#include "dll.h"
#include "dmscript.h"
#include "oleaut.h"
#include "globaldisp.h"
#include "activescript.h"
#include "sourcetext.h"

//////////////////////////////////////////////////////////////////////
// Creation

CDirectMusicScript::CDirectMusicScript()
  : m_cRef(0),
    m_fZombie(false),
    m_fCriticalSectionInitialized(false),
    m_pPerformance8(NULL),
    m_pLoader8P(NULL),
    m_pDispPerformance(NULL),
    m_pComposer8(NULL),
    m_fUseOleAut(true),
    m_pScriptManager(NULL),
    m_pContainerDispatch(NULL),
    m_pGlobalDispatch(NULL),
    m_fInitError(false)
{
    LockModule(true);
    InitializeCriticalSection(&m_CriticalSection);
    // Note: on pre-Blackcomb OS's, this call can raise an exception; if it
    // ever pops in stress, we can add an exception handler and retry loop.
    m_fCriticalSectionInitialized = TRUE;

    m_info.fLoaded = false;
    m_vDirectMusicVersion.dwVersionMS = 0;
    m_vDirectMusicVersion.dwVersionLS = 0;
    Zero(&m_iohead);
    ZeroAndSize(&m_InitErrorInfo);
}

void CDirectMusicScript::ReleaseObjects()
{
    if (m_pScriptManager)
    {
        m_pScriptManager->Close();
        SafeRelease(m_pScriptManager);
    }
    SafeRelease(m_pPerformance8);
    SafeRelease(m_pDispPerformance);
    if (m_pLoader8P)
    {
        m_pLoader8P->ReleaseP();
        m_pLoader8P = NULL;
    }
    SafeRelease(m_pComposer8);
    delete m_pContainerDispatch;
    m_pContainerDispatch = NULL;
    delete m_pGlobalDispatch;
    m_pGlobalDispatch = NULL;
}

HRESULT CDirectMusicScript::CreateInstance(
        IUnknown* pUnknownOuter,
        const IID& iid,
        void** ppv)
{
    *ppv = NULL;
    if (pUnknownOuter)
         return CLASS_E_NOAGGREGATION;

    CDirectMusicScript *pInst = new CDirectMusicScript;
    if (pInst == NULL)
        return E_OUTOFMEMORY;

    return pInst->QueryInterface(iid, ppv);
}

//////////////////////////////////////////////////////////////////////
// IUnknown

STDMETHODIMP 
CDirectMusicScript::QueryInterface(const IID &iid, void **ppv)
{
    V_INAME(CDirectMusicScript::QueryInterface);
    V_PTRPTR_WRITE(ppv);
    V_REFGUID(iid);

    if (iid == IID_IUnknown || iid == IID_IDirectMusicScript)
    {
        *ppv = static_cast<IDirectMusicScript*>(this);
    }
    else if (iid == IID_IDirectMusicScriptPrivate)
    {
        *ppv = static_cast<IDirectMusicScriptPrivate*>(this);
    }
    else if (iid == IID_IDirectMusicObject)
    {
        *ppv = static_cast<IDirectMusicObject*>(this);
    }
    else if (iid == IID_IDirectMusicObjectP)
    {
        *ppv = static_cast<IDirectMusicObjectP*>(this);
    }
    else if (iid == IID_IPersistStream)
    {
        *ppv = static_cast<IPersistStream*>(this);
    }
    else if (iid == IID_IPersist)
    {
        *ppv = static_cast<IPersist*>(this);
    }
    else if (iid == IID_IDispatch)
    {
        *ppv = static_cast<IDispatch*>(this);
    }
    else
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }
    
    reinterpret_cast<IUnknown*>(this)->AddRef();
    
    return S_OK;
}

STDMETHODIMP_(ULONG)
CDirectMusicScript::AddRef()
{
    return InterlockedIncrement(&m_cRef);
}

STDMETHODIMP_(ULONG)
CDirectMusicScript::Release()
{
    if (!InterlockedDecrement(&m_cRef)) 
    {
        this->Zombie();
        DeleteCriticalSection(&m_CriticalSection);
        delete this;
        LockModule(false);
        return 0;
    }

    return m_cRef;
}

//////////////////////////////////////////////////////////////////////
// IPersistStream

STDMETHODIMP
CDirectMusicScript::Load(IStream* pStream)
{
    V_INAME(CDirectMusicScript::Load);
    V_INTERFACE(pStream);

    if (m_fZombie)
    {
        Trace(1, "Error: Call of IDirectMusicScript::Load after the script has been garbage collected. "
                    "It is invalid to continue using a script after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
                    "and then calling CollectGarbage or Release on the loader.");
        return DMUS_S_GARBAGE_COLLECTED;
    }

    HRESULT hr = S_OK;

    SmartRef::CritSec CS(&m_CriticalSection);

    // Clear any old info
    this->ReleaseObjects();
    m_info.fLoaded = false;
    m_info.oinfo.Clear();
    m_vDirectMusicVersion.dwVersionMS = 0;
    m_vDirectMusicVersion.dwVersionLS = 0;
    m_wstrLanguage = NULL;
    m_fInitError = false;

    // Get the loader from stream
    IDirectMusicGetLoader *pIDMGetLoader = NULL;
    SmartRef::ComPtr<IDirectMusicLoader> scomLoader;
    hr = pStream->QueryInterface(IID_IDirectMusicGetLoader, reinterpret_cast<void **>(&pIDMGetLoader));
    if (FAILED(hr))
    {
        Trace(1, "Error: unable to load script from a stream because it doesn't support the IDirectMusicGetLoader interface.\n");
        return DMUS_E_UNSUPPORTED_STREAM;
    }

    hr = pIDMGetLoader->GetLoader(&scomLoader);
    pIDMGetLoader->Release();
    if (FAILED(hr))
        return hr;

    hr = scomLoader->QueryInterface(IID_IDirectMusicLoader8P, reinterpret_cast<void **>(&m_pLoader8P)); // OK if this fails -- just means the scripts won't be garbage collected
    if (SUCCEEDED(hr))
    {
        // Hold only a private ref on the loader.  See IDirectMusicLoader8P::AddRefP for more info.
        m_pLoader8P->AddRefP();
        m_pLoader8P->Release(); // offset the QI
    }

    // Read the script's header information

    SmartRef::RiffIter riForm(pStream);
    if (!riForm)
    {
#ifdef DBG
        if (SUCCEEDED(riForm.hr()))
        {
            Trace(1, "Error: Unable to load script: Unexpected end of file.\n");
        }
#endif
        return SUCCEEDED(riForm.hr()) ? DMUS_E_SCRIPT_INVALID_FILE : riForm.hr();
    }
    hr = riForm.FindRequired(SmartRef::RiffIter::Riff, DMUS_FOURCC_SCRIPT_FORM, DMUS_E_SCRIPT_INVALID_FILE);
    if (FAILED(hr))
    {
#ifdef DBG
        if (hr == DMUS_E_SCRIPT_INVALID_FILE)
        {
            Trace(1, "Error: Unable to load script: Form 'DMSC' not found.\n");
        }
#endif
        return hr;
    }

    SmartRef::RiffIter ri = riForm.Descend();
    if (!ri)
        return ri.hr();

    hr = ri.FindRequired(SmartRef::RiffIter::Chunk, DMUS_FOURCC_SCRIPT_CHUNK, DMUS_E_SCRIPT_INVALID_FILE);
    if (FAILED(hr))
    {
#ifdef DBG
        if (hr == DMUS_E_SCRIPT_INVALID_FILE)
        {
            Trace(1, "Error: Unable to load script: Chunk 'schd' not found.\n");
        }
#endif
        return hr;
    }

    hr = SmartRef::RiffIterReadChunk(ri, &m_iohead);
    if (FAILED(hr))
        return hr;

    hr = ri.LoadObjectInfo(&m_info.oinfo, SmartRef::RiffIter::Chunk, DMUS_FOURCC_SCRIPTVERSION_CHUNK);
    if (FAILED(hr))
        return hr;

    hr = SmartRef::RiffIterReadChunk(ri, &m_vDirectMusicVersion);
    if (FAILED(hr))
        return hr;

    // Read the script's embedded container
    IDirectMusicContainer *pContainer = NULL;
    hr = ri.FindAndGetEmbeddedObject(
                SmartRef::RiffIter::Riff,
                DMUS_FOURCC_CONTAINER_FORM,
                DMUS_E_SCRIPT_INVALID_FILE,
                scomLoader,
                CLSID_DirectMusicContainer,
                IID_IDirectMusicContainer,
                reinterpret_cast<void**>(&pContainer));
    if (FAILED(hr))
    {
#ifdef DBG
        if (hr == DMUS_E_SCRIPT_INVALID_FILE)
        {
            Trace(1, "Error: Unable to load script: Form 'DMCN' no found.\n");
        }
#endif
        return hr;
    }

    // Build the container object that will represent the items in the container to the script

    m_pContainerDispatch = new CContainerDispatch(pContainer, scomLoader, m_iohead.dwFlags, &hr);
    pContainer->Release();
    if (!m_pContainerDispatch)
        return E_OUTOFMEMORY;
    if (FAILED(hr))
        return hr;

    // Create the global dispatch object

    m_pGlobalDispatch = new CGlobalDispatch(this);
    if (!m_pGlobalDispatch)
        return E_OUTOFMEMORY;

    // Get the script's language

    hr = ri.FindRequired(SmartRef::RiffIter::Chunk, DMUS_FOURCC_SCRIPTLANGUAGE_CHUNK, DMUS_E_SCRIPT_INVALID_FILE);
    if (FAILED(hr))
    {
#ifdef DBG
        if (hr == DMUS_E_SCRIPT_INVALID_FILE)
        {
            Trace(1, "Error: Unable to load script: Chunk 'scla' no found.\n");
        }
#endif
        return hr;
    }

    hr = ri.ReadText(&m_wstrLanguage);
    if (FAILED(hr))
    {
#ifdef DBG
        if (hr == E_FAIL)
        {
            Trace(1, "Error: Unable to load script: Problem reading 'scla' chunk.\n");
        }
#endif
        return hr == E_FAIL ? DMUS_E_SCRIPT_INVALID_FILE : hr;
    }

    // Get the script's source code

    SmartRef::WString wstrSource;
    for (++ri; ;++ri)
    {
        if (!ri)
        {
            Trace(1, "Error: Unable to load script: Expected chunk 'scsr' or list 'DMRF'.\n");
            return DMUS_E_SCRIPT_INVALID_FILE;
        }

        SmartRef::RiffIter::RiffType type = ri.type();
        FOURCC id = ri.id();

        if (type == SmartRef::RiffIter::Chunk)
        {
            if (id == DMUS_FOURCC_SCRIPTSOURCE_CHUNK)
            {
                hr = ri.ReadText(&wstrSource);
                if (FAILED(hr))
                {
#ifdef DBG
                    if (hr == E_FAIL)
                    {
                        Trace(1, "Error: Unable to load script: Problem reading 'scsr' chunk.\n");
                    }
#endif
                    return hr == E_FAIL ? DMUS_E_SCRIPT_INVALID_FILE : hr;
                }
            }
            break;
        }
        else if (type == SmartRef::RiffIter::List)
        {
            if (id == DMUS_FOURCC_REF_LIST)
            {
                DMUS_OBJECTDESC desc;
                hr = ri.ReadReference(&desc);
                if (FAILED(hr))
                    return hr;
                // The resulting desc shouldn't have a name or GUID (the plain text file can't hold name/GUID info)
                // and it should have a clsid should be GUID_NULL, which we'll replace with the clsid of our private
                // source helper object.
                if (desc.dwValidData & (DMUS_OBJ_NAME | DMUS_OBJ_OBJECT) ||
                        !(desc.dwValidData & DMUS_OBJ_CLASS) || desc.guidClass != GUID_NULL)
                {
#ifdef DBG
                    if (desc.dwValidData & (DMUS_OBJ_NAME | DMUS_OBJ_OBJECT))
                    {
                        Trace(1, "Error: Unable to load script: 'DMRF' list must have dwValidData with DMUS_OBJ_CLASS and guidClassID of GUID_NULL.\n");
                    }
                    else
                    {
                        Trace(1, "Error: Unable to load script: 'DMRF' list cannot have dwValidData with DMUS_OBJ_NAME or DMUS_OBJ_OBJECT.\n");
                    }
#endif
                    return DMUS_E_SCRIPT_INVALID_FILE;
                }
                desc.guidClass = CLSID_DirectMusicSourceText;
                IDirectMusicSourceText *pISource = NULL;
                hr = scomLoader->EnableCache(CLSID_DirectMusicSourceText, false); // This is a private object we just use temporarily. Don't want these guys hanging around in the cache.
                if (FAILED(hr))
                    return hr;
                hr = scomLoader->GetObject(&desc, IID_IDirectMusicSourceText, reinterpret_cast<void**>(&pISource));
                if (FAILED(hr))
                    return hr;
                DWORD cwchSourceBufferSize = 0;
                pISource->GetTextLength(&cwchSourceBufferSize);
                WCHAR *pwszSource = new WCHAR[cwchSourceBufferSize];
                if (!pwszSource)
                    return E_OUTOFMEMORY;
                pISource->GetText(pwszSource);
                *&wstrSource = pwszSource;
                pISource->Release();
            }
            break;
        }
    }

    m_info.fLoaded = true;

    // Now that we are loaded and initialized, we can start active scripting

    // See if we're dealing with a custom DirectMusic scripting engine.  Such engines are marked with the key DMScript.  They can be
    // called on multiple threads and they don't use oleaut32.  Ordinary active scripting engines are marked with the key OLEScript.
    SmartRef::HKey shkeyLanguage;
    SmartRef::HKey shkeyMark;
    SmartRef::AString astrLanguage = m_wstrLanguage;
    if (ERROR_SUCCESS != ::RegOpenKeyEx(HKEY_CLASSES_ROOT, astrLanguage, 0, KEY_QUERY_VALUE, &shkeyLanguage) || !shkeyLanguage)
    {
        Trace(1, "Error: Unable to load script: Scripting engine for language %s does not exist or is not registered.\n", astrLanguage);
        return DMUS_E_SCRIPT_LANGUAGE_INCOMPATIBLE;
    }
    bool fCustomScriptEngine = ERROR_SUCCESS == ::RegOpenKeyEx(shkeyLanguage, "DMScript", 0, KEY_QUERY_VALUE, &shkeyMark) && shkeyMark;
    if (!fCustomScriptEngine)
    {
        if (ERROR_SUCCESS != ::RegOpenKeyEx(shkeyLanguage, "OLEScript", 0, KEY_QUERY_VALUE, &shkeyMark) || !shkeyMark)
        {
            Trace(1, "Error: Unable to load script: Language %s refers to a COM object that is not registered as a scripting engine (OLEScript key).\n", astrLanguage);
            return DMUS_E_SCRIPT_LANGUAGE_INCOMPATIBLE;
        }
    }

    m_fUseOleAut = !fCustomScriptEngine;
    if (fCustomScriptEngine)
    {
        m_pScriptManager = new CActiveScriptManager(
                                        m_fUseOleAut,
                                        m_wstrLanguage,
                                        wstrSource,
                                        this,
                                        &hr,
                                        &m_InitErrorInfo);
    }
    else
    {
        m_pScriptManager = new CSingleThreadedScriptManager(
                                        m_fUseOleAut,
                                        m_wstrLanguage,
                                        wstrSource,
                                        this,
                                        &hr,
                                        &m_InitErrorInfo);
    }

    if (!m_pScriptManager)
        return E_OUTOFMEMORY;

    if (FAILED(hr))
    {
        SafeRelease(m_pScriptManager);
    }

    if (hr == DMUS_E_SCRIPT_ERROR_IN_SCRIPT)
    {
        // If we fail here, load would fail and client would never be able to get the
        // error information.  Instead, return S_OK and save the error to return from Init.
        m_fInitError = true;
        hr = S_OK;
    }

    return hr;
}

//////////////////////////////////////////////////////////////////////
// IDirectMusicObject

STDMETHODIMP 
CDirectMusicScript::GetDescriptor(LPDMUS_OBJECTDESC pDesc)
{
    V_INAME(CDirectMusicScript::GetDescriptor);
    V_PTR_WRITE(pDesc, DMUS_OBJECTDESC);
    
    ZeroMemory(pDesc, sizeof(DMUS_OBJECTDESC));
    pDesc->dwSize = sizeof(DMUS_OBJECTDESC);

    if (m_fZombie)
    {
        Trace(1, "Error: Call of IDirectMusicScript::GetDescriptor after the script has been garbage collected. "
                    "It is invalid to continue using a script after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
                    "and then calling CollectGarbage or Release on the loader.");
        return DMUS_S_GARBAGE_COLLECTED;
    }
    
    if (wcslen(m_info.oinfo.wszName) > 0)
    {
        pDesc->dwValidData |= DMUS_OBJ_NAME;
        wcsncpy(pDesc->wszName, m_info.oinfo.wszName, DMUS_MAX_NAME);
        pDesc->wszName[DMUS_MAX_NAME-1] = L'\0';
    }

    if (GUID_NULL != m_info.oinfo.guid)
    {
        pDesc->guidObject = m_info.oinfo.guid;
        pDesc->dwValidData |= DMUS_OBJ_OBJECT;
    }

    pDesc->vVersion = m_info.oinfo.vVersion;
    pDesc->dwValidData |= DMUS_OBJ_VERSION;

    pDesc->guidClass = CLSID_DirectMusicScript;
    pDesc->dwValidData |= DMUS_OBJ_CLASS;

    if (m_info.wstrFilename)
    {
        wcsncpy(pDesc->wszFileName, m_info.wstrFilename, DMUS_MAX_FILENAME);
        pDesc->wszFileName[DMUS_MAX_FILENAME-1] = L'\0';
        pDesc->dwValidData |= DMUS_OBJ_FILENAME;
    }

    if (m_info.fLoaded)
    {
        pDesc->dwValidData |= DMUS_OBJ_LOADED;
    }

    return S_OK;
}

STDMETHODIMP 
CDirectMusicScript::SetDescriptor(LPDMUS_OBJECTDESC pDesc)
{
    V_INAME(CDirectMusicScript::SetDescriptor);
    V_STRUCTPTR_READ(pDesc, DMUS_OBJECTDESC);

    if (m_fZombie)
    {
        Trace(1, "Error: Call of IDirectMusicScript::SetDescriptor after the script has been garbage collected. "
                    "It is invalid to continue using a script after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
                    "and then calling CollectGarbage or Release on the loader.");
        return DMUS_S_GARBAGE_COLLECTED;
    }
    
    DWORD dwTemp = pDesc->dwValidData;

    if (pDesc->dwValidData & DMUS_OBJ_OBJECT)
    {
        m_info.oinfo.guid = pDesc->guidObject;
    }

    if (pDesc->dwValidData & DMUS_OBJ_CLASS)
    {
        pDesc->dwValidData &= ~DMUS_OBJ_CLASS;
    }

    if (pDesc->dwValidData & DMUS_OBJ_NAME)
    {
        wcsncpy(m_info.oinfo.wszName, pDesc->wszName, DMUS_MAX_NAME);
        m_info.oinfo.wszName[DMUS_MAX_NAME-1] = L'\0';
    }

    if (pDesc->dwValidData & DMUS_OBJ_CATEGORY)
    {
        pDesc->dwValidData &= ~DMUS_OBJ_CATEGORY;
    }

    if (pDesc->dwValidData & DMUS_OBJ_FILENAME)
    {
        m_info.wstrFilename = pDesc->wszFileName;
    }

    if (pDesc->dwValidData & DMUS_OBJ_FULLPATH)
    {
        pDesc->dwValidData &= ~DMUS_OBJ_FULLPATH;
    }

    if (pDesc->dwValidData & DMUS_OBJ_URL)
    {
        pDesc->dwValidData &= ~DMUS_OBJ_URL;
    }

    if (pDesc->dwValidData & DMUS_OBJ_VERSION)
    {
        m_info.oinfo.vVersion = pDesc->vVersion;
    }
    
    if (pDesc->dwValidData & DMUS_OBJ_DATE)
    {
        pDesc->dwValidData &= ~DMUS_OBJ_DATE;
    }

    if (pDesc->dwValidData & DMUS_OBJ_LOADED)
    {
        pDesc->dwValidData &= ~DMUS_OBJ_LOADED;
    }
    
    return dwTemp == pDesc->dwValidData ? S_OK : S_FALSE;
}

STDMETHODIMP 
CDirectMusicScript::ParseDescriptor(LPSTREAM pStream, LPDMUS_OBJECTDESC pDesc)
{
    V_INAME(CDirectMusicScript::ParseDescriptor);
    V_INTERFACE(pStream);
    V_PTR_WRITE(pDesc, DMUS_OBJECTDESC);
    
    ZeroMemory(pDesc, sizeof(DMUS_OBJECTDESC));
    pDesc->dwSize = sizeof(DMUS_OBJECTDESC);

    if (m_fZombie)
    {
        Trace(1, "Error: Call of IDirectMusicScript::ParseDescriptor after the script has been garbage collected. "
                    "It is invalid to continue using a script after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
                    "and then calling CollectGarbage or Release on the loader.");
        return DMUS_S_GARBAGE_COLLECTED;
    }
    
    SmartRef::CritSec CS(&m_CriticalSection);

    // Read the script's header information

    SmartRef::RiffIter riForm(pStream);
    if (!riForm)
    {
#ifdef DBG
        if (SUCCEEDED(riForm.hr()))
        {
            Trace(2, "Error: ParseDescriptor on a script failed: Unexpected end of file. "
                        "(Note that this may be OK, such as when ScanDirectory is used to parse a set of unknown files, some of which are not scripts.)\n");
        }
#endif
        return SUCCEEDED(riForm.hr()) ? DMUS_E_SCRIPT_INVALID_FILE : riForm.hr();
    }
    HRESULT hr = riForm.FindRequired(SmartRef::RiffIter::Riff, DMUS_FOURCC_SCRIPT_FORM, DMUS_E_SCRIPT_INVALID_FILE);
    if (FAILED(hr))
    {
#ifdef DBG
        if (hr == DMUS_E_SCRIPT_INVALID_FILE)
        {
            Trace(1, "Error: ParseDescriptor on a script failed: Form 'DMSC' not found. "
                        "(Note that this may be OK, such as when ScanDirectory is used to parse a set of unknown files, some of which are not scripts.)\n");
        }
#endif
        return hr;
    }

    SmartRef::RiffIter ri = riForm.Descend();
    if (!ri)
        return ri.hr();

    hr = ri.LoadObjectInfo(&m_info.oinfo, SmartRef::RiffIter::Chunk, DMUS_FOURCC_SCRIPTVERSION_CHUNK);
    if (FAILED(hr))
        return hr;

    hr = this->GetDescriptor(pDesc);
    return hr;
}

STDMETHODIMP_(void)
CDirectMusicScript::Zombie()
{
    m_fZombie = true;
    this->ReleaseObjects();
}

//////////////////////////////////////////////////////////////////////
// IDirectMusicScript

STDMETHODIMP
CDirectMusicScript::Init(IDirectMusicPerformance *pPerformance, DMUS_SCRIPT_ERRORINFO *pErrorInfo)
{
    V_INAME(CDirectMusicScript::Init);
    V_INTERFACE(pPerformance);
    V_PTR_WRITE_OPT(pErrorInfo, DMUS_SCRIPT_ERRORINFO);

    if (m_fZombie)
    {
        Trace(1, "Error: Call of IDirectMusicScript::Init after the script has been garbage collected. "
                    "It is invalid to continue using a script after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
                    "and then calling CollectGarbage or Release on the loader.");
        return DMUS_S_GARBAGE_COLLECTED;
    }

    SmartRef::ComPtr<IDirectMusicPerformance8> scomPerformance8;
    HRESULT hr = pPerformance->QueryInterface(IID_IDirectMusicPerformance8, reinterpret_cast<void **>(&scomPerformance8));
    if (FAILED(hr))
        return hr;
    
    // Don't take the critical section if the script is already initialized.
    // For example, this is necessary in the following situation:
    //  - The critical section has already been taken by CallRoutine.
    //  - The routine played a segment with a script track referencing this script.
    //  - The script track calls Init (from a different thread) to make sure the script
    //    is initialized.
    if (m_pPerformance8)
    {
        // Additional calls to Init are ignored.
        // First call wins.  Return S_FALSE if performance doesn't match.
        if (m_pPerformance8 == scomPerformance8)
            return S_OK;
        else
            return S_FALSE;
    }

    SmartRef::CritSec CS(&m_CriticalSection);

    if (m_fInitError)
    {
        if (pErrorInfo)
        {
            // Syntax errors in a script occur as it is loaded, before SetDescriptor gives a script
            // its filename.  We'll have it after the load (before init is called) so can add it
            // back in here.
            if (m_InitErrorInfo.wszSourceFile[0] == L'\0' && m_info.wstrFilename)
                wcsTruncatedCopy(m_InitErrorInfo.wszSourceFile, m_info.wstrFilename, DMUS_MAX_FILENAME);

            CopySizedStruct(pErrorInfo, &m_InitErrorInfo);
        }

        return DMUS_E_SCRIPT_ERROR_IN_SCRIPT;
    }

    if (!m_info.fLoaded)
    {
        Trace(1, "Error: IDirectMusicScript::Init called before the script has been loaded.\n");
        return DMUS_E_NOT_LOADED;
    }

    // Get the dispatch interface for the performance
    SmartRef::ComPtr<IDispatch> scomDispPerformance = NULL;
    hr = pPerformance->QueryInterface(IID_IDispatch, reinterpret_cast<void **>(&scomDispPerformance));
    if (FAILED(hr))
        return hr;

    // Get a composer object
    hr = CoCreateInstance(CLSID_DirectMusicComposer, NULL, CLSCTX_INPROC_SERVER, IID_IDirectMusicComposer8, reinterpret_cast<void **>(&m_pComposer8));
    if (FAILED(hr))
        return hr;

    m_pDispPerformance = scomDispPerformance.disown();
    m_pPerformance8 = scomPerformance8.disown();

    hr = m_pScriptManager->Start(pErrorInfo);
    if (FAILED(hr))
        return hr;

    hr = m_pContainerDispatch->OnScriptInit(m_pPerformance8);
    return hr;
}

// Returns DMUS_E_SCRIPT_ROUTINE_NOT_FOUND if routine doesn't exist in the script.
STDMETHODIMP
CDirectMusicScript::CallRoutine(WCHAR *pwszRoutineName, DMUS_SCRIPT_ERRORINFO *pErrorInfo)
{
    V_INAME(CDirectMusicScript::CallRoutine);
    V_BUFPTR_READ(pwszRoutineName, 2);
    V_PTR_WRITE_OPT(pErrorInfo, DMUS_SCRIPT_ERRORINFO);

    if (m_fZombie)
    {
        Trace(1, "Error: Call of IDirectMusicScript::CallRoutine after the script has been garbage collected. "
                    "It is invalid to continue using a script after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
                    "and then calling CollectGarbage or Release on the loader.");
        return DMUS_S_GARBAGE_COLLECTED;
    }

    SmartRef::CritSec CS(&m_CriticalSection);

    if (!m_pScriptManager || !m_pPerformance8)
    {
        Trace(1, "Error: IDirectMusicScript::Init must be called before IDirectMusicScript::CallRoutine.\n");
        return DMUS_E_NOT_INIT;
    }

    return m_pScriptManager->CallRoutine(pwszRoutineName, pErrorInfo);
}

// Returns DMUS_E_SCRIPT_VARIABLE_NOT_FOUND if variable doesn't exist in the script.
STDMETHODIMP
CDirectMusicScript::SetVariableVariant(
        WCHAR *pwszVariableName,
        VARIANT varValue,
        BOOL fSetRef,
        DMUS_SCRIPT_ERRORINFO *pErrorInfo)
{
    V_INAME(CDirectMusicScript::SetVariableVariant);
    V_BUFPTR_READ(pwszVariableName, 2);
    V_PTR_WRITE_OPT(pErrorInfo, DMUS_SCRIPT_ERRORINFO);

    switch (varValue.vt)
    {
    case VT_BSTR:
        V_BUFPTR_READ_OPT(varValue.bstrVal, sizeof(OLECHAR));
        // We could be more thorough and verify each character until we hit the terminator but
        // that would be inefficient.  We could also use the length preceding a BSTR pointer,
        // but that would be cheating COM's functions that encapsulate BSTRs and could lead to
        // problems in future versions of windows such as 64 bit if the BSTR format changes.
        break;
    case VT_UNKNOWN:
        V_INTERFACE_OPT(varValue.punkVal);
        break;
    case VT_DISPATCH:
        V_INTERFACE_OPT(varValue.pdispVal);
        break;
    }

    if (m_fZombie)
    {
        Trace(1, "Error: Call of IDirectMusicScript::SetVariableObject/SetVariableNumber/SetVariableVariant after the script has been garbage collected. "
                    "It is invalid to continue using a script after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
                    "and then calling CollectGarbage or Release on the loader.");
        return DMUS_S_GARBAGE_COLLECTED;
    }

    SmartRef::CritSec CS(&m_CriticalSection);

    if (!m_pScriptManager || !m_pPerformance8)
    {
        Trace(1, "Error: IDirectMusicScript::Init must be called before IDirectMusicScript::SetVariableVariant.\n");
        return DMUS_E_NOT_INIT;
    }

    HRESULT hr = m_pScriptManager->SetVariable(pwszVariableName, varValue, !!fSetRef, pErrorInfo);
    if (hr == DMUS_E_SCRIPT_VARIABLE_NOT_FOUND)
    {
        // There are also items in the script's container that the m_pScriptManager object isn't available.
        // If that's the case, we should return a more specific error message.
        IUnknown *punk = NULL;
        hr = m_pContainerDispatch->GetVariableObject(pwszVariableName, &punk);
        if (SUCCEEDED(hr))
        {
            // We don't actually need the object--it can't be set.  Just needed to find out if it's there
            // in order to return a more specific error message.
            punk->Release();
            return DMUS_E_SCRIPT_CONTENT_READONLY;
        }
    }
    return hr;
}

// Returns DMUS_E_SCRIPT_VARIABLE_NOT_FOUND and empty value if variable doesn't exist in the script.
// Certain varient types such as BSTRs and interface pointers must be freed/released according to the standards for VARIANTS.
// If unsure, use VariantClear (requires oleaut32).
STDMETHODIMP
CDirectMusicScript::GetVariableVariant(WCHAR *pwszVariableName, VARIANT *pvarValue, DMUS_SCRIPT_ERRORINFO *pErrorInfo)
{
    V_INAME(CDirectMusicScript::GetVariableVariant);
    V_BUFPTR_READ(pwszVariableName, 2);
    V_PTR_WRITE(pvarValue, VARIANT);
    V_PTR_WRITE_OPT(pErrorInfo, DMUS_SCRIPT_ERRORINFO);
    
    DMS_VariantInit(m_fUseOleAut, pvarValue);

    if (m_fZombie)
    {
        Trace(1, "Error: Call of IDirectMusicScript::GetVariableObject/GetVariableNumber/GetVariableVariant after the script has been garbage collected. "
                    "It is invalid to continue using a script after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
                    "and then calling CollectGarbage or Release on the loader.");
        return DMUS_S_GARBAGE_COLLECTED;
    }

    SmartRef::CritSec CS(&m_CriticalSection);

    if (!m_pScriptManager || !m_pPerformance8)
    {
        Trace(1, "Error: IDirectMusicScript::Init must be called before IDirectMusicScript::GetVariableVariant.\n");
        return DMUS_E_NOT_INIT;
    }

    HRESULT hr = m_pScriptManager->GetVariable(pwszVariableName, pvarValue, pErrorInfo);

    if (hr == DMUS_E_SCRIPT_VARIABLE_NOT_FOUND)
    {
        // There are also items in the script's container that we need to return.
        // This is implemented by the container, which returns the IUnknown pointer directly rather than through a variant.
        IUnknown *punk = NULL;
        hr = m_pContainerDispatch->GetVariableObject(pwszVariableName, &punk);
        if (SUCCEEDED(hr))
        {
            pvarValue->vt = VT_UNKNOWN;
            pvarValue->punkVal = punk;
        }
    }

#ifdef DBG
    if (hr == DMUS_E_SCRIPT_VARIABLE_NOT_FOUND)
    {
        Trace(1, "Error: Attempt to get variable '%S' that is not defined in the script.\n", pwszVariableName);
    }
#endif

    if (!m_fUseOleAut && pvarValue->vt == VT_BSTR)
    {
        // m_fUseOleAut is false when we're using our own custom scripting engine that avoids
        // depending on oleaut32.dll.  But in this case we're returning a BSTR variant to the
        // caller.  We have to allocate this string with SysAllocString (from oleaut32)
        // because the caller is going to free it with SysFreeString--the standard thing to
        // do with a variant BSTR.
        BSTR bstrOle = DMS_SysAllocString(true, pvarValue->bstrVal); // allocate a copy with oleaut
        DMS_SysFreeString(false, pvarValue->bstrVal); // free the previous value (allocated without oleaut)
        pvarValue->bstrVal = bstrOle; // return the oleaut string to the user
        if (!bstrOle)
            hr = E_OUTOFMEMORY;
    }

    return hr;
}

// Returns DMUS_E_SCRIPT_VARIABLE_NOT_FOUND if variable doesn't exist in the script.
STDMETHODIMP
CDirectMusicScript::SetVariableNumber(WCHAR *pwszVariableName, LONG lValue, DMUS_SCRIPT_ERRORINFO *pErrorInfo)
{
    VARIANT var;
    var.vt = VT_I4;
    var.lVal = lValue;
    return this->SetVariableVariant(pwszVariableName, var, false, pErrorInfo);
}

// Returns DMUS_E_SCRIPT_VARIABLE_NOT_FOUND and 0 if variable doesn't exist in the script.
// Returns DISP_E_TYPEMISMATCH if variable's datatype cannot be converted to LONG.
STDMETHODIMP
CDirectMusicScript::GetVariableNumber(WCHAR *pwszVariableName, LONG *plValue, DMUS_SCRIPT_ERRORINFO *pErrorInfo)
{
    V_INAME(CDirectMusicScript::GetVariableNumber);
    V_PTR_WRITE(plValue, LONG);
    *plValue = 0;

    VARIANT var;
    HRESULT hr = this->GetVariableVariant(pwszVariableName, &var, pErrorInfo);
    if (FAILED(hr) || hr == S_FALSE || hr == DMUS_S_GARBAGE_COLLECTED)
        return hr;

    hr = DMS_VariantChangeType(m_fUseOleAut, &var, &var, 0, VT_I4);
    if (SUCCEEDED(hr))
        *plValue = var.lVal;

    // GetVariableVariant forces a BSTR to be allocated with SysAllocString;
    // so if we allocated a BSTR there, we need to free it with SysAllocString here.
    bool fUseOleAut = m_fUseOleAut;
    if (!m_fUseOleAut && var.vt == VT_BSTR)
    {
        fUseOleAut = true;
    }
    DMS_VariantClear(fUseOleAut, &var);
    return hr;
}

// Returns DMUS_E_SCRIPT_VARIABLE_NOT_FOUND if variable doesn't exist in the script.
STDMETHODIMP
CDirectMusicScript::SetVariableObject(WCHAR *pwszVariableName, IUnknown *punkValue, DMUS_SCRIPT_ERRORINFO *pErrorInfo)
{
    VARIANT var;
    var.vt = VT_UNKNOWN;
    var.punkVal = punkValue;
    return this->SetVariableVariant(pwszVariableName, var, true, pErrorInfo);
}

// Returns DMUS_E_SCRIPT_VARIABLE_NOT_FOUND and NULL if variable doesn't exist in the script.
// Returns DISP_E_TYPEMISMATCH if variable's datatype cannot be converted to IUnknown.
STDMETHODIMP
CDirectMusicScript::GetVariableObject(WCHAR *pwszVariableName, REFIID riid, LPVOID FAR *ppv, DMUS_SCRIPT_ERRORINFO *pErrorInfo)
{
    V_INAME(CDirectMusicScript::GetVariableObject);
    V_PTR_WRITE(ppv, IUnknown *);
    *ppv = NULL;

    VARIANT var;
    HRESULT hr = this->GetVariableVariant(pwszVariableName, &var, pErrorInfo);
    if (FAILED(hr) || hr == DMUS_S_GARBAGE_COLLECTED)
        return hr;

    hr = DMS_VariantChangeType(m_fUseOleAut, &var, &var, 0, VT_UNKNOWN);
    if (SUCCEEDED(hr))
        hr = var.punkVal->QueryInterface(riid, ppv);
    DMS_VariantClear(m_fUseOleAut, &var);
    return hr;
}

STDMETHODIMP
CDirectMusicScript::EnumRoutine(DWORD dwIndex, WCHAR *pwszName)
{
    V_INAME(CDirectMusicScript::EnumRoutine);
    V_BUFPTR_WRITE(pwszName, MAX_PATH);

    *pwszName = L'\0';

    if (m_fZombie)
    {
        Trace(1, "Error: Call of IDirectMusicScript::EnumRoutine after the script has been garbage collected. "
                    "It is invalid to continue using a script after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
                    "and then calling CollectGarbage or Release on the loader.");
        return DMUS_S_GARBAGE_COLLECTED;
    }

    if (!m_pScriptManager || !m_pPerformance8)
    {
        Trace(1, "Error: IDirectMusicScript::Init must be called before IDirectMusicScript::EnumRoutine.\n");
        return DMUS_E_NOT_INIT;
    }

    return m_pScriptManager->EnumItem(true, dwIndex, pwszName, NULL);
}

STDMETHODIMP
CDirectMusicScript::EnumVariable(DWORD dwIndex, WCHAR *pwszName)
{
    V_INAME(CDirectMusicScript::EnumRoutine);
    V_BUFPTR_WRITE(pwszName, MAX_PATH);

    *pwszName = L'\0';

    if (m_fZombie)
    {
        Trace(1, "Error: Call of IDirectMusicScript::EnumVariable after the script has been garbage collected. "
                    "It is invalid to continue using a script after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
                    "and then calling CollectGarbage or Release on the loader.");
        return DMUS_S_GARBAGE_COLLECTED;
    }

    if (!m_pScriptManager || !m_pPerformance8)
    {
        Trace(1, "Error: IDirectMusicScript::Init must be called before IDirectMusicScript::EnumVariable.\n");
        return DMUS_E_NOT_INIT;
    }

    int cScriptItems = 0;
    HRESULT hr = m_pScriptManager->EnumItem(false, dwIndex, pwszName, &cScriptItems);
    if (FAILED(hr))
        return hr;

    if (hr == S_FALSE)
    {
        // There are also items in the script's container that we need to report.
        assert(dwIndex >= cScriptItems);
        hr = m_pContainerDispatch->EnumItem(dwIndex - cScriptItems, pwszName);
    }

    return hr;
}

STDMETHODIMP
CDirectMusicScript::ScriptTrackCallRoutine(
        WCHAR *pwszRoutineName,
        IDirectMusicSegmentState *pSegSt,
        DWORD dwVirtualTrackID,
        bool fErrorPMsgsEnabled,
        __int64 i64IntendedStartTime,
        DWORD dwIntendedStartTimeFlags)
{
    V_INAME(CDirectMusicScript::CallRoutine);
    V_BUFPTR_READ(pwszRoutineName, 2);
    V_INTERFACE(pSegSt);

    if (m_fZombie)
    {
        Trace(1, "Error: Script track attempted to call a routine after the script has been garbage collected. "
                    "It is invalid to continue using a script after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
                    "and then calling CollectGarbage or Release on the loader.");
        return DMUS_S_GARBAGE_COLLECTED;
    }

    SmartRef::CritSec CS(&m_CriticalSection);

    if (!m_pScriptManager || !m_pPerformance8)
    {
        Trace(1, "Error: Unitialized Script elements in an attempt to call a Script Routine.\n");
        return DMUS_E_NOT_INIT;
    }

    return m_pScriptManager->ScriptTrackCallRoutine(
                                pwszRoutineName,
                                pSegSt,
                                dwVirtualTrackID,
                                fErrorPMsgsEnabled,
                                i64IntendedStartTime,
                                dwIntendedStartTimeFlags);
}

STDMETHODIMP
CDirectMusicScript::GetTypeInfoCount(UINT *pctinfo)
{
    V_INAME(CDirectMusicScript::GetTypeInfoCount);
    V_PTR_WRITE(pctinfo, UINT);
    *pctinfo = 0;

    if (m_fZombie)
    {
        Trace(1, "Error: Call of IDirectMusicScript::GetTypeInfoCount after the script has been garbage collected. "
                    "It is invalid to continue using a script after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
                    "and then calling CollectGarbage or Release on the loader.");
        return DMUS_S_GARBAGE_COLLECTED;
    }

    return S_OK;
}

STDMETHODIMP
CDirectMusicScript::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo __RPC_FAR *__RPC_FAR *ppTInfo)
{
    *ppTInfo = NULL;
    return E_NOTIMPL;
}

STDMETHODIMP
CDirectMusicScript::GetIDsOfNames(
        REFIID riid,
        LPOLESTR __RPC_FAR *rgszNames,
        UINT cNames,
        LCID lcid,
        DISPID __RPC_FAR *rgDispId)
{
    if (m_fZombie)
    {
        if (rgDispId)
        {
            for (int i = 0; i < cNames; ++i)
            {
                rgDispId[i] = DISPID_UNKNOWN;
            }
        }
        Trace(1, "Error: Call of GetIDsOfNames after a script has been garbage collected. "
                    "It is invalid to continue using a script after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
                    "and then calling CollectGarbage or Release on the loader.");
        return DMUS_S_GARBAGE_COLLECTED;
    }
    
    if (!m_pScriptManager || !m_pPerformance8)
    {
        Trace(1, "Error: IDirectMusicScript::Init must be called before GetIDsOfNames.\n");
        return DMUS_E_NOT_INIT;
    }

    return m_pScriptManager->DispGetIDsOfNames(riid, rgszNames, cNames, lcid, rgDispId);
}

STDMETHODIMP
CDirectMusicScript::Invoke(
        DISPID dispIdMember,
        REFIID riid,
        LCID lcid,
        WORD wFlags,
        DISPPARAMS __RPC_FAR *pDispParams,
        VARIANT __RPC_FAR *pVarResult,
        EXCEPINFO __RPC_FAR *pExcepInfo,
        UINT __RPC_FAR *puArgErr)
{
    if (m_fZombie)
    {
        if (pVarResult)
            DMS_VariantInit(m_fUseOleAut, pVarResult);
        Trace(1, "Error: Call of Invoke after the script has been garbage collected. "
                    "It is invalid to continue using a script after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
                    "and then calling CollectGarbage or Release on the loader.");
        return DMUS_S_GARBAGE_COLLECTED;
    }
    
    if (!m_pScriptManager || !m_pPerformance8)
    {
        Trace(1, "Error: IDirectMusicScript::Init must be called before Invoke.\n");
        return DMUS_E_NOT_INIT;
    }

    return m_pScriptManager->DispInvoke(dispIdMember, riid, lcid, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr);
}

//////////////////////////////////////////////////////////////////////
// Methods that allow CActiveScriptManager access to private script interfaces

IDispatch *CDirectMusicScript::GetGlobalDispatch()
{
    assert(m_pGlobalDispatch);
    return m_pGlobalDispatch;
}