views:

54

answers:

1

Our application hosts the Windows Scripting Host JScript engine and exposes several domain objects that can be called from script code.

One of the domain objects is a COM component that implements IDispatch (actually, IDispatchEx) and which has a method that takes a script-function as a call-back parameter (an IDispatch* as a parameter). This COM component is called by script, does some things, and then calls back into script via that supplied IDispatch parameter before returning to the calling script.

If the call-back script happens to throw an exception (e.g., makes a call to another COM component which returns something other than S_OK), then the call to IDispatch::Invoke on the call-back script will return SCRIPT_E_PROPAGATE instead of the HRESULT from the other COM component; not the expected HRESULT from the other COM object. If I return that HRESULT (SCRIPT_E_PROPAGATE) back to the caller of the first COM component (e.g., to the calling script), then the script engine correctly throws an error with the expected HRESULT from the other COM object.

However, the ACTUAL ERROR is nowhere to be found. It's not returned from the Invoke call (the return value is SCRIPT_E_PROPAGATE). It's not returned via the EXCEPINFO supplied to Invoke (the structure remains empty). AND, it's not available via GetErrorInfo (the call returns S_FALSE)!

Script
    Defines ScriptCallback = function() { return ComComponentB.doSomething(); }
    Invokes ComComponentA.execute(ScriptCallback)
        Invokes ScriptCallback()
            Invokes ComComponentB.doSomething()
                Returns E_FAIL (or some other HRESULT)
            Throws returned HRESULT
        Receives SCRIPT_E_PROPAGATE <--- WHERE IS THE ACTUAL ERROR?
        Returns SCRIPT_E_PROPAGATE
    Throws E_FAIL (or whatever HRESULT was returned from ComComponentB)

I'd really like to get my hands on that error, because it would be useful to cache it and return the same error on subsequent calls (getting to the error often involves an expensive operation that is defined by the script-function passed as a parameter, but I do know how to cache the error). Is there a way for a scripted COM component to get to an exception thrown during a call-back into a supplied script-function???

+1  A: 

Wow, this was seriously underdocumented.

The answer is to:

In the COM component making a callback into script...

  1. QI to get an IDispatchEx pointer on the script function to be called.
  2. Construct an object implementing both IServiceProvider & ICanHandleException; e.g. CScriptErrorCapturer.
    • IServiceProvider::QueryService can return E_NOINTERFACE
    • If the script callback function throws, but does not catch, an exception when InvokEx'd (see below), then ICanHandleException::CanHandleException will get an EXCEPINFO and VARIANT* (look on MSDN for documentation).
    • The variant will contain the object thrown, which might be an Error object.
    • Try to get the "number" and "message" properties from the IDispatch on this Error object, where "number" represents the actual script error (HRESULT).
    • These values can/should be used to update the EXCEPINFO scode and (optionally) bstrDescription in order to propagate the error up to the calling script. If you don't update the scode, then then engine will throw an "Exception thrown but not caught" (0x800A139E), which is what the EXCEPINFO contains before you modify it.
    • Not sure if pfnDeferredFillIn should be cleared, but it works without doing this.
    • In my code, I capture the error here in my CScriptErrorCapturer.
    • Return S_OK. Returning E_FAIL here will abort the entire script run, and not allow the exception to be thrown back up to the original calling script.
  3. Call IDispatchEx::InvokeEx and pass your CScriptErrorCapturer as the IServiceProvider parameter.
  4. Upon return from InvokeEx, query your CScriptErrorCapturer to see if it caught an error. According to code in the GoogleWebKit, sometimes InvokeEx may return S_OK, even if an error is thrown.
  5. Don't touch the return value from InvokeEx, especially if it is SCRIPT_E_PROPAGATE (0x80020102)

Note: this link contains some of the undocumented JScript HRESULTS described above.

James Hugard