Author Topic: AutoCAD.NET | Handling *Exceptions  (Read 21684 times)

0 Members and 1 Guest are viewing this topic.

irneb

  • Water Moccasin
  • Posts: 1794
  • ACad R9-2016, Revit Arch 6-2016
Re: AutoCAD.NET | Handling *Exceptions
« Reply #30 on: May 01, 2012, 09:22:03 AM »
And BTW, that previous samples were done using VS2008 with .Net 3.0 inside Vanilla ACad 2008 on XP32. The same thing still applies if I run the same DLL in Vanilla 2012 on W7-64:
Code - Auto/Visual Lisp: [Select]
  1. Command: (setq return (vl-catch-all-apply 'SomeLispFunc (list "Test")))
  2. System.ArgumentException: Too many arguments
  3.    at MyLisp.MyLisp.SomeLispFunc (ResultBuffer args) in
  4. J:\Programming\MyLisp\MyLisp.cs:line 22
  5.    at Autodesk.AutoCAD.Runtime.CommandClass.InvokeWorker(MethodInfo mi, Object
  6. commandObject, Boolean bLispFunction)
  7.    at
  8. Autodesk.AutoCAD.Runtime.CommandClass.InvokeWorkerWithExceptionFilter(MethodInfo
  9.  mi, Object commandObject, Boolean bLispFunction)
  10.    at Autodesk.AutoCAD.Runtime.PerDocumentCommandClass.Invoke(MethodInfo mi,
  11. Boolean bLispFunction)
  12.    at
  13. Autodesk.AutoCAD.Runtime.CommandClass.CommandThunk.InvokeLisp()#<%catch-all-appl
  14. y-error%>
  15.  
  16. "ADS request error"
Common sense - the curse in disguise. Because if you have it, you have to live with those that don't.

TheMaster

  • Guest
Re: AutoCAD.NET | Handling *Exceptions
« Reply #31 on: May 01, 2012, 09:47:15 AM »
Now here's a nettle for you. Even if I have a throw statement somewhere to return an error to lisp, the wrong error text gets sent to lisp along with an entire error stack trace.

And this was tested using both a debug and release build. Doesn't matter if VS started acad, or even if it's running.

....
So the problem is twofold:
  • I don't want the entire stack trace on the user's command-line, I just want to show him an error message using princ / alert (at worst, probably run some other lisp in case of some type of error). This stack is just clutter to a normal user.
  • The returned message is simply always a "ADS request error" ... so without the stack trace you have no way of passing a custom error back to lisp. And thus you can't distinguish something like a out of bounds exception from a argument exception, making such throws an all or nothing scenario.
At least that's from my understanding & testing. I hope I'm just missing something here, perhaps just a compiler setting or so. Anyone have an idea how to do this?

I believe that's how it's worked from day 1 (of LispFunction), and I agree that
it's not the right way to handle errors.  For the purpose of catching errors in the
calling LISP, your only option would be to return something signaling a failure
rather than throwing an error. You could return nil to signal that, but in some
cases, nil may be a valid result, so the caller can't distinguish between that and
an error condition.

One way to deal with that is to expose a method that returns the Message
of the exception that was caught during the most recent invocation of one
of your LISP functions:

Code - C#: [Select]
  1.  
  2. public static class Class1
  3. {
  4.  
  5.   static string last_error = null;
  6.  
  7.   [LispFunction("MYLISPFUNCTION")]
  8.   public static object MyLispFuncion( ResultBuffer args )
  9.   {
  10.     last_error = null;
  11.     try
  12.     {
  13.          // code that may fail here
  14.          // or succeed and return a valid result
  15.     }
  16.     catch( SomeSpecialException ex )
  17.     {
  18.        last_error = ex.Message;
  19.        return null;
  20.     }
  21.   }
  22.  
  23.   // LISP user can call this to get the exception message:
  24.  
  25.   [LispFunction("GET-LAST-ERROR")]
  26.   public static string MyLispFuncion( ResultBuffer args )
  27.   {
  28.     return last_error ?? string.Empty;
  29.   }
  30. }
  31.  

If you don't anticipate the error and catch it in managed code, then the
LISP error and stack trace will happen and in most cases, that's what
should happen (the calling LISP code should stop).


irneb

  • Water Moccasin
  • Posts: 1794
  • ACad R9-2016, Revit Arch 6-2016
Re: AutoCAD.NET | Handling *Exceptions
« Reply #32 on: May 01, 2012, 10:09:07 AM »
Yep, I was thinking along such lines as well. Though I can see a lot of problems if you have more than one assembly doing the same error handling technique: name conflicts spring to mind - thus only the last assembly loaded would define the GET-LAST-ERROR function.

Another thing I've "thought" about  :lmao: is to call a function in lisp which then casts an error. Something like the vl-exit-with-error function. Though that would need a lot of investigation. If I believe the help on the function it would simply end all lisp to the command-line and call the *error* function - no way of catching such with vl-catch-all-apply. Not to mention you'd need some serious coding to use PInvoke to call that function if ACad Version < 2012.
Common sense - the curse in disguise. Because if you have it, you have to live with those that don't.

TheMaster

  • Guest
Re: AutoCAD.NET | Handling *Exceptions
« Reply #33 on: May 01, 2012, 11:15:04 AM »
Yep, I was thinking along such lines as well. Though I can see a lot of problems if you have more than one assembly doing the same error handling technique: name conflicts spring to mind - thus only the last assembly loaded would define the GET-LAST-ERROR function.

Another thing I've "thought" about  :lmao: is to call a function in lisp which then casts an error. Something like the vl-exit-with-error function. Though that would need a lot of investigation. If I believe the help on the function it would simply end all lisp to the command-line and call the *error* function - no way of catching such with vl-catch-all-apply. Not to mention you'd need some serious coding to use PInvoke to call that function if ACad Version < 2012.

The 'get-last-error' workaround could be shared by many assemblies by adding public methods to set and clear the last error message, so that any number of assemblies could use a single static class for setting/clearing the error, and only that one assembly would define the LISP function needed to retrieve it.

As far as vl-exit-with-error, that was intended for separate namespace VLX code to raise an error, but you still may be able to call it using acedInvoke() or VL.Application. It may require a call to vl-acad-defun to register it for calling via acedInvoke().


GUIDO ROOMS

  • Guest
Re: AutoCAD.NET | Handling *Exceptions
« Reply #34 on: November 18, 2012, 04:31:19 AM »
I'm using the following kludge to circumvent the problem:

1) When certain exceptions occur in a lispfunction (eg. an invalid number of arguments was used to call the function from lisp), I display a message at the prompt, signalling this. So the user or programmer knows there has been an error in
a given function.

2) Then I return an invalid ResultBuffer or TypedValue, that is, one containing an unmatched left parenthesis. That is, I return a New TypedValue(LispDataType.ListBegin,-1) or a resultbuffer containing just that. In this case, the following message is displayed at the autocad prompt: "unclosed :LB in reslist: (:LB)".

