|
|
// Copyright (c) 1999 Microsoft Corporation. All rights reserved.
//
// Implementation of Executor.
//
#include "stdinc.h"
#include "enginc.h"
#include "engexec.h"
#include "math.h"
#include "packexception.h"
//////////////////////////////////////////////////////////////////////
// CallStack
HRESULT CallStack::Push(UINT i) { UINT iInitialSize = m_vec.size(); if (!m_vec.AccessTo(m_iNext + i - 1)) return E_OUTOFMEMORY; UINT iNewNext = m_iNext + i; for (UINT iInit = std::_MAX<UINT>(m_iNext, iInitialSize); iInit < iNewNext; ++iInit) DMS_VariantInit(g_fUseOleAut, &m_vec[iInit]);
m_iNext = iNewNext; return S_OK; }
void CallStack::PopTo(UINT i) { for (UINT iInit = i; iInit < m_iNext; ++iInit) DMS_VariantClear(g_fUseOleAut, &m_vec[iInit]);
m_iNext = std::_MIN<UINT>(m_iNext, i); }
//////////////////////////////////////////////////////////////////////
// Executor
Executor::Executor(Script &script, IDispatch *pGlobalDispatch) : m_fInitialized(false), m_script(script), m_scomGlobalDispatch(pGlobalDispatch) { DMS_VariantInit(g_fUseOleAut, &m_varEmpty); }
Executor::~Executor() { m_stack.PopTo(0); // clear any varients on the stack that might be holding refs
}
HRESULT Executor::SetGlobal(Variables::index ivar, const VARIANT &varValue, bool fPutRef, EXCEPINFO *pExcepInfo) { HRESULT hr = EnsureInitialized(); if (FAILED(hr)) return hr;
hr = ErrorIfImproperRef(varValue, fPutRef, m_script.globals[ivar].istrIdentifier, pExcepInfo); if (FAILED(hr)) return hr;
assert(ivar <= m_script.globals.Next()); return DMS_VariantCopy(g_fUseOleAut, &m_stack[ivar], &varValue); }
const VARIANT & Executor::GetGlobal(Variables::index ivar) { if (!m_fInitialized) { // No variable gets or routine calls have been performed yet (or they failed).
// But we don't want to return an error here. Since nothing's been used yet, the correct
// thing to do is to return an empty value.
return m_varEmpty; }
assert(ivar <= m_script.globals.Next()); return m_stack[ivar]; }
HRESULT Executor::ExecRoutine(Routines::index irtn, EXCEPINFO *pExcepInfo) { HRESULT hr = EnsureInitialized(); if (FAILED(hr)) return hr;
Routine r = m_script.routines[irtn];
UINT iLocals = m_stack.Next(); hr = m_stack.Push(r.ivarNextLocal); if (FAILED(hr)) return hr;
hr = ExecStatements(r.istmtBody, pExcepInfo, iLocals); m_stack.PopTo(iLocals); return hr; }
HRESULT Executor::EnsureInitialized() { if (m_fInitialized) return S_OK;
// we'll keep the global variables right at the bottom of the stack
// this function ensures that they get pushed on before any operations that use them
HRESULT hr = m_stack.Push(m_script.globals.Next()); if (FAILED(hr)) return hr;
// Also set the first items to the build in constant values True, False, and Nothing.
// See also engparse.cpp which creates these global variables before parsing each script.
if (m_stack.Next() < 3) { assert(false); return E_UNEXPECTED; } VARIANT &vTrue = m_stack[0]; vTrue.vt = VT_I4; vTrue.lVal = VARIANT_TRUE; VARIANT &vFalse = m_stack[1]; vFalse.vt = VT_I4; vFalse.lVal = VARIANT_FALSE; VARIANT &vNothing = m_stack[2]; vNothing.vt = VT_UNKNOWN; vNothing.punkVal = NULL; m_fInitialized = true; return S_OK; }
HRESULT Executor::Error(EXCEPINFO *pExcepInfo, bool fOperation, const WCHAR *pwszBeginning, const char *paszMiddle, const WCHAR *pwszEnd) { if (!pExcepInfo) { assert(false); // our script host should always request error info
return DISP_E_EXCEPTION; }
// NULL for beginning, middle, or end treated as empty string
if (!pwszBeginning) pwszBeginning = L""; if (!paszMiddle) paszMiddle = ""; if (!pwszEnd) pwszEnd = L"";
pExcepInfo->wCode = 0; pExcepInfo->wReserved = 0; pExcepInfo->bstrSource = DMS_SysAllocString(g_fUseOleAut, fOperation ? L"Microsoft AudioVBScript Operation Failed" : L"Microsoft AudioVBScript Runtime Error");
SmartRef::WString wstrMiddle = paszMiddle; WCHAR *pwszDescription = NULL; if (wstrMiddle) { pwszDescription = new WCHAR[wcslen(pwszBeginning) + wcslen(wstrMiddle) + wcslen(pwszEnd) + 1]; } if (!pwszDescription) { // Oh well. Just return no description if we're out of memory.
pExcepInfo->bstrDescription = NULL; } else { wcscpy(pwszDescription, pwszBeginning); wcscat(pwszDescription, wstrMiddle); wcscat(pwszDescription, pwszEnd); pExcepInfo->bstrDescription = DMS_SysAllocString(g_fUseOleAut, pwszDescription); delete[] pwszDescription; } pExcepInfo->bstrHelpFile = NULL; pExcepInfo->pvReserved = NULL; pExcepInfo->pfnDeferredFillIn = NULL; pExcepInfo->scode = fOperation ? DMUS_E_AUDIOVBSCRIPT_OPERATIONFAILURE : DMUS_E_AUDIOVBSCRIPT_RUNTIMEERROR;
return DISP_E_EXCEPTION; }
HRESULT Executor::ErrorIfImproperRef(const VARIANT &v, bool fRef, Strings::index istrIdentifier, EXCEPINFO *pExcepInfo) { bool fIsObject = v.vt == VT_DISPATCH || v.vt == VT_UNKNOWN; if (fRef != fIsObject) { if (fRef) return ErrorObjectRequired(istrIdentifier, pExcepInfo); else return Error(pExcepInfo, false, L"Type mismatch: '", m_script.strings[istrIdentifier], L"'. Likely cause is missing Set statement."); }
return S_OK; }
// Check for the error HRESULTs returned by IDispatch::Invoke. Those that we expect to occur in AudioVBScript need to
// be converted into exception (DISP_E_EXCEPTION) so that the user gets a nice error message.
// The first parameter lets us know the kind of Invoke call that was made (property get, property set, function/sub call)
// so that we can tailor the message.
HRESULT Executor::ErrorIfInvokeProblem(DispatchOperationType e, HRESULT hr, Strings::index istrIdentifier, EXCEPINFO *pExcepInfo) { if (SUCCEEDED(hr) || HRESULT_FACILITY(hr) != FACILITY_DISPATCH || hr == DISP_E_EXCEPTION) return hr;
const char *pszName = m_script.strings[istrIdentifier]; if (hr == DISP_E_BADPARAMCOUNT) { // This can happen with a _call (obviously) and also with a get because property gets are also treated as function
// calls with no arguments. "x=GetMasterVolume" is valid but "x=Trace" would produce this error. But I can't
// see that this should occur with property sets.
assert(e == _get || e == _call);
return Error(pExcepInfo, false, L"Wrong number of parameters in call to '", pszName, L"'"); } else if (hr == DISP_E_MEMBERNOTFOUND) { if (e == _call) { // Because Invoke was called, GetIDsOfNames must have succeeded, so the thing's name exists
// but it must not be a method.
return Error(pExcepInfo, false, L"Type mismatch: '", pszName, L"' is not a routine or method"); } else if (e == _put || e == _putref) { return Error(pExcepInfo, false, L"Type mismatch: '", pszName, L"' is not a variable or is a read-only property"); } else { // As mentioned above, a property get can be treated as either gets or function calls so they
// shouldn't fail in this way.
assert(false); } } else if (hr == DISP_E_TYPEMISMATCH) { // This indicates that one of the parameters was of the wrong type.
if (e == _call) { return Error(pExcepInfo, false, L"Type mismatch: a parameter in call to '", pszName, L"' is not of the expected type"); } else if (e == _put || e == _putref) { return Error(pExcepInfo, false, L"Type mismatch: value assigned to '", pszName, L"' is not of the expected type"); } else { // Property gets don't have any parameters so this shouldn't happen.
assert(false); } } else if (hr == DISP_E_PARAMNOTOPTIONAL) { if (e == _call) { return Error(pExcepInfo, false, L"A required parameter was omitted in call to '", pszName, L"'"); } else { // Only calls should send an optional parameters.
assert(false); } }
// The other errors shouldn't normally occur in AudioVBScript. They could occur if someone was
// doing something ususual in a custom IDispatch interface, but we'll consider them exceptional cases and
// just return the error HRESULT (meaning the user won't get a friendly text message). Assert so we'll
// find out if there are regular cases where this is happening in our testing.
assert(false);
// DISP_E_BADVARTYPE: We just use standard variant types.
// DISP_E_NONAMEDARGS: We don't do named args.
// DISP_E_OVERFLOW: AudioVBScript uses VT_I4 and so do our DMusic dispatch interfaces.
// DISP_E_PARAMNOTFOUND: Only applies with named args.
// DISP_E_UNKNOWNINTERFACE, DISP_E_UNKNOWNLCID: AudioVBScript uses calling convention and locale matching the DMusic dispatch interfaces.
return hr; }
HRESULT Executor::ExecStatements(Statements::index istmt, EXCEPINFO *pExcepInfo, UINT iLocals) { HRESULT hr = S_OK;
for (Statements::index istmtCur = istmt; /* ever */; ++istmtCur) { // �� Check if this generates fast retail code. If not, walk a pointer instead of using the index.
Statement s = m_script.statements[istmtCur]; switch (s.k) { case Statement::_end: return hr;
case Statement::_asgn: hr = ExecAssignment(s.iasgn, pExcepInfo, iLocals); break;
case Statement::_if: hr = ExecIf(s.iif, pExcepInfo, iLocals); istmtCur = s.istmtIfTail - 1; break;
case Statement::_call: hr = ExecCall(s.icall, false, pExcepInfo, iLocals); break; }
if (FAILED(hr)) { if (hr == DISP_E_EXCEPTION) { // Save the statement's line number in the exception info.
// Hack: See packexception.h for more info
ULONG ulLine = s.iLine - 1; // The IActiveScript interfaces expects zero-based line and column numbers while we have them one-based.
PackExceptionFileAndLine(g_fUseOleAut, pExcepInfo, NULL, &ulLine); }
return hr; } } }
HRESULT Executor::ExecAssignment(Assignments::index iasgn, EXCEPINFO *pExcepInfo, UINT iLocals) { Assignment a = m_script.asgns[iasgn];
VARIANT var; DMS_VariantInit(g_fUseOleAut, &var); HRESULT hr = EvalExpression(var, a.iexprRHS, pExcepInfo, iLocals); if (FAILED(hr)) return hr;
hr = SetVariableReference(a.fSet, a.ivarrefLHS, var, pExcepInfo, iLocals); DMS_VariantClear(g_fUseOleAut, &var); if (FAILED(hr)) return hr;
return S_OK; }
HRESULT Executor::ExecIf(IfBlocks::index iif, EXCEPINFO *pExcepInfo, UINT iLocals) { for (IfBlocks::index i = iif; /* ever */; ++i) { IfBlock &ib = m_script.ifs[i]; if (ib.k == IfBlock::_end) return S_OK;
bool fMatch = true; // default to true because an else block always matches
if (ib.k == IfBlock::_cond) { // if the condition isn't true, set match to false
SmartVariant svar; EvalExpression(svar, ib.iexprCondition, pExcepInfo, iLocals);
VARTYPE vt = static_cast<VARIANT&>(svar).vt; if (vt != VT_I4) { if (vt == VT_BSTR) return Error(pExcepInfo, false, L"Type mismatch: the condition of an if statement evaluated as a string where a numeric True/False value was expected", NULL, NULL); else if (vt == VT_UNKNOWN || vt == VT_DISPATCH) return Error(pExcepInfo, false, L"Type mismatch: the condition of an if statement evaluated as an object where a numeric True/False value was expected", NULL, NULL); return Error(pExcepInfo, false, L"Type mismatch: the condition of an if statement did not evaluate to a numeric True/False value", NULL, NULL); } if (static_cast<VARIANT&>(svar).lVal != VARIANT_TRUE) fMatch = false; }
if (fMatch) { // found the block to take -- execute its statements and we're done
return ExecStatements(ib.istmtBlock, pExcepInfo, iLocals); } }
return S_OK; }
// Helper function that eats up a set amount of stack space.
const UINT g_uiExecCallCheckStackBytes = 1484 * 4; void ExecCallCheckStack();
// Helper function that returns true if the exception code needs to be caught.
LONG ExecCallExceptionFilter(DWORD dwExceptionCode) { // We need to access violations as well as stack overflows. The first time we run out
// of stack space we get a stack overflow. The next time we get an access violation.
return dwExceptionCode == EXCEPTION_STACK_OVERFLOW || dwExceptionCode == EXCEPTION_ACCESS_VIOLATION; }
HRESULT Executor::ExecCall(Calls::index icall, bool fPushResult, EXCEPINFO *pExcepInfo, UINT iLocals) { // This is a wrapper for ExecCallInternal, which actually does the work. Here, we just want
// to catch a potential stack overflow and return it as an error instead of GPF-ing.
HRESULT hr = E_FAIL; __try { // It is better to fail now than to actually go ahead and call the routine and fail at some point we
// can't predict. Routines could do lots of different things including calling into DirectMusic or the
// OS and we can't be sure we'd get the stack overflow exception and return in a good state. This
// routine uses more stack space than we'd expect recursive calls to require to get back to this point
// again. In essence, it clears the way, checking if there's enough stack space in a way we know is safe.
ExecCallCheckStack();
#ifdef DBG
// The value for g_uiExecCallCheckStackBytes was determined by experiment. Each time through ExecCall,
// the following code prints out the address of a char on the current stack and the difference between
// the previous call. I found that two scripts, which each evaluated an if statement (always true) and
// then called the other one produced a difference of 1476. Then I multiplied that by 4 for good measure.
char c; static char *s_pchPrev = &c; DWORD s_dwPrevThreadID = 0; DWORD dwGrowth = 0; if (s_pchPrev > &c && s_dwPrevThreadID == GetCurrentThreadId()) dwGrowth = s_pchPrev-&c; TraceI(4, "Stack: 0x%08x, -%lu\n", &c, dwGrowth);
// This assert will fire if a path is executed where a recursive path back to this function takes
// more stack space than g_uiExecCallCheckStackBytes. If that's the case then g_uiExecCallCheckStackBytes
// probably needs to be increased.
assert(dwGrowth <= g_uiExecCallCheckStackBytes);
s_pchPrev = &c; s_dwPrevThreadID = GetCurrentThreadId(); #endif
// If we fail inside this call, it means g_uiExecCallCheckStackBytes probably needs to be increased because
// ExecCallCheckStack didn't catch the stack overflow.
hr = ExecCallInternal(icall, fPushResult, pExcepInfo, iLocals); } __except(ExecCallExceptionFilter(GetExceptionCode())) { Trace(1, "Error: Stack overflow.\n");
// determine routine name
Call &c = m_script.calls[icall]; const char *pszCall = NULL; if (c.k == Call::_global) { pszCall = m_script.strings[c.istrname]; } else { // name to use is last of the call's reference names
for (ReferenceNames::index irname = m_script.varrefs[c.ivarref].irname; m_script.rnames[irname].istrIdentifier != -1; ++irname) {} pszCall = m_script.strings[m_script.rnames[irname - 1].istrIdentifier]; } if (GetExceptionCode() == EXCEPTION_STACK_OVERFLOW) { hr = Error(pExcepInfo, false, L"Out of stack space: '", pszCall, L"'. Too many nested function calls."); } else { hr = Error(pExcepInfo, false, L"Out of stack space or catastrophic error: '", pszCall, L"'."); } } return hr; }
// This function doesn't actually do anything besides occupying stack space. Turn off optimization so the
// copiler doesn't get all clever on us and skip it.
#pragma optimize("", off)
void ExecCallCheckStack() { char chFiller[g_uiExecCallCheckStackBytes]; chFiller[g_uiExecCallCheckStackBytes - 1] = '\0'; } #pragma optimize("", on)
HRESULT Executor::ExecCallInternal(Calls::index icall, bool fPushResult, EXCEPINFO *pExcepInfo, UINT iLocals) { HRESULT hr = S_OK; SmartVariant svar; // holds temporary variant values at various points
SmartVariant svar2; // ditto
Call &c = m_script.calls[icall];
IDispatch *pDispCall = NULL; Strings::index istrCall = 0; const char *pszCall = NULL;
if (c.k == Call::_global) { istrCall = c.istrname; pszCall = m_script.strings[istrCall];
// Handle the call directly if it is a call to one of the script's own Routines.
Routines::index irtnLast = m_script.routines.Next(); for (Routines::index irtn = 0; irtn < irtnLast; ++irtn) { if (0 == _stricmp(pszCall, m_script.strings[m_script.routines[irtn].istrIdentifier])) { return ExecRoutine(irtn, pExcepInfo); } }
// Must be a call to the global script API.
pDispCall = m_scomGlobalDispatch; } else { assert(c.k == Call::_dereferenced); // count the reference names (needed later)
for (ReferenceNames::index irname = m_script.varrefs[c.ivarref].irname; m_script.rnames[irname].istrIdentifier != -1; ++irname) {} assert(irname - m_script.varrefs[c.ivarref].irname > 1); // if there was only one name, this should have been a global call
hr = VariableReferenceInternal(_call, c.ivarref, svar, pExcepInfo, iLocals); if (FAILED(hr)) return hr; hr = ChangeToDispatch(svar, pExcepInfo, irname - 2); if (FAILED(hr)) return hr; pDispCall = static_cast<VARIANT>(svar).pdispVal;
// the method name is the last reference name
istrCall = m_script.rnames[irname - 1].istrIdentifier; pszCall = m_script.strings[istrCall]; }
DISPID dispidCall = GetDispID(pDispCall, pszCall); if (dispidCall == DISPID_UNKNOWN) { return Error(pExcepInfo, false, L"The routine '", pszCall, L"' does not exist"); }
// We'll push the parameters onto the stack. (The function we're calling doesn't actually read them directly using the stack, but
// it is a convenient place for us to keep them temporarily.)
// First, count the parameters.
UINT cParams = 0; for (ExprBlocks::index iexpr = c.iexprParams; m_script.exprs[iexpr]; ++iexpr) { // each parameter is an expression terminated by an end block
++cParams; while (m_script.exprs[++iexpr]) {} }
// Make space for them.
UINT iParamSlots = m_stack.Next(); hr = m_stack.Push(std::_MAX<UINT>(cParams, fPushResult ? 1 : 0)); // even if there are no params, leave one slot for the result if fPushResult is true
if (FAILED(hr)) return hr;
// Fill the params in reverse order.
iexpr = c.iexprParams; for (UINT iParam = iParamSlots + cParams - 1; iParam >= iParamSlots; --iParam) { if (m_script.exprs[iexpr].k == ExprBlock::_omitted) { // write the variant value IDispatch::Invoke uses for an omitted parameter
m_stack[iParam].vt = VT_ERROR; m_stack[iParam].scode = DISP_E_PARAMNOTFOUND; } else { hr = EvalExpression(svar, iexpr, pExcepInfo, iLocals); if (FAILED(hr)) return hr;
hr = DMS_VariantCopy(g_fUseOleAut, &m_stack[iParam], &svar); if (FAILED(hr)) return hr; }
// each parameter is an expression terminated by an end block
++iexpr; while (m_script.exprs[iexpr++]) {} }
DISPPARAMS dispparams; Zero(&dispparams); dispparams.rgvarg = cParams > 0 ? &m_stack[iParamSlots] : NULL; dispparams.rgdispidNamedArgs = NULL; dispparams.cArgs = cParams; dispparams.cNamedArgs = 0;
// Make the call.
// Push the result onto the stack if fPushResult is true.
hr = InvokeAttemptingNotToUseOleAut( pDispCall, dispidCall, DISPATCH_METHOD, &dispparams, fPushResult ? &svar2 : NULL, // We can't save the result directly onto the stack because we could be makeing a recursive script call that could cause a stack to be reallocation, invalidating the address so we use svar2 instead.
pExcepInfo, NULL); hr = ErrorIfInvokeProblem(_call, hr, istrCall, pExcepInfo); if (SUCCEEDED(hr) && fPushResult) { hr = DMS_VariantCopy(g_fUseOleAut, &m_stack[iParamSlots], &svar2); }
m_stack.PopTo(iParamSlots + (fPushResult ? 1 : 0)); if (FAILED(hr)) return hr;
return S_OK; }
// possible error-message type returns: DISP_E_TYPEMISMATCH
HRESULT Executor::EvalExpression(VARIANT &varResult, ExprBlocks::index iexpr, EXCEPINFO *pExcepInfo, UINT iLocals) { HRESULT hr = S_OK;
UINT iTempSlots = m_stack.Next();
for (ExprBlocks::index iexprCur = iexpr; /* ever */; ++iexprCur) { ExprBlock &e = m_script.exprs[iexprCur]; switch (e.k) { case ExprBlock::_end: // pop the result and return it
if (m_stack.Next() != iTempSlots + 1) { assert(false); return E_FAIL; }
DMS_VariantCopy(g_fUseOleAut, &varResult, &m_stack[iTempSlots]); m_stack.PopTo(iTempSlots); return hr;
case ExprBlock::_op: // Pop one (unary operator) or two (binary operator) items, apply the operator, and push the result.
// (Actually, I just assign the result into the stack instead of pushing it, but conceptually the is
// the same as popping and pushing the new value.)
{ Token t = e.op; bool fUnary = t == TOKEN_op_not || t == TOKEN_sub;
UINT iNext = m_stack.Next(); if (iNext < iTempSlots + (fUnary ? 1 : 2)) { assert(false); return E_FAIL; }
VARIANT &v1 = m_stack[iNext - 1]; if (fUnary) hr = EvalUnaryOp(t, v1); else { VARIANT &v2 = m_stack[iNext - 2]; hr = EvalBinaryOp(t, v1, v2, pExcepInfo); m_stack.PopTo(iNext - 1); } } break;
case ExprBlock::_val: { // push it
hr = m_stack.Push(1); VARIANT &varToPush = m_stack[m_stack.Next() - 1]; if (SUCCEEDED(hr)) hr = EvalValue(e.ival, varToPush, pExcepInfo, iLocals); if (varToPush.vt == VT_EMPTY) { // treat an empty value as zero
varToPush.vt = VT_I4; varToPush.lVal = 0; } } break;
case ExprBlock::_call: // push it
if (SUCCEEDED(hr)) hr = ExecCall(e.icall, true, pExcepInfo, iLocals); break; }
if (FAILED(hr)) { m_stack.PopTo(iTempSlots); return hr; } }
return S_OK; }
HRESULT Executor::EvalValue(Values::index ival, VARIANT &v, EXCEPINFO *pExcepInfo, UINT iLocals) { Value val = m_script.vals[ival]; switch (val.k) { case Value::_numvalue: v.vt = VT_I4; v.lVal = val.inumvalue; break;
case Value::_strvalue: { v.vt = VT_BSTR; SmartRef::WString wstr = m_script.strings[val.istrvalue]; if (!wstr) return E_OUTOFMEMORY; v.bstrVal = DMS_SysAllocString(g_fUseOleAut, wstr); if (!v.bstrVal) return E_OUTOFMEMORY; break; }
case Value::_varref: HRESULT hr = GetVariableReference(val.ivarref, v, pExcepInfo, iLocals); if (FAILED(hr)) return hr; }
return S_OK; }
HRESULT Executor::EvalUnaryOp(Token t, VARIANT &v) { if (v.vt != VT_I4) { assert(false); return DISP_E_TYPEMISMATCH; }
if (t == TOKEN_op_not) { v.lVal = ~v.lVal; } else { assert(t == TOKEN_sub); v.lVal = -v.lVal; }
return S_OK; }
// Returns a proper VB boolean value (0 for false, -1 for true)
inline LONG BoolForVB(bool f) { return f ? VARIANT_TRUE : VARIANT_FALSE; }
HRESULT Executor::EvalBinaryOp(Token t, VARIANT &v1, VARIANT &v2, EXCEPINFO *pExcepInfo) { if (v1.vt == VT_DISPATCH || v1.vt == VT_UNKNOWN) { // the only operator that accepts object values is is
if (t != TOKEN_is || !(v2.vt == VT_DISPATCH || v2.vt == VT_UNKNOWN)) { assert(false); return DISP_E_TYPEMISMATCH; }
HRESULT hr = DMS_VariantChangeType(g_fUseOleAut, &v1, &v1, 0, VT_UNKNOWN); if (FAILED(hr)) return hr; hr = DMS_VariantChangeType(g_fUseOleAut, &v2, &v2, 0, VT_UNKNOWN); if (FAILED(hr)) return hr;
bool fIs = v1.punkVal == v2.punkVal;
hr = DMS_VariantClear(g_fUseOleAut, &v2); if (FAILED(hr)) return hr;
v2.vt = VT_I4; v2.lVal = BoolForVB(fIs); return S_OK; }
if (v1.vt != VT_I4 || v2.vt != VT_I4) { assert(false); return DISP_E_TYPEMISMATCH; }
switch (t) { case TOKEN_op_minus: v2.lVal -= v1.lVal; break; case TOKEN_op_pow: v2.lVal = _Pow_int(v2.lVal, v1.lVal); break; case TOKEN_op_mult: v2.lVal *= v1.lVal; break; case TOKEN_op_div: if (v1.lVal == 0) return Error(pExcepInfo, false, L"Division by zero", NULL, NULL); v2.lVal /= v1.lVal; break; case TOKEN_op_mod: if (v1.lVal == 0) return Error(pExcepInfo, false, L"Mod by zero", NULL, NULL); v2.lVal %= v1.lVal; break; case TOKEN_op_plus: v2.lVal += v1.lVal; break; case TOKEN_op_lt: v2.lVal = BoolForVB(v2.lVal < v1.lVal); break; case TOKEN_op_leq: v2.lVal = BoolForVB(v2.lVal <= v1.lVal); break; case TOKEN_op_gt: v2.lVal = BoolForVB(v2.lVal > v1.lVal); break; case TOKEN_op_geq: v2.lVal = BoolForVB(v2.lVal >= v1.lVal); break; case TOKEN_op_eq: v2.lVal = BoolForVB(v2.lVal == v1.lVal); break; case TOKEN_op_neq: v2.lVal = BoolForVB(v2.lVal != v1.lVal); break; case TOKEN_and: v2.lVal &= v1.lVal; break; case TOKEN_or: v2.lVal |= v1.lVal; break; default: assert(false); return E_UNEXPECTED; }
return S_OK; }
// O.K. This is a bit funky, but bear with me. This function has four different behaviors determined by the first (e) parameter.
// This is some ugly code, but at least this way I get to use it for multiple purposes.
// _get: Returns the value of the variable reference via out parameter v.
// _put: Sets the value of the variable reference to the in parameter v.
// _putref: Same as _put, but assigns by reference ala VB's 'set' statements.
// _call: Same as _get, but returns the second-to-last value in the chain via out parameter v.
// For example, if the reference is 'a.b.c' this returns the value of 'a.b', which can then be used to invoke function c.
// It is an error to call VariableReferenceInternal in this way with only a single item such as 'a'.
HRESULT Executor::VariableReferenceInternal(DispatchOperationType e, Variables::index ivarref, VARIANT &v, EXCEPINFO *pExcepInfo, UINT iLocals) { HRESULT hr = S_OK;
VariableReference r = m_script.varrefs[ivarref]; bool fGlobal = r.k == VariableReference::_global;
SmartVariant svar;
assert(m_script.rnames[r.irname].istrIdentifier != -1); bool fJustOnePart = m_script.rnames[r.irname + 1].istrIdentifier == -1; if (fJustOnePart && e == _call) { assert(false); return E_UNEXPECTED; }
//
// Handle the base item of the reference, which is either a script variable or an item on the global dispatch.
// If we're doing a set and there aren't more parts to the rnames, just do the set.
// Otherwise, get the result into 'var'.
//
// Example:
// x = 1
// There is just one part and it is a set. Determine whether x is in the script or part of the global dispatch, set it to 1,
// and we're done.
// x.y = 1
// x is the base. Determine whether x is in the script or part of the global dispatch and get its value. (We'll worry about
// setting the y property later in this function.
//
// check if the base is part of the global dispatch
DISPID dispid = DISPID_UNKNOWN; if (fGlobal) { dispid = m_script.globals[r.ivar].dispid; } if (dispid != DISPID_UNKNOWN) { // base is part of global dispatch
if (fJustOnePart && (e == _put || e == _putref)) { // set it and we're done
hr = SetDispatchProperty(m_scomGlobalDispatch, dispid, e == _putref, v, pExcepInfo); hr = ErrorIfInvokeProblem(e, hr, m_script.globals[r.ivar].istrIdentifier, pExcepInfo); return hr; } else { hr = GetDispatchProperty(m_scomGlobalDispatch, dispid, svar, pExcepInfo); hr = ErrorIfInvokeProblem(e, hr, m_script.globals[r.ivar].istrIdentifier, pExcepInfo); if (FAILED(hr)) return hr; } } else { // base is in script
VARIANT &vVariable = m_stack[r.ivar + (fGlobal ? 0 : iLocals)];
if (fJustOnePart && (e == _put || e == _putref)) { // set it and we're done
hr = ErrorIfImproperRef(v, e == _putref, m_script.rnames[r.irname].istrIdentifier, pExcepInfo); if (FAILED(hr)) return hr; hr = DMS_VariantCopy(g_fUseOleAut, &vVariable, &v); return hr; } else { hr = DMS_VariantCopy(g_fUseOleAut, &svar, &vVariable); if (FAILED(hr)) return hr; } }
//
// Great! The base value is now held in svar. Any remaining rnames are a chain of properties we need to get from that object.
// And the last rname needs to be a set if we're in one of the put modes or the last name is ignored if we're in the _call mode.
//
if (m_script.rnames[r.irname + 1].istrIdentifier != -1) { // the base value must be of object type
hr = ErrorIfImproperRef(svar, true, m_script.rnames[r.irname].istrIdentifier, pExcepInfo); if (FAILED(hr)) return hr;
for (ReferenceNames::index irname = r.irname + 1; /* ever */; ++irname) { bool fLastPart = m_script.rnames[irname + 1].istrIdentifier == -1; if (fLastPart && e == _call) break;
// get its IDispatch interface
hr = ChangeToDispatch(svar, pExcepInfo, irname - 1); if (FAILED(hr)) return hr; IDispatch *pDisp = static_cast<VARIANT>(svar).pdispVal;
// get the dispid
ReferenceName &rname = m_script.rnames[irname]; DISPID dispidName = GetDispID(pDisp, m_script.strings[rname.istrIdentifier]); if (dispidName == DISPID_UNKNOWN) return Error(pExcepInfo, false, L"The property '", m_script.strings[rname.istrIdentifier], L"' does not exist");
if (fLastPart && (e == _put || e == _putref)) { // set it and we're done
hr = SetDispatchProperty(pDisp, dispidName, e == _putref, v, pExcepInfo); hr = ErrorIfInvokeProblem(e, hr, rname.istrIdentifier, pExcepInfo); return hr; } else { hr = GetDispatchProperty(pDisp, dispidName, svar, pExcepInfo); hr = ErrorIfInvokeProblem(e, hr, rname.istrIdentifier, pExcepInfo); if (FAILED(hr)) return hr; }
if (fLastPart) { // we've done all the names
break; } else { // the new value must be of object type
hr = ErrorIfImproperRef(svar, true, rname.istrIdentifier, pExcepInfo); if (FAILED(hr)) return hr; } } }
//
// We're done. Now we just have to return the value we calculated. (We know that a set would have already returned.)
//
hr = DMS_VariantCopy(g_fUseOleAut, &v, &svar); return hr; }
HRESULT Executor::ChangeToDispatch(VARIANT &var, EXCEPINFO *pExcepInfo, ReferenceNames::index irnameIdentifier) { HRESULT hr = DMS_VariantChangeType(g_fUseOleAut, &var, &var, 0, VT_DISPATCH); if (FAILED(hr)) return ErrorObjectRequired(m_script.rnames[irnameIdentifier].istrIdentifier, pExcepInfo);
return S_OK; }
|