.NET

.NET

Reply
*Expert Elite*
_gile
Posts: 2,125
Registered: ‎04-29-2006
Message 1 of 10 (585 Views)

How to cleanly throw an exception from LispFunction

585 Views, 9 Replies
12-16-2013 09:02 AM

Hi,

 

When defining new LISP function with .NET, I think it's necessary to first check for the argument validity (number and type) and I am convinced the evaluation must stop in case of invalid inputs (as the built-in LISP function work).

 

Fot this, I use some classes which inherit from System.Exception:

 

using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;

namespace LispFunctionSample
{
    /// <summary>
    /// Base exception type for LISP exceptions.
    /// </summary>
    class LispException : System.Exception
    {
        /// <summary>
        /// Creates a new instance of LispException.
        /// </summary>
        /// <param name="msg">The message to be displayed when LispException is thrown.</param>
        public LispException(string msg)
            : base(msg) { }
    }

    /// <summary>
    /// Exception to be used if the LISP function is called with too few arguments.
    /// </summary>
    class TooFewArgsException : LispException
    {
        /// <summary>
        /// Creates a new instance of TooFewArgsException.
        /// </summary>
        public TooFewArgsException()
            : base("too few arguments") { }
    }

    /// <summary>
    /// Exception to be used if the LISP function is called with too many arguments.
    /// </summary>
    class TooManyArgsException : LispException
    {
        /// <summary>
        /// Creates a new instance of TooManyArgsException.
        /// </summary>
        public TooManyArgsException()
            : base("too many arguments") { }
    }

    /// <summary>
    /// Exception to be used if the LISP function is called with an incorrect argument type.
    /// </summary>
    class ArgumentTypeException : LispException
    {
        /// <summary>
        /// Creates a new instance of ArgumentTypeException
        /// </summary>
        /// <param name="msg">The string to be displayed in the message 
        /// (fixnump, numberp stringp, listp, lentityp, lselsetp).</param>
        /// <param name="tv">The TypedValue representing the incorrect argument.</param>
        public ArgumentTypeException(string msg, TypedValue tv)
            : base(string.Format(
            "bad argument type: {0}: {1}",
            msg, 
            tv.TypeCode == (int)LispDataType.Nil ? 
            "nil" :  
            tv.TypeCode == (int)LispDataType.Text ? 
            "\"" + tv.Value + "\"" : 
            tv.Value))
        { }
    }
}

 

These exceptions may be thrown from the LispFunction code, catched to display a message and re-raised to stop the LISP evaluation.

 

Example:

        [LispFunction("acos")]
        public double Acos(ResultBuffer resbuf)
        {
            try
            {
                if (resbuf == null)
                    throw new TooFewArgsException();
                TypedValue[] args = resbuf.AsArray();
                if (args.Length > 1)
                    throw new TooManyArgsException();
                int code = args[0].TypeCode;
                if (code != (int)LispDataType.Int16 && code != (int)LispDataType.Int32 && code != (int)LispDataType.Double)
                    throw new ArgumentTypeException("numberp", args[0]);
                double value = Convert.ToDouble(args[0].Value);
                if (value < -1.0 || value > 1.0)
                    throw new LispException("the value must be between -1.0 and 1.0 inclusive");
                return Math.Acos(value);
            }
            catch (System.Exception ex)
            {
                AcAp.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\n; error: " + ex.Message + "\n");
                throw;
            }
        }

 

This works quite fine except the message in the command line is followed by an eInvalidAdsName stack trace and, in the Visual LISP console, the message is: "; error: ADS request error".

 

So, is there a way to avoid the eInvalidAdsName stack trace in the command line and have the thrown exception message in the Visual LISP console so that it mimics closer the built-in functions?

 

Thanks in advance.

 

 

Gilles Chanteau
Valued Mentor
DiningPhilosopher
Posts: 370
Registered: ‎05-06-2012
Message 2 of 10 (553 Views)

