// Copyright (c) 1999 Microsoft Corporation. All rights reserved. // // Implementation of CActiveScriptManager. // #include "stdinc.h" #include "activescript.h" #include "dll.h" #include "oleaut.h" #include "dmscript.h" #include "authelper.h" #include "packexception.h" #include ////////////////////////////////////////////////////////////////////// // Global constants const LCID lcidUSEnglish = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT); const WCHAR g_wszGlobalDispatch[] = L"DirectMusic"; ////////////////////////////////////////////////////////////////////// // Static variables SmartRef::Vector CActiveScriptManager::ms_svecContext; ////////////////////////////////////////////////////////////////////// // ScriptNames HRESULT ScriptNames::Init(bool fUseOleAut, DWORD cNames) { m_prgbstr = new BSTR[cNames]; if (!m_prgbstr) return E_OUTOFMEMORY; ZeroMemory(m_prgbstr, sizeof(BSTR) * cNames); m_fUseOleAut = fUseOleAut; m_dwSize = cNames; return S_OK; } void ScriptNames::Clear() { if (m_prgbstr) { for (DWORD i = 0; i < m_dwSize; ++i) { DMS_SysFreeString(m_fUseOleAut, m_prgbstr[i]); } } delete[] m_prgbstr; } ////////////////////////////////////////////////////////////////////// // Public functions CActiveScriptManager::CActiveScriptManager( bool fUseOleAut, const WCHAR *pwszLanguage, const WCHAR *pwszSource, CDirectMusicScript *pParentScript, HRESULT *phr, DMUS_SCRIPT_ERRORINFO *pErrorInfo) : m_cRef(1), m_pParentScript(pParentScript), m_fUseOleAut(fUseOleAut), m_pActiveScript(NULL), m_pDispatchScript(NULL), m_bstrErrorSourceComponent(NULL), m_bstrErrorDescription(NULL), m_bstrErrorSourceLineText(NULL), m_bstrHelpFile(NULL), m_i64IntendedStartTime(0), m_dwIntendedStartTimeFlags(0) { LockModule(true); this->ClearErrorInfo(); IActiveScriptParse *pActiveScriptParse = NULL; if (!m_pParentScript) { assert(false); *phr = E_POINTER; goto Fail; } // Create the scripting engine CLSID clsid; *phr = CLSIDFromProgID(pwszLanguage, &clsid); if (FAILED(*phr)) goto Fail; *phr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, IID_IActiveScript, reinterpret_cast(&m_pActiveScript)); if (FAILED(*phr)) goto Fail; // Initialize the scripting engine { IObjectSafety* pSafety = NULL; if (SUCCEEDED(m_pActiveScript->QueryInterface(IID_IObjectSafety, (void**) &pSafety))) { DWORD dwSafetySupported, dwSafetyEnabled; // Get the interface safety otions if (SUCCEEDED(*phr = pSafety->GetInterfaceSafetyOptions(IID_IActiveScript, &dwSafetySupported, &dwSafetyEnabled))) { // Only allow objects which say they are safe for untrusted data, and // say that we require the use of a security manager. This gives us much // more control dwSafetyEnabled |= INTERFACESAFE_FOR_UNTRUSTED_DATA | INTERFACESAFE_FOR_UNTRUSTED_CALLER | INTERFACE_USES_DISPEX | INTERFACE_USES_SECURITY_MANAGER; *phr = pSafety->SetInterfaceSafetyOptions(IID_IActiveScript, dwSafetySupported, dwSafetyEnabled); } pSafety->Release(); if (FAILED(*phr)) goto Fail; } } *phr = m_pActiveScript->SetScriptSite(this); if (FAILED(*phr)) goto Fail; // Add the default objects *phr = m_pActiveScript->AddNamedItem(g_wszGlobalDispatch, SCRIPTITEM_ISVISIBLE | SCRIPTITEM_NOCODE | SCRIPTITEM_GLOBALMEMBERS); if (FAILED(*phr)) goto Fail; // Parse the script *phr = m_pActiveScript->QueryInterface(IID_IActiveScriptParse, reinterpret_cast(&pActiveScriptParse)); if (FAILED(*phr)) { if (*phr == E_NOINTERFACE) { Trace(1, "Error: Scripting engine '%S' does not support the IActiveScriptParse interface required for use with DirectMusic.\n", pwszLanguage); *phr = DMUS_E_SCRIPT_LANGUAGE_INCOMPATIBLE; } goto Fail; } *phr = pActiveScriptParse->InitNew(); if (FAILED(*phr)) goto Fail; EXCEPINFO exinfo; ZeroMemory(&exinfo, sizeof(EXCEPINFO)); *phr = pActiveScriptParse->ParseScriptText( pwszSource, NULL, NULL, NULL, NULL, 0, 0, NULL, &exinfo); if (*phr == DISP_E_EXCEPTION) this->ContributeErrorInfo(L"parsing script", L"", exinfo); if (FAILED(*phr)) goto Fail; SafeRelease(pActiveScriptParse); // No longer needed return; Fail: if (m_pActiveScript) m_pActiveScript->Close(); SafeRelease(pActiveScriptParse); SafeRelease(m_pActiveScript); *phr = this->ReturnErrorInfo(*phr, pErrorInfo); } HRESULT CActiveScriptManager::Start(DMUS_SCRIPT_ERRORINFO *pErrorInfo) { if (!m_pActiveScript) { Trace(1, "Error: Script element not initialized.\n"); return DMUS_E_NOT_INIT; } // Start the script running // Set context to this script (VBScript runs global code and could play something when it starts) CActiveScriptManager *pASM = NULL; HRESULT hr = CActiveScriptManager::SetCurrentContext(this, &pASM); if (FAILED(hr)) return hr; hr = m_pActiveScript->SetScriptState(SCRIPTSTATE_STARTED); // We don't need to sink any events CActiveScriptManager::SetCurrentContext(pASM, NULL); if (FAILED(hr)) goto Fail; assert(hr != S_FALSE); if (hr != S_OK) { assert(false); hr = DMUS_E_SCRIPT_LANGUAGE_INCOMPATIBLE; goto Fail; } hr = m_pActiveScript->GetScriptDispatch(NULL, &m_pDispatchScript); if (FAILED(hr)) goto Fail; return S_OK; Fail: if (m_pActiveScript) m_pActiveScript->Close(); SafeRelease(m_pActiveScript); SafeRelease(m_pDispatchScript); hr = this->ReturnErrorInfo(hr, pErrorInfo); return hr; } HRESULT CActiveScriptManager::CallRoutine( const WCHAR *pwszRoutineName, DMUS_SCRIPT_ERRORINFO *pErrorInfo) { if (!m_pDispatchScript) { Trace(1, "Error calling script routine: Script element not initialized.\n"); return DMUS_E_NOT_INIT; } DISPID dispid; HRESULT hr = this->GetIDOfName(pwszRoutineName, &dispid); if (hr == DISP_E_UNKNOWNNAME) { Trace(1, "Error: Attempt to call routine '%S' that is not defined in the script.\n", pwszRoutineName); return DMUS_E_SCRIPT_ROUTINE_NOT_FOUND; } if (FAILED(hr)) return hr; this->ClearErrorInfo(); DISPPARAMS dispparamsNoArgs = {NULL, NULL, 0, 0}; EXCEPINFO exinfo; ZeroMemory(&exinfo, sizeof(EXCEPINFO)); // Set context to this script CActiveScriptManager *pASM = NULL; hr = CActiveScriptManager::SetCurrentContext(this, &pASM); if (FAILED(hr)) return hr; hr = m_pDispatchScript->Invoke( dispid, m_fUseOleAut ? IID_NULL : g_guidInvokeWithoutOleaut, lcidUSEnglish, DISPATCH_METHOD, &dispparamsNoArgs, NULL, &exinfo, NULL); // Restore previous context (the routine could have been called from another script, // whose context needs to be restored). CActiveScriptManager::SetCurrentContext(pASM, NULL); if (hr == DISP_E_EXCEPTION) this->ContributeErrorInfo(L"calling routine ", pwszRoutineName, exinfo); return this->ReturnErrorInfo(hr, pErrorInfo); } HRESULT CActiveScriptManager::ScriptTrackCallRoutine( const WCHAR *pwszRoutineName, IDirectMusicSegmentState *pSegSt, DWORD dwVirtualTrackID, bool fErrorPMsgsEnabled, __int64 i64IntendedStartTime, DWORD dwIntendedStartTimeFlags) { DMUS_SCRIPT_ERRORINFO ErrorInfo; if (fErrorPMsgsEnabled) ZeroAndSize(&ErrorInfo); // record current timing context __int64 i64IntendedStartTime_PreCall = m_i64IntendedStartTime; DWORD dwIntendedStartTimeFlags_PreCall = m_dwIntendedStartTimeFlags; // set designated timing context (used by play/stop methods if called within the routine) m_i64IntendedStartTime = i64IntendedStartTime; m_dwIntendedStartTimeFlags = dwIntendedStartTimeFlags; HRESULT hr = CallRoutine(pwszRoutineName, &ErrorInfo); // Restore the previous timing context. // This is important because when R finishes it will resore both fields to the values set in the // constructor, which are music time 0. This setting means that routines called via IDirectMusicScript // will play segments at the current time. // It is also important because such calls can be nested. Assume that track T calls a script routine R // that plays a segment containing track T', which calls another script routine R'. Statements // in R should be associated with the time of R in T, but statements in R' get the time of R' in T'. m_i64IntendedStartTime = i64IntendedStartTime_PreCall; m_dwIntendedStartTimeFlags = dwIntendedStartTimeFlags_PreCall; if (fErrorPMsgsEnabled && hr == DMUS_E_SCRIPT_ERROR_IN_SCRIPT) { IDirectMusicPerformance *pPerf = m_pParentScript->GetPerformance(); FireScriptTrackErrorPMsg(pPerf, pSegSt, dwVirtualTrackID, &ErrorInfo); } return hr; } HRESULT CActiveScriptManager::SetVariable( const WCHAR *pwszVariableName, VARIANT varValue, bool fSetRef, DMUS_SCRIPT_ERRORINFO *pErrorInfo) { if (!m_pDispatchScript) { Trace(1, "Error setting script variable: Script element not initialized.\n"); return DMUS_E_NOT_INIT; } DISPID dispid; HRESULT hr = this->GetIDOfName(pwszVariableName, &dispid); if (hr == DISP_E_UNKNOWNNAME) { Trace(1, "Error: Attempt to set variable '%S' that is not defined in the script.\n", pwszVariableName); return DMUS_E_SCRIPT_VARIABLE_NOT_FOUND; } if (FAILED(hr)) return hr; this->ClearErrorInfo(); DISPID dispidPropPut = DISPID_PROPERTYPUT; DISPPARAMS dispparams; dispparams.rgvarg = &varValue; dispparams.rgdispidNamedArgs = &dispidPropPut; dispparams.cArgs = 1; dispparams.cNamedArgs = 1; EXCEPINFO exinfo; ZeroMemory(&exinfo, sizeof(EXCEPINFO)); hr = m_pDispatchScript->Invoke( dispid, m_fUseOleAut ? IID_NULL : g_guidInvokeWithoutOleaut, lcidUSEnglish, fSetRef ? DISPATCH_PROPERTYPUTREF : DISPATCH_PROPERTYPUT, &dispparams, NULL, &exinfo, NULL); if (hr == DISP_E_EXCEPTION) { this->ContributeErrorInfo(L"setting variable ", pwszVariableName, exinfo); // Check if it was more likely a malformed call to SetVariable rather than an error in the script, in which // case return a descriptive HRESULT rather than the textual error. bool fObject = varValue.vt == VT_DISPATCH || varValue.vt == VT_UNKNOWN; if (fObject) { if (!fSetRef) { // Theoretically an object could support the value property, which would allow it to be assigned by value. // (Not that any of our built-in objects currently do this.) // But in this case we know that the set failed, so probably this is the fault of the caller, who forgot to use // fSetRef when setting an object. this->ClearErrorInfo(); return DMUS_E_SCRIPT_VALUE_NOT_SUPPORTED; } } else { if (fSetRef) { // Setting by reference without using an object. this->ClearErrorInfo(); return DMUS_E_SCRIPT_NOT_A_REFERENCE; } } } return this->ReturnErrorInfo(hr, pErrorInfo); } HRESULT CActiveScriptManager::GetVariable(const WCHAR *pwszVariableName, VARIANT *pvarValue, DMUS_SCRIPT_ERRORINFO *pErrorInfo) { if (!m_pDispatchScript) { Trace(1, "Error getting script variable: Script element not initialized.\n"); return DMUS_E_NOT_INIT; } assert(pvarValue->vt == VT_EMPTY); DISPID dispid; HRESULT hr = this->GetIDOfName(pwszVariableName, &dispid); if (hr == DISP_E_UNKNOWNNAME) return DMUS_E_SCRIPT_VARIABLE_NOT_FOUND; if (FAILED(hr)) return hr; this->ClearErrorInfo(); DISPPARAMS dispparamsNoArgs = {NULL, NULL, 0, 0}; EXCEPINFO exinfo; ZeroMemory(&exinfo, sizeof(EXCEPINFO)); hr = m_pDispatchScript->Invoke( dispid, m_fUseOleAut ? IID_NULL : g_guidInvokeWithoutOleaut, lcidUSEnglish, DISPATCH_PROPERTYGET, &dispparamsNoArgs, pvarValue, &exinfo, NULL); if (hr == DISP_E_EXCEPTION) this->ContributeErrorInfo(L"getting variable ", pwszVariableName, exinfo); return this->ReturnErrorInfo(hr, pErrorInfo); } HRESULT CActiveScriptManager::EnumItem(bool fRoutine, DWORD dwIndex, WCHAR *pwszName, int *pcItems) { HRESULT hr = this->EnsureEnumItemsCached(fRoutine); if (FAILED(hr)) return hr; ScriptNames &snames = fRoutine ? m_snamesRoutines : m_snamesVariables; DWORD cNames = snames.size(); // snames was allocated for the size of the most items there could be as reported by the script's type info. // However, the global "DirectMusic" variable may have been skipped, leaving a NULL entry at the end of snames. if (cNames > 0 && !snames[cNames - 1]) --cNames; if (pcItems) *pcItems = cNames; if (dwIndex >= cNames) return S_FALSE; const BSTR bstrName = snames[dwIndex]; if (!bstrName) { assert(false); return S_FALSE; } return wcsTruncatedCopy(pwszName, bstrName, MAX_PATH); } HRESULT CActiveScriptManager::DispGetIDsOfNames(REFIID riid, LPOLESTR __RPC_FAR *rgszNames, UINT cNames, LCID lcid, DISPID __RPC_FAR *rgDispId) { if (!m_pDispatchScript) { Trace(1, "Error: Script element not initialized.\n"); return DMUS_E_NOT_INIT; } // handle the dummy load method HRESULT hr = AutLoadDispatchGetIDsOfNames(riid, rgszNames, cNames, lcid, rgDispId); if (SUCCEEDED(hr)) return hr; // otherwise defer to the scripting engine return m_pDispatchScript->GetIDsOfNames(riid, rgszNames, cNames, lcid, rgDispId); } HRESULT CActiveScriptManager::DispInvoke(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_pDispatchScript) { Trace(1, "Error: Script element not initialized.\n"); return DMUS_E_NOT_INIT; } // handle the dummy load method HRESULT hr = AutLoadDispatchInvoke(NULL, dispIdMember, riid, lcid, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr); if (SUCCEEDED(hr)) return hr; // otherwise defer to the scripting engine... CActiveScriptManager *pASM = NULL; hr = CActiveScriptManager::SetCurrentContext(this, &pASM); if (FAILED(hr)) return hr; // If this is a property set of an object then we need to report it to garbage collecting loader if present. // Note that we do this before actually setting the property with Invoke. We do this because if the garbage collector // fails to track the reference then it won't necessarily keep the target object alive and we don't want to create // a dangling reference in the script. if (wFlags & DISPATCH_PROPERTYPUTREF && pDispParams && pDispParams->cArgs == 1) { IDirectMusicLoader8P *pLoader8P = m_pParentScript->GetLoader8P(); VARIANT &var = pDispParams->rgvarg[0]; if (pLoader8P && (var.vt == VT_UNKNOWN || var.vt == VT_DISPATCH)) { hr = pLoader8P->ReportDynamicallyReferencedObject(m_pParentScript, var.vt == VT_UNKNOWN ? var.punkVal : var.pdispVal); if (FAILED(hr)) return hr; } } hr = m_pDispatchScript->Invoke(dispIdMember, riid, lcid, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr); bool fExceptionUsingOleAut = !!(riid != g_guidInvokeWithoutOleaut); if (hr == 0x80020101 && pExcepInfo) // supposedly this is SCRIPT_E_REPORTED { // See KB article ID: Q247784, INFO: '80020101' Returned From Some ActiveX Scripting Methods. // Sometimes VBScript just returns this undocumented HRESULT, which means the error has already been // reported via OnScriptError. Since it then doesn't give us the exception info via pExcepInfo, we have // to take the info we saves from OnScriptError and put it back in. assert(fExceptionUsingOleAut && m_fUseOleAut); // We don't expect this to happen with a custom scripting engine. assert(!pExcepInfo->bstrSource && !pExcepInfo->bstrDescription && !pExcepInfo->bstrHelpFile); // We don't expect this will happen when the exception info has been filled in. pExcepInfo->scode = m_hrError; DMS_SysFreeString(fExceptionUsingOleAut, pExcepInfo->bstrSource); pExcepInfo->bstrSource = DMS_SysAllocString(fExceptionUsingOleAut, m_bstrErrorSourceComponent); DMS_SysFreeString(fExceptionUsingOleAut, pExcepInfo->bstrDescription); pExcepInfo->bstrDescription = DMS_SysAllocString(fExceptionUsingOleAut, m_bstrErrorDescription); DMS_SysFreeString(fExceptionUsingOleAut, pExcepInfo->bstrHelpFile); pExcepInfo->bstrHelpFile = DMS_SysAllocString(fExceptionUsingOleAut, m_bstrHelpFile); hr = DISP_E_EXCEPTION; } if (hr == DISP_E_EXCEPTION) { // Hack: See packexception.h for more info PackExceptionFileAndLine(fExceptionUsingOleAut, pExcepInfo, m_pParentScript->GetFilename(), m_fError ? &m_ulErrorLineNumber : NULL); } CActiveScriptManager::SetCurrentContext(pASM, NULL); return hr; } void CActiveScriptManager::Close() { if (!m_pActiveScript) { assert(false); // Close being called if initialization failed. Or Close was called twice. Or else m_pActiveScript is getting cleared prematurely somehow. return; } HRESULT hr = m_pActiveScript->Close(); assert(SUCCEEDED(hr) && hr != S_FALSE); SafeRelease(m_pDispatchScript); SafeRelease(m_pActiveScript); } ////////////////////////////////////////////////////////////////////// // IUnknown STDMETHODIMP CActiveScriptManager::QueryInterface(const IID &iid, void **ppv) { V_INAME(CActiveScriptManager::QueryInterface); V_PTRPTR_WRITE(ppv); V_REFGUID(iid); if (iid == IID_IUnknown || iid == IID_IActiveScriptSite) { *ppv = static_cast(this); } else { *ppv = NULL; return E_NOINTERFACE; } reinterpret_cast(this)->AddRef(); return S_OK; } STDMETHODIMP_(ULONG) CActiveScriptManager::AddRef() { return InterlockedIncrement(&m_cRef); } STDMETHODIMP_(ULONG) CActiveScriptManager::Release() { if (!InterlockedDecrement(&m_cRef)) { SafeRelease(m_pDispatchScript); SafeRelease(m_pActiveScript); delete this; LockModule(false); return 0; } return m_cRef; } ////////////////////////////////////////////////////////////////////// // IActiveScriptSite STDMETHODIMP CActiveScriptManager::GetLCID(/* [out] */ LCID __RPC_FAR *plcid) { V_INAME(CActiveScriptManager::GetLCID); V_PTR_WRITE(plcid, LCID); *plcid = lcidUSEnglish; return S_OK; } STDMETHODIMP CActiveScriptManager::GetItemInfo( /* [in] */ LPCOLESTR pstrName, /* [in] */ DWORD dwReturnMask, /* [out] */ IUnknown __RPC_FAR *__RPC_FAR *ppiunkItem, /* [out] */ ITypeInfo __RPC_FAR *__RPC_FAR *ppti) { V_INAME(CActiveScriptManager::GetLCID); V_PTR_WRITE_OPT(ppti, ITypeInfo*); bool fGetUnknown = !!(dwReturnMask | SCRIPTINFO_IUNKNOWN); if (fGetUnknown || ppiunkItem) { V_PTR_WRITE(ppiunkItem, IUnknown*); } if (ppiunkItem) *ppiunkItem = NULL; if (ppti) *ppti = NULL; if (0 != wcscmp(g_wszGlobalDispatch, pstrName)) { assert(false); // we should only be asked about the global object return TYPE_E_ELEMENTNOTFOUND; } if (fGetUnknown) { IDispatch *pDispGlobal = m_pParentScript->GetGlobalDispatch(); pDispGlobal->AddRef(); *ppiunkItem = pDispGlobal; } return S_OK; } STDMETHODIMP CActiveScriptManager::GetDocVersionString(/* [out] */ BSTR __RPC_FAR *pbstrVersion) { return E_NOTIMPL; // Not an issue for our scripts that don't persist their state and aren't edited at runtime. } STDMETHODIMP CActiveScriptManager::OnScriptTerminate( /* [in] */ const VARIANT __RPC_FAR *pvarResult, /* [in] */ const EXCEPINFO __RPC_FAR *pexcepinfo) { if (pexcepinfo) this->ContributeErrorInfo(L"terminating script", L"", *pexcepinfo); return S_OK; } STDMETHODIMP CActiveScriptManager::OnStateChange(/* [in] */ SCRIPTSTATE ssScriptState) { return S_OK; } STDMETHODIMP CActiveScriptManager::OnScriptError(/* [in] */ IActiveScriptError __RPC_FAR *pscripterror) { V_INAME(CActiveScriptManager::OnScriptError); V_INTERFACE(pscripterror); BSTR bstrSource = NULL; pscripterror->GetSourceLineText(&bstrSource); // this may fail, in which case the source text will remain blank ULONG ulLine = 0; LONG lChar = 0; HRESULT hr = pscripterror->GetSourcePosition(NULL, &ulLine, &lChar); assert(SUCCEEDED(hr)); EXCEPINFO exinfo; ZeroMemory(&exinfo, sizeof(EXCEPINFO)); hr = pscripterror->GetExceptionInfo(&exinfo); assert(SUCCEEDED(hr)); this->SetErrorInfo(ulLine, lChar, bstrSource, exinfo); return S_OK; } STDMETHODIMP CActiveScriptManager::OnEnterScript() { return S_OK; } STDMETHODIMP CActiveScriptManager::OnLeaveScript() { return S_OK; } IDirectMusicPerformance8 * CActiveScriptManager::GetCurrentPerformanceNoAssertWEAK() { CActiveScriptManager *pASM = CActiveScriptManager::GetCurrentContext(); if (!pASM) return NULL; return pASM->m_pParentScript->GetPerformance(); } IDirectMusicObject * CActiveScriptManager::GetCurrentScriptObjectWEAK() { CActiveScriptManager *pASM = CActiveScriptManager::GetCurrentContext(); if (!pASM) { assert(false); return NULL; } assert(pASM->m_pParentScript); return pASM->m_pParentScript; } IDirectMusicComposer8 *CActiveScriptManager::GetComposerWEAK() { CActiveScriptManager *pASM = CActiveScriptManager::GetCurrentContext(); if (!pASM) { assert(false); return NULL; } assert(pASM->m_pParentScript); return pASM->m_pParentScript->GetComposer(); } void CActiveScriptManager::GetCurrentTimingContext(__int64 *pi64IntendedStartTime, DWORD *pdwIntendedStartTimeFlags) { CActiveScriptManager *pASM = CActiveScriptManager::GetCurrentContext(); if (!pASM) { assert(false); *pi64IntendedStartTime = 0; *pdwIntendedStartTimeFlags = 0; } else { *pi64IntendedStartTime = pASM->m_i64IntendedStartTime; *pdwIntendedStartTimeFlags = pASM->m_dwIntendedStartTimeFlags; } } ////////////////////////////////////////////////////////////////////// // Private functions HRESULT CActiveScriptManager::GetIDOfName(const WCHAR *pwszName, DISPID *pdispid) { V_INAME(CDirectMusicScript::GetIDOfName); V_BUFPTR_READ(pwszName, 2); V_PTR_WRITE(pdispid, DISPID); if (!m_pDispatchScript) { assert(false); return DMUS_E_NOT_INIT; } HRESULT hr = m_pDispatchScript->GetIDsOfNames( IID_NULL, const_cast(&pwszName), 1, lcidUSEnglish, pdispid); return hr; } // Clears the error info and frees all cached BSTRs. void CActiveScriptManager::ClearErrorInfo() { m_fError = false; if (m_bstrErrorSourceComponent) { DMS_SysFreeString(m_fUseOleAut, m_bstrErrorSourceComponent); m_bstrErrorSourceComponent = NULL; } if (m_bstrErrorDescription) { DMS_SysFreeString(m_fUseOleAut, m_bstrErrorDescription); m_bstrErrorDescription = NULL; } if (m_bstrErrorSourceLineText) { DMS_SysFreeString(m_fUseOleAut, m_bstrErrorSourceLineText); m_bstrErrorSourceLineText = NULL; } if (m_bstrHelpFile) { DMS_SysFreeString(m_fUseOleAut, m_bstrHelpFile); m_bstrHelpFile = NULL; } } // Saves the passed error values. // Assumes ownership of the BSTRs so don't use them after this call since they may be freed! void CActiveScriptManager::SetErrorInfo( ULONG ulLineNumber, LONG ichCharPosition, BSTR bstrSourceLine, const EXCEPINFO &excepinfo) { this->ClearErrorInfo(); m_fError = true; m_hrError = excepinfo.scode; m_ulErrorLineNumber = ulLineNumber; m_ichErrorCharPosition = ichCharPosition; m_bstrErrorSourceComponent = excepinfo.bstrSource; m_bstrErrorDescription = excepinfo.bstrDescription; m_bstrErrorSourceLineText = bstrSourceLine; m_bstrHelpFile = excepinfo.bstrHelpFile; } // Sometimes a EXCEPINFO is returned when calling Invoke or on script termination. Although // there is no source code information, we still want to do our best to set info about // the error. If OnScriptError has already been called, then calling this function has // no effect, since we prefer that information. // Assumes ownership of the BSTRs so don't use them after this call since they may be freed! void CActiveScriptManager::ContributeErrorInfo( const WCHAR *pwszActivity, const WCHAR *pwszSubject, const EXCEPINFO &excepinfo) { if (m_fError) { // Error info already set. Just clear the BSTRs and bail. if (excepinfo.bstrSource) DMS_SysFreeString(m_fUseOleAut, excepinfo.bstrSource); if (excepinfo.bstrDescription) DMS_SysFreeString(m_fUseOleAut, excepinfo.bstrDescription); if (excepinfo.bstrHelpFile) DMS_SysFreeString(m_fUseOleAut, excepinfo.bstrHelpFile); return; } this->SetErrorInfo(0, 0, NULL, excepinfo); } // If no error occurred, hr is returned unchanged and pErrorInfo is unaffected. // If an error did occur, DMUS_E_SCRIPT_ERROR_IN_SCRIPT is returned, the error // information is saved into pErrorInfo (if nonnull), and the error info is // cleared for next time. HRESULT CActiveScriptManager::ReturnErrorInfo(HRESULT hr, DMUS_SCRIPT_ERRORINFO *pErrorInfo) { if (!m_fError) return hr; assert(FAILED(hr)); if (pErrorInfo) { // We'll fill in a structure with the error info and then copy it to pErrorInfo. // This is done because it will make things simpler if more fields are added // to DMUS_SCRIPT_ERRORINFO in the future. DMUS_SCRIPT_ERRORINFO dmei; ZeroAndSize(&dmei); dmei.hr = m_hrError; dmei.ulLineNumber = m_ulErrorLineNumber; dmei.ichCharPosition = m_ichErrorCharPosition; if (m_bstrErrorDescription) { // Hack: See packexception.h for more info UnpackExceptionFileAndLine(m_bstrErrorDescription, &dmei); } // The IActiveScript interfaces return zero-based line and column numbers, but we want // to return them from IDirectMusicScript using a one-based line and column that is // natural for users. ++dmei.ulLineNumber; ++dmei.ichCharPosition; if (dmei.wszSourceFile[0] == L'\0') { // if there was no filename packaged in the description, use this script's filename const WCHAR *pwszFilename = m_pParentScript->GetFilename(); if (pwszFilename) wcsTruncatedCopy(dmei.wszSourceFile, pwszFilename, DMUS_MAX_FILENAME); } if (m_bstrErrorSourceComponent) wcsTruncatedCopy(dmei.wszSourceComponent, m_bstrErrorSourceComponent, DMUS_MAX_FILENAME); if (m_bstrErrorSourceLineText) wcsTruncatedCopy(dmei.wszSourceLineText, m_bstrErrorSourceLineText, DMUS_MAX_FILENAME); CopySizedStruct(pErrorInfo, &dmei); } this->ClearErrorInfo(); #ifdef DBG if (pErrorInfo) { Trace(1, "Error: Script error in %S, line %u, column %i, near %S. %S: %S. Error code 0x%08X.\n", pErrorInfo->wszSourceFile, pErrorInfo->ulLineNumber, pErrorInfo->ichCharPosition, pErrorInfo->wszSourceLineText, pErrorInfo->wszSourceComponent, pErrorInfo->wszDescription, pErrorInfo->hr); } else { Trace(1, "Error: Unknown Script error.\n"); } #endif return DMUS_E_SCRIPT_ERROR_IN_SCRIPT; } CActiveScriptManager *CActiveScriptManager::GetCurrentContext() { DWORD dwThreadId = GetCurrentThreadId(); UINT uiSize = ms_svecContext.size(); for (UINT i = 0; i < uiSize; ++i) { if (ms_svecContext[i].dwThreadId == dwThreadId) break; } if (i == uiSize) return NULL; return ms_svecContext[i].pActiveScriptManager; } HRESULT CActiveScriptManager::SetCurrentContext(CActiveScriptManager *pActiveScriptManager, CActiveScriptManager **ppActiveScriptManagerPrevious) { if (ppActiveScriptManagerPrevious) *ppActiveScriptManagerPrevious = NULL; DWORD dwThreadId = GetCurrentThreadId(); UINT uiSize = ms_svecContext.size(); for (UINT i = 0; i < uiSize; ++i) { if (ms_svecContext[i].dwThreadId == dwThreadId) break; } if (i == uiSize) { // add an entry if (!ms_svecContext.AccessTo(i)) return E_OUTOFMEMORY; } ThreadContextPair &tcp = ms_svecContext[i]; if (i == uiSize) { // initialize the new entry tcp.dwThreadId = dwThreadId; tcp.pActiveScriptManager = NULL; } if (ppActiveScriptManagerPrevious) *ppActiveScriptManagerPrevious = tcp.pActiveScriptManager; tcp.pActiveScriptManager = pActiveScriptManager; return S_OK; } HRESULT CActiveScriptManager::EnsureEnumItemsCached(bool fRoutine) { if (!m_pDispatchScript) { Trace(1, "Error: Script element not initialized.\n"); return DMUS_E_NOT_INIT; } ScriptNames &snames = fRoutine ? m_snamesRoutines : m_snamesVariables; if (snames) return S_OK; UINT uiTypeInfoCount = 0; HRESULT hr = m_pDispatchScript->GetTypeInfoCount(&uiTypeInfoCount); if (SUCCEEDED(hr) && !uiTypeInfoCount) hr = E_NOTIMPL; if (FAILED(hr)) return hr; SmartRef::ComPtr scomITypeInfo; hr = m_pDispatchScript->GetTypeInfo(0, lcidUSEnglish, &scomITypeInfo); if (FAILED(hr)) return hr; TYPEATTR *pattr = NULL; hr = scomITypeInfo->GetTypeAttr(&pattr); if (FAILED(hr)) return hr; UINT cMaxItems = fRoutine ? pattr->cFuncs : pattr->cVars; hr = snames.Init(m_fUseOleAut, cMaxItems); if (FAILED(hr)) return hr; // Iterate over the items DWORD dwCurIndex = 0; // Index position of next name to be saved in our cache for (UINT i = 0; i < cMaxItems; ++i) { FUNCDESC *pfunc = NULL; VARDESC *pvar = NULL; MEMBERID memid = DISPID_UNKNOWN; if (fRoutine) { hr = scomITypeInfo->GetFuncDesc(i, &pfunc); if (FAILED(hr)) break; if (pfunc->funckind == FUNC_DISPATCH && pfunc->invkind == INVOKE_FUNC && pfunc->cParams == 0) memid = pfunc->memid; } else { hr = scomITypeInfo->GetVarDesc(i, &pvar); if (SUCCEEDED(hr) && pvar->varkind == VAR_DISPATCH) memid = pvar->memid; } if (memid != DISPID_UNKNOWN) { UINT cNames = 0; BSTR bstrName = NULL; hr = scomITypeInfo->GetNames(memid, &bstrName, 1, &cNames); if (SUCCEEDED(hr) && cNames == 1 && (fRoutine || 0 != wcscmp(bstrName, g_wszGlobalDispatch))) snames[dwCurIndex++] = bstrName; else DMS_SysFreeString(m_fUseOleAut, bstrName); } if (fRoutine) scomITypeInfo->ReleaseFuncDesc(pfunc); else scomITypeInfo->ReleaseVarDesc(pvar); } scomITypeInfo->ReleaseTypeAttr(pattr); return hr; }