/// Kaboom.cs Copyright (c) Tony Tanzillo 2013.
///
/// The following code contains implementations of
/// three externally-defined LISP functions:
///
/// (safe-kaboom)
/// (unsafe-kaboom)
/// (ez-safe-kaboom)
///
/// All functions take one or more arguments.
///
/// If you pass any of them two arguments and both
/// arguments are NIL, like so:
///
/// Command: (unsafe-kaboom nil nil)
///
/// A bug in the TypedValue's == operator triggers
/// a NullReferenceException.
///
/// The (unsafe-kaboom) function provides no safety-net,
/// and doesn't catch any exceptions thrown by the call
/// to the KaboomHelper() method, and as a result, the
/// call '(unsafe-kaboom nil nil)' will crash AutoCAD.
///
/// Some mistakenly believe that AutoCAD will catch any
/// exception that's raised in code called in or from
/// a LispFunction or CommandMethod handler, and make
/// the mistake of relying on AutoCAD's managed runtime
/// to act as the safety-net that prevents managed code
/// from doing serious damage, such as crashing AutoCAD
/// which often results in the loss of unsaved work.
///
/// That is simply not the case.
///
/// AutoCAD's managed runtime does catch most exceptions
/// raised by the CLR, but does not catch all exceptions,
/// including NullReferenceExceptions that are signaled
/// in hardware, rather than by the CLR. If you do not
/// implement an exception handler within a LispFunction
/// or CommandMethod handler, there's an excellent chance
/// that AutoCAD will crash if your code contains a bug
/// that triggers a NullReferenceException, or other low-
/// level or hardware-signaled exception.
///
/// The handler for the (safe-kaboom) LISP function shows
/// how you must deal with exceptions that can be thrown
/// from code in LispFunctions and CommandMethods (or any
/// code called from those contexts).
///
/// (safe-kaboom) traps any exception, including those
/// that AutoCAD's managed runtime fail to catch, and
/// simply rethrows them as managed exceptions.
///
/// Re-throwing an exception from managed code is what
/// allows AutoCAD's runtime to catch the exception and
/// prevents it from becoming fatal.
///
/// The call '(safe-kaboom nil nil)', also results in a
/// NullReferenceException, but because that exception
/// is caught by the SafeKaboom() LispFunction handler,
/// and re-thrown as a managed exception, the AutoCAD
/// managed runtime will catch it, and AutoCAD will not
/// go belly-up.
///
/// The handler for the (ez-safe-kaboom) LISP function
/// shows a more-advanced, structured way to deal with
/// the problem, using delegates and lambda functions.
///
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.DatabaseServices;
namespace KaboomSample
{
public static class Kaboom
{
/// <summary>
/// Calling this with two nils as
/// show below, will crash AutoCAD:
///
/// Command: (unsafe-kaboom nil nil)
///
/// </summary>
[LispFunction( "UNSAFE-KABOOM" )]
public static bool UnsafeKaboom( ResultBuffer args )
{
return KaboomHelper( args );
}
/// <summary>
/// Calling this with the same arguments
/// shown above, will result in an error
/// condition, without crashing AutoCAD.
/// </summary>
[LispFunction( "SAFE-KABOOM" )]
public static bool SafeKaboom( ResultBuffer args )
{
/// The try/catch blocks catch exceptions
/// that may not be caught by AutoCAD, and
/// will most-likely become fatal:
try
{
return KaboomHelper( args );
}
catch( System.Exception ex ) /// <- variable is required !
{
/// The exception that was thrown may be a
/// hardware-signaled or SEH exception, in
/// which case AutoCAD's managed runtime may
/// not be able to catch it, resulting in it
/// becoming fatal.
///
/// The way to prevent that from happening
/// is to simply re-throw the exception as
/// a managed exception, allowing AutoCAD's
/// managed runtime to catch it. Note that
/// we declare the exception variable in the
/// catch() statement because if we don't do
/// that, the effect is the same as having
/// no catch block at all.
///
/// In other words, do NOT do this:
///
/// try
/// {
/// /// some code that may fail
/// }
/// catch
/// {
/// throw;
/// }
///
/// What you see above may NOT allow AutoCAD to
/// catch the re-thrown exception. That may be
/// because the compiler may simply discard the
/// catch block entirely as it does nothing, or
/// behave as if the catch block doesn't exist.
///
/// So, you must declare an exception variable,
/// and throw that variable, as shown in the
/// code examples.
///
/// Of course, it should go without saying,
/// that you may want to do something else
/// when an exception is caught, depending
/// on the type of the exception and/or the
/// specifics of your application.
throw ex;
}
}
/// <summary>
///
/// Works like SafeKaboom(), except that it
/// uses a helper method that does the work
/// of catching and re-throwing exceptions,
/// making it possible for them to be caught
/// by AutoCAD's managed runtime:
///
/// </summary>
[LispFunction( "EZ-SAFE-KABOOM" )]
public static bool EZSafeKaboom( ResultBuffer args )
{
return SafeInvoke( KaboomHelper, args );
}
/// <summary>
///
/// By-design, this method will throw a
/// NullReferenceException when passed
/// a ResultBuffer containing exactly
/// two RTNIL's .
///
/// The exception results from a bug in
/// the TypedValue's == operator, that
/// mistakenly uses Object.Equals (the
/// instance version) on left operand's
/// Value property, without fist doing
/// a null check on it.
///
/// </summary>
static bool KaboomHelper( ResultBuffer args )
{
if( args == null )
return false;
TypedValue[] array = args.AsArray();
if( array.Length != 2 )
return false;
TypedValue a = array[0];
TypedValue b = array[1];
return a == b;
}
/// <summary>
/// Helper methods that will invoke a passed
/// delegate inside a try block, and catch any
/// exception thrown, including ones that are
/// not catchable by AutoCAD's managed runtime,
/// and re-throw the exception, allowing it to
/// be caught by the AutoCAD runtime.
///
/// Why all this bullshit is necessary,
/// is the $60,000 question.
///
/// </summary>
/// <param name="action"></param>
public static void SafeInvoke( Action action )
{
try
{
action();
}
catch( System.Exception ex )
{
/// Here you can also do other things,
/// like log the exception, generate a
/// CER (customer error report), send
/// an email, SMS, tweet 8), etc...
///
/// But you still have to re-throw here,
/// or the calling LISP code will never
/// know what happened.
throw ex;
}
}
/// Same as above, but returns a value
public static T SafeInvoke<T>( Func<T> func )
{
try
{
return func();
}
catch( System.Exception ex )
{
throw ex;
}
}
/// Same as a above, but also takes an argument
/// and returns a value.
public static T SafeInvoke<TArg, T>( Func<TArg, T> func, TArg arg )
{
try
{
return func( arg );
}
catch( System.Exception ex )
{
throw ex;
}
}
}
}