3) I redefine the *error* function so that it ignores this string.

The result is that the error gets signalled to users without autocad spewing error messages at the prompt that are incompreshensible to them. And also that the running function or command aborts, if the author of the lispfunction wants that.

Of course, if there's a real error of this kind, an unmatched left parenthesis, the redefined *error* function will hide that, too.
As I mentioned, it's a kludge, not a solution to the problem. But passing nil was no option for me either. Some of my functions have a T or nil result.

(I know this is a reply to an old message, but this way I didn't have to write so much.)
Greetings.




TheMaster

  • Guest
Re: AutoCAD.NET | Handling *Exceptions
« Reply #35 on: November 19, 2012, 12:59:17 AM »
I'm using the following kludge to circumvent the problem:

1) When certain exceptions occur in a lispfunction (eg. an invalid number of arguments was used to call the function from lisp), I display a message at the prompt, signalling this. So the user or programmer knows there has been an error in
a given function.

2) Then I return an invalid ResultBuffer or TypedValue, that is, one containing an unmatched left parenthesis. That is, I return a New TypedValue(LispDataType.ListBegin,-1) or a resultbuffer containing just that. In this case, the following message is displayed at the autocad prompt: "unclosed :LB in reslist: (:LB)".

3) I redefine the *error* function so that it ignores this string.

