Using Conditional Compilation in HDi

One of the cardinal rules for developing HD DVD applications is, "let no exception go unhandled." The reason for this is simple – if you fail to handle an exception, the player will kill your application and stop the disc. This leads many developers to wrap entire functions in try / catch blocks, which solves the problem but introduces some of its own.

To me, one of the biggest problems with using try / catch during development is that it makes debugging script with Visual Studio virtually impossible. For a start, you can't rely on "just-in-time" (JIT) debugging to work because you never have any unhandled exceptions. Additionally, even if you manually attach the debugger and step through your code line by line, as soon as there is an exception it will dump you out at the catch handler, losing all the context information you might need to fix the bug (stack trace, local variables, even the location in the source file).

As an example, try this script (run it under WSH with the command-line cscript //x test.js) and step through each line by hitting F11. Step over the PretendThisIsAPlatformAPI function by hitting F10 instead.

// Hit F11 to step INTO each line
try
{
  foo(1)
}
catch(ex)

  WScript.Echo(ex)
}

function foo(x)

  // Imagine lots of complex code here... 
  var y = x - 1
  bar(y)
}

function bar(x)

  // More complex code here... 
  var y = x / 2 
  // Hit F10 to step OVER this function 
  PretendThisIsAPlatformAPI(y)
}

function PretendThisIsAPlatformAPI(x)

  if (x <= 0) 
    throw("bad parameter value")
}

Note that everything is fine until you call that fake platform API. Then you are dumped back into the catch block of the global code with a message saying "bad parameter value." But what was the parameter value I passed to the function? I can't find out, because all the context (the stack frames for bar and foo) has been removed.

Is the solution to remove the try / catch block during development, then add it back at the last minute before shipping?

NO!

That is a recipe for disaster for at least two reasons:

  1. You might forget to put it back in
  2. You will never have tested your exception-handling code

There's a better approach, and it relies on a feature of Microsoft's JScript implementation that lets you "hide" parts of the script from JScript whilst making it clearly visible to normal ECMAScript engines. This allows you to toggle exception handling on or off during development (with HDiSim), whilst never risking having it turned off when you ship.

This feature is called conditional compilation, and here's how we use it:

The first thing to do is to activate conditional compilation. We do that like this – note how it is in an ECMAScript comment, so that other script engines will ignore the command:

//@cc_on

Next, we define a conditional-compilation variable that will determine whether or not we want exception handling (note: you can name the variable anything you want; this isn't some magic value):

//@set @enable_exceptions = true

Perhaps obviously, we set this to true if we want to handle exceptions with try / catch, and false if we do not.

Finally, we wrap our try and catch blocks with a conditional statement:

//@if(@enable_exceptions)
try
{
//@end 

  foo(1)

//@if(@enable_exceptions)
}
catch(ex)

  WScript.Echo(ex)
}
//@end

Now, depending on the value of @enable_exceptions, JScript either sees this source code (when true):

try

  foo(1)
}
catch(ex)

  WScript.Echo(ex)
}

Or this source code (when false):

  foo(1)

Non-JScript engines will always see the former source code, because they simply treat the @if statements as comments. That means they will always have try / catch turned on, and is why I say it is impossible to "forget" to turn exception handling back on when you ship.

During normal testing and development, you should leave @enable_exceptions set to true, because you need to test your exception-handling code. But when you are trying to debug a problem caused by a thrown exception, you can set it to false and use the JIT feature of Visual Studio to jump right to the line that is throwing the exception. You should then turn it back to true again after you've finished debugging, because it is possible that some players on Windows might use the JScript engine. Even then, this approach is better than manually adding and removing the exception-handling code from your applications since there's only one place in your code to do it, rather than every single location where you use try / catch.

You can try this out now, by running the sample script again with exception handling turned on and off. In this case, instead of stepping through the file with F11, just hit F5 to run the program. When exception handling is turned on, you don't get any help from the VS debugger. When it's turned off, VS will break at the offending line so you can investigate the problem and determine the right fix.

Marvellous!

Typically I have a single file, cc.js, that I always include as the first <Script> element in my manifest. It contains the @cc_on statement and the @enable_exceptions variable, along with any others I might decide to use. As part of your shipping process, you could decide to always remove this file (or replace it with one that forces @enable_exceptions to true) "just in case"...