// // 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(this); } else if (iid == IID_IDirectMusicScriptPrivate) { *ppv = static_cast(this); } else if (iid == IID_IDirectMusicObject) { *ppv = static_cast(this); } else if (iid == IID_IDirectMusicObjectP) { *ppv = static_cast(this); } else if (iid == IID_IPersistStream) { *ppv = static_cast(this); } else if (iid == IID_IPersist) { *ppv = static_cast(this); } else if (iid == IID_IDispatch) { *ppv = static_cast(this); } else { *ppv = NULL; return E_NOINTERFACE; } reinterpret_cast(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 scomLoader; hr = pStream->QueryInterface(IID_IDirectMusicGetLoader, reinterpret_cast(&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(&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(&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(&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 scomPerformance8; HRESULT hr = pPerformance->QueryInterface(IID_IDirectMusicPerformance8, reinterpret_cast(&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 scomDispPerformance = NULL; hr = pPerformance->QueryInterface(IID_IDispatch, reinterpret_cast(&scomDispPerformance)); if (FAILED(hr)) return hr; // Get a composer object hr = CoCreateInstance(CLSID_DirectMusicComposer, NULL, CLSCTX_INPROC_SERVER, IID_IDirectMusicComposer8, reinterpret_cast(&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; }