The result is that the error gets signalled to users without autocad spewing error messages at the prompt that are incompreshensible to them. And also that the running function or command aborts, if the author of the lispfunction wants that.

Of course, if there's a real error of this kind, an unmatched left parenthesis, the redefined *error* function will hide that, too.
As I mentioned, it's a kludge, not a solution to the problem. But passing nil was no option for me either. Some of my functions have a T or nil result.

(I know this is a reply to an old message, but this way I didn't have to write so much.)
Greetings.

Because of an upcoming project where there is going to be a lot of interop between LISP and managed code (about 20-30 lisp functions implemented in managed code), I moved some of the work I had started long ago in support of Lisp/Managed code interop to the front burner, and I've come up with a few things that can help save a LispFunction writer a lot of code.

I've discussed some of it here, and here's another example that can simplify supporting functions that can be called with different arguments or optional arguments:

Code - C#: [Select]
  1. //LispDynamicDispatchExample.cs  copyright 2012 (c) Tony Tanzillo
  2.  
  3. using System;
  4. using System.Linq;
  5. using System.Reflection;
  6. using System.Collections.Generic;
  7. using Autodesk.AutoCAD.ApplicationServices;
  8. using Autodesk.AutoCAD.DatabaseServices;
  9. using Autodesk.AutoCAD.Geometry;
  10. using Autodesk.AutoCAD.Runtime;
  11. using Autodesk.AutoCAD.EditorInput;
  12.  
  13. namespace Lisp.Interop.Dynamic
  14. {
  15.    public static class LispDynamicDispatchExample
  16.    {
  17.       /// <summary>
  18.       ///
  19.       /// Dynamic Dispatch: http://en.wikipedia.org/wiki/Dynamic_dispatch
  20.       ///
  21.       /// The ability to late-bind to one of several overloads of
  22.       /// a method or function, dynamically at runtime, based on
  23.       /// the number and type(s) of a set of given arguments.
  24.       ///
  25.       /// In statically-compiled languages like C or C++, dynamic
  26.       /// dispatch is not supported. In .NET, dynamic dispatch is
  27.       /// supported, but only through the use of System.Dynamic.
  28.       ///
  29.       /// In languages that don't support Dynamic Dispatch, when
  30.       /// multiple overloaded versions of a method are defined,
  31.       /// the version that will be called is determined at compile
  32.       /// time (this is referred to as 'binding'). If no method
  33.       /// matching the given arguments is found, a compile-time
  34.       /// error occurs.
  35.       ///
  36.       /// With dynamic dispatch, the process of binding to methods
  37.       /// is deferred until runtime. Hence, if no matching method can
  38.       /// be found, a runtime error occurs.
  39.       ///
  40.       /// As it turns out, this is an excellent example of how to
  41.       /// leverage the framework and get it to do a lot work for you,
  42.       /// that would otherwise require significant amounts of very
  43.       /// complicated and error-prone code.
  44.       ///
  45.       /// The (dynamic-dispatch) LISP function can be called with
  46.       /// a variety of arguments, as defined by the seven overloads
  47.       /// of the MyLispFunction() method included below. When the
  48.       /// Dispatch() extension method is passed the arguments and
  49.       /// MethodInfo of the caller, it will try to find a matching
  50.       /// overload, and if one is found, it invokes the overload
  51.       /// with the given arguments. If the arguments do not match
  52.       /// one of the overloads, an error is raised.
  53.       ///
  54.       /// LIST arguments are not supported in this demo, but the
  55.       /// ability to support lists is theoretically possible with
  56.       /// some caveats.
  57.       ///
  58.       /// The 'params' modifier that allows a method to accept an
  59.       /// array of any number of arguments of a specified type is
  60.       /// not currently supported. While supporting 'params' may be
  61.       /// possible using custom binding, it would be non-trivial.
  62.       ///
  63.       /// </summary>
  64.    
  65.       /// The "base" method which is called by the AutoCAD
  66.       /// managed runtime when the (dynamic-dispatch) LISP
  67.       /// function is called:
  68.      
  69.       [LispFunction( "dynamic-dispatch" )]
  70.       public static void MyLispFunction( ResultBuffer args )
  71.       {
  72.          args.Dispatch( MethodInfo.GetCurrentMethod() );
  73.       }
  74.  
  75.       /// Below are 7 overloads of the above MyLispFunction() method.
  76.       /// One of those overloads will be called if its argument list
  77.       /// matches the set of arguments passed from LISP (in this case,
  78.       /// 'match' means a method takes the same number of arguments as
  79.       /// was passed from LISP, and that each passed argument can be
  80.       /// assigned to the corresponding method argument variable).
  81.       ///
  82.       /// The overloads must be static methods, with the same name
  83.       /// as the 'base' method that has the LispFunction attribute
  84.       /// applied to it (overloads must NOT have the LispFunction
  85.       /// attribute applied to them).
  86.       ///
  87.       /// Given the set of overloads implemented below, any of the
  88.       /// following calls to (dynamic-dispatch) will succeed:
  89.       ///
  90.       ///    (dynamic-dispatch "foo")
  91.       ///    (dynamic-dispatch "foo" 10)
  92.       ///    (dynamic-dispatch "foo" "bar")
  93.       ///    (dynamic-dispatch pi (getvar "lastpoint"))
  94.       ///    (dynamic-dispatch 2.459 (entlast) "hello")
  95.       ///    (dynamic-dispatch "foo" (ssget "x"))
  96.       ///    (dynamic-dispatch "foo" T)    
  97.       ///    (dynamic-dispatch "foo" NIL)  
  98.       ///
  99.       /// Each of the above calls resolves to a call to one of
  100.       /// the following overloads. Also note that this mechanism
  101.       /// provides for optional arguments in the exact same way
  102.       /// they can be implemented in .NET code, via the use of
  103.       /// method overloading. For example, the first overloaded
  104.       /// function takes a string. The second overload takes a
  105.       /// string and an int, implicitly making the second int
  106.       /// argument optional.
  107.       ///
  108.       /// The functionality provided by the DynamicDispatcher
  109.       /// class achives something that would otherwise require
  110.       /// a sigificant amount of somewhat "messy", error-prone
  111.       /// code.
  112.  
  113.       public static void MyLispFunction( string a )
  114.       {
  115.          Utils.WriteLine( ">>> MyLispFunction( {0} )", a );
  116.       }
  117.  
  118.       public static void MyLispFunction( string a, int b )
  119.       {
  120.          Utils.WriteLine( ">>> MyLispFunction( {0}, {1} )", a, b );
  121.       }
  122.  
  123.       public static void MyLispFunction( double a, Point3d b )
  124.       {
  125.          Utils.WriteLine( ">>> MyLispFunction( {0}, {1} )", a, b );
  126.       }
  127.  
  128.       public static void MyLispFunction( string a, string b )
  129.       {
  130.          Utils.WriteLine( ">>> MyLispFunction( {0}, {1} )", a, b );
  131.       }
  132.  
  133.       public static void MyLispFunction( double a, ObjectId b, string c )
  134.       {
  135.          Utils.WriteLine( ">>> MyLispFunction( {0}, {1}, {2} )", a, b, c );
  136.       }
  137.  
  138.       public static void MyLispFunction( string a, SelectionSet b )
  139.       {
  140.          Utils.WriteLine( ">>> MyLispFunction( {0}, {1} )", a, b );
  141.       }
  142.  
  143.       public static void MyLispFunction()
  144.       {
  145.          Utils.WriteLine( ">>> MyLispFunction()" );
  146.       }
  147.  
  148.       /// To invoke this method you must pass the symbol T or NIL
  149.       /// in the second argument. Contrary to LISP conventions,
  150.       /// no other value is accepted (in LISP, Nil = 'false' and
  151.       /// anything else implies 'true', but that convention can
  152.       /// not be used here without opening a pandora's box that
  153.       /// could lead to selecting the wrong overload).
  154.  
  155.       public static void MyLispFunction( string a, bool b )
  156.       {
  157.          Utils.WriteLine( ">>> MyLispFunction( {0}, {1} )", a, b );
  158.       }
  159.    }
  160.  
  161.    public static class DynamicDispatcher
  162.    {
  163.       const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic
  164.          | BindingFlags.Static | BindingFlags.FlattenHierarchy;
  165.  
  166.       public static object Dispatch( this ResultBuffer args, MethodBase baseMethod )
  167.       {
  168.          if( baseMethod == null )
  169.             throw new ArgumentNullException( "baseMethod" );
  170.          // Validate and pre-process the arguments:
  171.             TypedValue[] array = args != null ? args.Cast<TypedValue>().Select( Validate ).ToArray() : new TypedValue[0];
  172.          // Try to find a matching overload of the caller:
  173.          MethodInfo method = baseMethod.DeclaringType.GetMethod(
  174.             baseMethod.Name, bindingFlags, null,
  175.             array.Select( tv => tv.Value.GetType() ).ToArray(), null );
  176.          if( method == null )
  177.             throw new MissingMethodException( baseMethod.DeclaringType.Name, baseMethod.Name );
  178.          else
  179.          {
  180.             object[] values = array != null ? array.Select( t => t.Value ).ToArray() : null;
  181.             Utils.WriteLine( "\n\nSelected method: {0}:", method.ToString() );
  182.             return method.Invoke( null, values );
  183.          }
  184.       }
  185.  
  186.       public static T Dispatch<T>( this ResultBuffer args, MethodBase baseMethod )
  187.       {
  188.          return (T) Dispatch( args, baseMethod );
  189.       }
  190.  
  191.       static short RTNIL = (short) LispDataType.Nil;
  192.       static short RTT = (short) LispDataType.T_atom;
  193.       static short RTLB = (short) LispDataType.ListBegin;
  194.  
  195.       static TypedValue Validate( TypedValue tv )
  196.       {
  197.          short code = tv.TypeCode;
  198.          if( code == RTNIL )
  199.             return new TypedValue( RTNIL, false ); // interpret NIL as boolean false
  200.          if( code == RTT )
  201.             return new TypedValue( RTT, true );  // interpret T as boolean true
  202.          if( code == RTLB )
  203.             throw new ArgumentException( "lists not supported in this example" );
  204.          if( tv.Value == null )
  205.             throw new ArgumentException( "value cannot be null" );
  206.          return tv;
  207.       }
  208.    }
  209.  
  210.    internal static class Utils
  211.    {
  212.       public static void WriteLine( string fmt, params object[] args )
  213.       {
  214.          Document doc = Application.DocumentManager.MdiActiveDocument;
  215.          if( doc != null )
  216.          {
  217.             string msg = fmt.StartsWith( "\n" ) ? fmt : "\n" + fmt;
  218.             msg = string.Format( msg, args );
  219.             doc.Editor.WriteMessage( msg );
  220.          }
  221.       }
  222.    }
  223.  
  224.    /*  ;; Tester - needs at least one entity in the current drawing:
  225.  
  226.       (defun dynamic-dispatch-test ()
  227.          (dynamic-dispatch "foo")
  228.          (dynamic-dispatch "foo" 10)
  229.          (dynamic-dispatch "foo" "bar")
  230.          (dynamic-dispatch pi (getvar "lastpoint"))
  231.          (dynamic-dispatch 2.459 (entlast) "hello")
  232.          (dynamic-dispatch "foo" (ssget "x"))
  233.          (dynamic-dispatch "foo" T)    
  234.          (dynamic-dispatch "foo" NIL)  
  235.       )
  236. */
  237.  
  238. }
  239.  
« Last Edit: November 19, 2012, 03:17:40 PM by TT »

GUIDO ROOMS

  • Guest
Re: AutoCAD.NET | Handling *Exceptions
« Reply #36 on: November 19, 2012, 12:19:54 PM »
Thanks for the example Tony.
As I wrote yesterday, the thing I came up with was a kludge.
Greetings.

TheMaster

  • Guest
Re: AutoCAD.NET | Handling *Exceptions
« Reply #37 on: November 19, 2012, 10:16:03 PM »
Thanks for the example Tony.
As I wrote yesterday, the thing I came up with was a kludge.
Greetings.

What I posted started out as a kludge, but since posting it, i've figured out how to add support for list arguments as well (with the latest version, you just declare an argument in an overload as 'object[]').  :laugh: