windows-nt/Source/XPSP1/NT/multimedia/directx/dmusic/dmscript/activescript.cpp

1077 lines
29 KiB
C++
Raw Permalink Normal View History

2020-09-26 03:20:57 -05:00
// 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 <objsafe.h>
//////////////////////////////////////////////////////////////////////
// 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::ThreadContextPair> 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<void **>(&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<void **>(&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<IActiveScriptSite*>(this);
}
else
{
*ppv = NULL;
return E_NOINTERFACE;
}
reinterpret_cast<IUnknown*>(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<WCHAR **>(&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<ITypeInfo> 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;
}