Re: How to cleanly throw an exception from LispFunction

12-16-2013 03:15 PM in reply to: _gile

Hi Giles.

 

The short answer is no.

 

Managed exceptions (that are not signaled by hardware or the OS), like those you are throwing, are being caught by the part of the managed runtime that invokes your LispFunction.  The caught exception is handled, the Exception's Message property is displayed on the console, and an RTXXXX result code is returned to AutoCAD, just like most externally-defined LISP written in C++ and registered using acedDefun()/ads_regfun() typically do.  It's that result code that is what causes AutoCAD to display the generic error message.  Note that in ObjectARX, there is no way to convey a more-specific error message, and hence, the only way to do it is to write it to the console yourself, just before raising the exception.

 

The commuication between LISP and AutoCAD is archaic, and provides no way to convey specifics of an error, other than by writing it to the console yourself.

 

 

 

 

*Expert Elite*
_gile
Posts: 2,125
Registered: ‎04-29-2006
Message 3 of 10 (529 Views)

Re: How to cleanly throw an exception from LispFunction

12-16-2013 11:18 PM in reply to: DiningPhilosopher

Hi Tony,

 

Thank you for the quick and clear answer, this is what I feared.

I will continue to use alert box to warn the user in case of error.

Gilles Chanteau
Valued Contributor
FRFR1426
Posts: 97
Registered: ‎04-05-2012
Message 4 of 10 (521 Views)

Re: How to cleanly throw an exception from LispFunction

12-17-2013 12:47 AM in reply to: _gile

As you can not cleanly throw an exception from AutoLISP, you should define a convention for returning an error and write a wrapper on the AutoLISP side to handle these errors.

 

First rewrite your .NET code like this :

 

[LispFunction("_acos")]
public ResultBuffer Acos(ResultBuffer resbuf)
{
    try
    {
        if (resbuf == null)
            throw new TooFewArgsException();
        TypedValue[] args = resbuf.AsArray();
        if (args.Length > 1)
            throw new TooManyArgsException();
        int code = args[0].TypeCode;
        if (code != (int)LispDataType.Int16 && code != (int)LispDataType.Int32 && code != (int)LispDataType.Double)
            throw new ArgumentTypeException("numberp", args[0]);
        double value = Convert.ToDouble(args[0].Value);
        if (value < -1.0 || value > 1.0)
            throw new LispException("the value must be between -1.0 and 1.0 inclusive");
        return new ResultBuffer(new[]
            {
                new TypedValue((int) LispDataType.T_atom), 
                new TypedValue((int) LispDataType.DottedPair),
                new TypedValue((int) LispDataType.Double, Math.Acos(value)), 
            });
    }
    catch (System.Exception ex)
    {
        Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\n; error: " + ex.Message + "\n");
        return new ResultBuffer(new[]
            {
                new TypedValue((int) LispDataType.Nil),
                new TypedValue((int) LispDataType.DottedPair),
                new TypedValue((int) LispDataType.Text, ex.Message),
            });
    }
}

 The functions should always return a dotted pair. The first value is a boolean indicating success or not, the second value the result of the function if all works well or the error message if something goes wrong (not used in this case, but can be useful for debugging). Note that I added a _ before the function name to indicate that it should not be called directly.

 

Then in AutoLISP, write this kind of wrapper function:

 

(defun acos (value / result)
  (setq result (_acos value))
  (if (car result)
    (cdr result)
    (exit)
  )
)

If there is an error, you will get two errors displayed on the command line : the exception message written from your .NET code, and the standard error displayed by the exit function ( ; error: quit/exit). To hide the second one, you can write an error handler like this:

 

(defun my-error (msg)
  (if
    (/= (getvar "ERRNO") 0)
     (princ (strcat "\n; error: " msg))
  )
  (setq *error* *ini-err* *ini-err* nil)
  (princ)
)

 Set ERRNO to 0 in your lisp wrapper before calling exit or in your .NET exception handler to mute the message or (better) use custom error numbers (> 85).

*Expert Elite*
_gile
Posts: 2,125
Registered: ‎04-29-2006
Message 5 of 10 (510 Views)

Re: How to cleanly throw an exception from LispFunction

12-17-2013 02:12 AM in reply to: FRFR1426

Hi FRFR1426,

 

Thank you for your contribution, but I do not want any wrapper on the LISP side.

The goal would be creating an easily extensible LISP functions library using some helpers on the .NET side within a wrapper which catch any exception.

 

Using this wrapper and helpers, the upper acos function would be:

 

        [LispFunction("acos")]
        public double Acos(ResultBuffer resbuf)
        {
            return resbuf.CatchApply(rb =>
            {
                double d = rb.GetAsArray(1)[0].GetAsNumber();
                if (d < -1.0 || d > 1.0)
                    throw new LispException("the value must be between -1.0 and 1.0 inclusive");
                return Math.Acos(d);
            });
        }

 

 

For more information about this, you can see here.

Gilles Chanteau
Valued Contributor
FRFR1426
Posts: 97
Registered: ‎04-05-2012
Message 6 of 10 (505 Views)

Re: How to cleanly throw an exception from LispFunction

12-17-2013 02:54 AM in reply to: _gile

Ok, I understand but as there is no clean integration between AutoLISP and .NET regarding error handling, you need to find a workaround. A modal alert box == no unit testing.

*Expert Elite*
_gile
Posts: 2,125
Registered: ‎04-29-2006
Message 7 of 10 (457 Views)

Re: How to cleanly throw an exception from LispFunction

12-18-2013 06:10 AM in reply to: FRFR1426

Salut Maxence,

 

Sans l'avatar, je ne t'avais pas reconnu.

Gilles Chanteau
Valued Mentor
DiningPhilosopher
Posts: 370
Registered: ‎05-06-2012
Message 8 of 10 (443 Views)

Re: How to cleanly throw an exception from LispFunction

12-18-2013 01:12 PM in reply to: FRFR1426

Having to manually check a result returned by externally-defined LISP functions wouldn't be something I could recommend, as it could easily complicate the consuming LISP code. 

The problem is easily solved internally, by simply allowing an error string to be returned by externally-defined LISP functions, along with a result code signaling that an error occurred, and that the returned string should be passed to the (*error*) function, or be returned by a call to (vl-catch-all-error-message).  That would allow specific types of errors to be handled intelligently by consuming LISP code, if needed. If there is no error handler deined, AutoCAD would simply display the returned string in lieu of the generic message it currently displays (which is not terribly useful if you think about it).  

Valued Contributor
FRFR1426
Posts: 97
Registered: ‎04-05-2012
Message 9 of 10 (404 Views)

Re: How to cleanly throw an exception from LispFunction

12-20-2013 12:45 AM in reply to: _gile

_gile a écrit :

Salut Maxence,

 

Sans l'avatar, je ne t'avais pas reconnu.


Yes, I'm using our ADN account to post because we can now send our question to the ADN team if there is no answer given by the community. I have not found how to change the nickname, but I have updated my avatar. It makes the forum prettier :smileywink:

ADN Support Specialist
StephenPreston
Posts: 422
Registered: ‎05-22-2006
Message 10 of 10 (384 Views)

Re: How to cleanly throw an exception from LispFunction

12-20-2013 09:30 AM in reply to: FRFR1426

>>I have not found how to change the nickname

 

Click on 'Edit Account' at the top of the fiorum webpage.

Change your 'User Id'.

 

(I was wondering why you were posting using your ADN ID as your username :-).

Cheers,

Stephen Preston
Autodesk Developer Network
Post to the Community

Have questions about Autodesk products? Ask the community.

New Post
Announcements
Do you have 60 seconds to spare? The Autodesk Community Team is revamping our site ranking system and we want your feedback! Please click here to launch the 5 question survey. As always your input is greatly appreciated.