5

I am using the Qt script engine in my application as an alternative way for the user to access its functionality. As such, I export some C++ classes to the Qt ScriptEngine, that will serve as the interface to the application. The problem is, these C++ classes can throw exceptions.

I have a "ScriptInterface" class running on its own thread, listening for requests to process scripts. So when I evaluate a user's script, I have a try/catch block around it to handle exceptions, and print the error to the console in the application.

...
try {
   m_engine->evaluate(script, name);
}
catch (Exception const& e) {
   // deal with it
}
catch (...) {
   // scary message
}

This works perfectly in windows... but doesn't work in linux- the program terminates with this message:

terminate called after throwing an instance of 'Basilisk::InvalidArgumentException'
  what():  N8Basilisk24InvalidArgumentExceptionE
Aborted

I had a hunch that it was because the exceptions bubbled up to the event handler (since the script engine uses signals to call the functions in my exported classes), so I reimplemented QApplication::notify, to handle exceptions there, but they weren't caught.

My question is, am I doing something fundamentally wrong? Also, as an alternative, is it possible to explicitly throw script exceptions from within my C++ classes?

Thanks in advance

EDIT: fixed the description to include the catch(...) statement.

UPDATE (SOLUTION): I "fixed" this problem by following a strategy similar to the one outlined in the accepted answer. Although I haven't gone to the source of why the exceptions don't get caught on linux (my suspicion now, is that m_engine->evaluate spawn a seperate thread on linux), but I have started using the intended way of exception throwing in Qt Scripts, and that is QScriptContext::throwError().

In cases where my function would look like this: (random example)

void SomeClass::doStuff(unsigned int argument) {
    if (argument != 42) {
        throw InvalidArgumentException(
            "Not the answer to Life, the Universe and Everything.");
    }

    // function that is not part of the scripting environment,
    // and can throw a C++ exception
    dangerousFunction(argument);
}

It is now this: (pay particular attention to the return type)

QScriptValue SomeClass::doStuff(unsigned int argument) {
    if (argument != 42) {
        // assuming m_engine points to an instance of
        // QScriptEngine that will be calling this function
        return m_engine->currentContext()->throwError(QScriptContext::SyntaxError,
             "Not the answer to Life, the Universe and Everything.");
    }


    try {
        // function that is not part of the scripting environment,
        // and can throw a C++ exception
        dangerousFunction(argument);
    } catch (ExpectedException const& e) {
        return m_engine->currentContext()->throwError(QScriptContext::UnknownError,
             e.message());
    }

    // if no errors returned, return an invalid QScriptValue,
    // equivalent to void
    return QScriptValue();
}

So where does one deal with these script errors? After the call to QScriptEngine::evaluate() you can check whether there are any uncaught exceptions, with QScriptEngine::hasUncaughtException(), obtain the error object with uncaughtException(), and now you have the message, the trace, and line number in the script where the error occured!

Hope this helps someone out!

Alexander Kondratskiy
  • 4,156
  • 2
  • 30
  • 51

2 Answers2

3

I ran into a similar type of problem when trying to use SWIG with Python to wrap C++ libraries. Eventually what happened was that I made a stub for all the wrapped classes which caught the exception and failed quietly. Luckily I had the luxury of wrapping functionality which only passed container classes and state pattern objects, so I could easily check if something was amiss. May I suggest the same for you?

  1. Wrap the functions you want with another function, same interface except the return value.
  2. Create an object that contains not only the requested return type but also an error indicator.
  3. Have the script make sure to check for the exceptions.

And yes, it's very possible for a script engine to throw C++ exceptions if you've given it access to an exception factory (a class whose sole purpose is to throw C++ exceptions.)

wheaties
  • 35,646
  • 15
  • 94
  • 131
  • 1
    Super late accept, but I came back to this problem this week and basically did something very similar to what you described. I'll edit my post to show my solution for my specific case. Thanks! – Alexander Kondratskiy Feb 26 '11 at 03:58
1

Run your program under a debugger and place a breakpoint inside your runtime library's terminate() function. That way you'll stop on terminate() in the debugger and by inspecting the call stack you will then see from where terminate() was called.

Martin Ba
  • 37,187
  • 33
  • 183
  • 337