1077 lines
30 KiB
C++
1077 lines
30 KiB
C++
// 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;
|
|
}
|