Author Topic: LispFunction and signaling an error  (Read 13487 times)

0 Members and 1 Guest are viewing this topic.

Glenn R

  • Guest
LispFunction and signaling an error
« on: October 26, 2007, 06:37:06 AM »
I've just started delving into the LispFunction attribute and I'm wondering how you would signal an error back to lisp.
For instance, in ARX, I think I used to call acdbFail("Your error message here") and return RSERR from an acedRegFunc'ed command/handler.

This would stop lisp dead with the descriptive message. For example, when you pass the worng number or type of arguments to
an in-built lisp function, it complains heartilly and stops...how do we do the same thing??? PInvoke maybe...

You can imagine, that with the LispFunction signature taking a Resbuf argument, that if that resbuf was null, or had the wrong number or
type of arguments, you would want your function to complain loudly and stop lisp not just return nil for instance.

Any thoughts are welcome.

Cheers,
Glenn.

LE

  • Guest
Re: LispFunction and signaling an error
« Reply #1 on: October 26, 2007, 03:07:41 PM »
Hola, Glenn;

I have done like three or four lisp function with C#, but have not found a way to test if the arguments in the resbuf are the right ones, as what we can do in ARX.

Like:

ARX - to make sure the function has two arguments and both are ename's
Code: [Select]
if (pArgs && pArgs->restype == RTENAME && pArgs->rbnext && pArgs->rbnext->restype == RTENAME)   
{

}

I have all the returned code values from the adscodes.h (there are some samples in the adesk net forum - from some of the ones that knowns C#)  but have not done any real test as the one above. :)

And yes for example this function
Code: [Select]
[LispFunction("Summation")]
static public void summation(ResultBuffer args)
{
    Document doc = acadApp.DocumentManager.MdiActiveDocument;
    Editor ed = doc.Editor;
    Dictionary<string, short> openWith = new Dictionary<string, short>();
    IEnumerator it = args.GetEnumerator();
    while (it.MoveNext())
    {
        short ad = 0, num = 0;
        TypedValue val = (TypedValue)it.Current;
        // ignore start or end of list
        short RTLB = 5016, RTLE = 5017;
        if (val.TypeCode == RTLB || val.TypeCode == RTLE) { }
        else
        {
            string sKey = val.Value.ToString();
            it.MoveNext();
            val = (TypedValue)it.Current;
            num = (short)val.Value;
            try
            {
                openWith.Add(sKey, num);
            }
            catch (ArgumentException)
            {
                if (openWith.TryGetValue(sKey, out ad))
                {
                    num = (short)(num + ad);
                    openWith.Remove(sKey);
                    openWith.Add(sKey, num);
                }
            }
        }
    }
    foreach (KeyValuePair<string, short> kvp in openWith)
    {
        ed.WriteMessage("\nKey = {0}, Value = {1}",
            kvp.Key, kvp.Value);
    }
}

If we, don't pass the right argument(s) it will throw something like:
Quote
Command: (summation "test")
System.Reflection.TargetInvocationException: Exception has been thrown by the
target of an invocation. ---> System.InvalidOperationException: Operation is
not valid due to the current state of the object.
   at Autodesk.AutoCAD.DatabaseServices.ResultBufferEnumerator.get_Current()
   at
Autodesk.AutoCAD.DatabaseServices.ResultBufferEnumerator.IEnumerator.get_Current
()
   at ClassLibrary.LESQClass.summation(ResultBuffer args) in
C:\Programming\C#\AddTicks17\Class.cs:line 1153
   --- End of inner exception stack trace ---
   at System.RuntimeMethodHandle._InvokeMethodFast(Object target, Object[]
arguments, SignatureStruct& sig, MethodAttributes methodAttributes,
RuntimeTypeHandle typeOwner)
   at System.RuntimeMethodHandle.InvokeMethodFast(Object target, Object[]
arguments, Signature sig, MethodAttributes methodAttributes, RuntimeTypeHandle
typeOwner)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags
invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean
skipVisibilityChecks)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags
invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at AcMgCommandClass.InvokeWorker(AcMgCommandClass* , MethodInfo mi, Object
commandObject, Boolean bLispFunction)
   at AcMgCommandClass.Invoke(AcMgCommandClass* ,
gcroot<System::Reflection::MethodInfo ^>* mi, Boolean bLispFunction)
   at Autodesk.AutoCAD.Runtime.TargetInvocationSEHExceptionFilter.InvokeWorker()
   at Autodesk.AutoCAD.Runtime.ExceptionFilter.Invoke(); error: ADS request
error

Because requires a list of list's:
Quote
(setq lst (list '("one" 2) '("one" 3) '("two" 4) '("two" 5)))

Now, how do we check if we are passing the above to our function?

There are forms that might help, but I have not gone that far, cause I simple have not needed anything in lisp :-( - But, I am interested in the the know how....

Or inside of the: foreach (TypedValue rb in args) - grab or test what it is needed...
Code: [Select]
            foreach (TypedValue rb in args)
            {
                switch ((LispDataType)rb.TypeCode)
                {
                    case LispDataType.ListBegin:
                        ed.WriteMessage("\nListBegin");
                        break;
                    case LispDataType.ListEnd:
                        ed.WriteMessage("\nListEnd");
                        break;
                    case LispDataType.Text:
                        ed.WriteMessage("\nText");
                        break;
                    case LispDataType.Int16:
                        ed.WriteMessage("\nInt16");
                        break;
                    default:
                        ed.WriteMessage("\nOther [{0}] ", rb.Value);
                        break;
                }
            }
            ed.WriteMessage("\n");

Maybe, if we first use a ResultBufferEnumerator and MoveNext() - verify all the items and once pass the test, use the Reset() and reuse the iterator....

Hope, makes some sense.... if not no problem :)

Glenn R

  • Guest
Re: LispFunction and signaling an error
« Reply #2 on: October 26, 2007, 08:26:32 PM »
Luis,

As far as testing the arguments, how about this:

LillyPondCode

LE

  • Guest
Re: LispFunction and signaling an error
« Reply #3 on: October 26, 2007, 08:40:10 PM »
Luis,

As far as testing the arguments, how about this:

LillyPondCode

Looks like ObjectARX way to me..... good! -

I been always using the enumerator from the ResultBuffer... not the Array - it opens more possibilities.


Thank you for sharing!

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8706
  • AKA Daniel
Re: LispFunction and signaling an error
« Reply #4 on: October 26, 2007, 10:49:32 PM »

How about throwing an exception, instead of forcing one?

Code: [Select]
[LispFunction("Test")]
    static public ResultBuffer errtest(ResultBuffer Rb)
    {
      ResultBuffer RetBuf = new ResultBuffer();

      if (Rb == null)
      {
        throw new Autodesk.AutoCAD.Runtime.Exception
          (ErrorStatus.InvalidInput, "\n.\n** Incorrect Number of Parameters **\n.\n.\n.");
      }

      List<TypedValue> ArgList = new List<TypedValue>(Rb.AsArray());

      if (ArgList.Count != 1)
      {
        throw new Autodesk.AutoCAD.Runtime.Exception
          (ErrorStatus.InvalidInput, "\n.\n** Incorrect Number of Parameters **\n.\n.\n.");
      }

      if (ArgList[0].TypeCode != (int)LispDataType.Text)
      {
        throw new Autodesk.AutoCAD.Runtime.Exception
          (ErrorStatus.InvalidInput, "\n.\n** Parameter 1 should be a string **\n.\n.\n.");
      }

      RetBuf.Add(new TypedValue((int)LispDataType.Text, ArgList[0].Value));

      return RetBuf;
    }

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8706
  • AKA Daniel
Re: LispFunction and signaling an error
« Reply #5 on: October 27, 2007, 12:20:09 AM »
After playing with this for a while, I think that I am more inclined to return nil and generate some sort of an error log.
Who knows what un-freed unmanaged resources / memory leaks might be left behind by ungracefully throwing exceptions.
I also think its better to let the consumer of the function decide how to handle the return value.

Dan

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: LispFunction and signaling an error
« Reply #6 on: October 27, 2007, 02:15:10 AM »
.............  I also think its better to let the consumer of the function decide how to handle the return value.

Dan

That would be my design model too.

paraphrased : give me back by ball, If you wont play by my rules I'm not playing any longer ..
kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

Glenn R

  • Guest
Re: LispFunction and signaling an error
« Reply #7 on: October 27, 2007, 07:06:48 AM »
Dan,

Shame on you ;) Throwing a .NET excpetion to lisp!

Seriously, what I was after is the same functionality as autolisp gives.

Ponder the following:
Code: [Select]
Command: (/ 1 0)
; error: divide by zero

NOT this:
Code: [Select]
Command: (test)
Autodesk.AutoCAD.Runtime.Exception:
.
** Incorrect Number of Parameters **
.
.
.
   at CsMgdAcad3.tcgsCommands.errtest(ResultBuffer Rb) in C:\Documents and
Settings\Glenn\My Documents\Visual Studio
2005\Projects\CsMgdAcad3\CsMgdAcad3\Commands.cs:line 50
   at AcMgCommandClass.InvokeWorker(AcMgCommandClass* , MethodInfo mi, Object
commandObject, Boolean bLispFunction)
   at AcMgCommandClass.InvokeWorkerWithExceptionFilter(AcMgCommandClass* ,
MethodInfo mi, Object commandObject, Boolean bLispFunction)
   at AcMgCommandClass.Invoke(AcMgCommandClass* ,
gcroot<System::Reflection::MethodInfo ^>* mi, Boolean bLispFunction)
   at AcMgCommandClass.CommandThunk.InvokeLisp(CommandThunk* ); error: ADS
request error

Hence, my original question - you should NOT just return NIL if there is a serious error preventing you from continuing,
but it should be as 'nice' as the lisp errors are on the command line.

Thanks for taking a look.

Cheers,
Glenn.

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: LispFunction and signaling an error
« Reply #8 on: October 27, 2007, 07:42:59 AM »
[hint]
ERRNO system variable
[/hint]

(if (and
        returnVal
        (not (zerop ERRNO ))
    )
    (proceed ... 

« Last Edit: October 27, 2007, 07:45:34 AM by Kerry Brown »
kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8706
  • AKA Daniel
Re: LispFunction and signaling an error
« Reply #9 on: October 27, 2007, 07:51:39 AM »
Dan,

Shame on you ;) Throwing a .NET excpetion to lisp!

Hehe well you said make it complain loudly   :-o




Glenn R

  • Guest
Re: LispFunction and signaling an error
« Reply #10 on: October 27, 2007, 08:23:28 AM »
Hehe true...that I did and you certianly did :)

But seriously, I think we still an 'elegant' answer as it were...

Glenn R

  • Guest
Re: LispFunction and signaling an error
« Reply #11 on: October 27, 2007, 08:26:07 AM »
Kerry,

Regardless of setting ERRNO, that example you gave would still rely on SOMETHING being returned to lisp...that's the point. We want to make lisp stop dead the same way as the inbuilt lisp functions do.

I think....

Cheers,
Glenn.

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8706
  • AKA Daniel
Re: LispFunction and signaling an error
« Reply #12 on: October 27, 2007, 09:26:10 AM »
try this

Code: [Select]
// PostCommand("CANCELCMD");
    [DllImport("acad.exe",CharSet = CharSet.Unicode,
    CallingConvention = CallingConvention.Cdecl,
    EntryPoint = "?acedPostCommand@@YAHPB_W@Z")]
    extern static public int PostCommand(string cmd);

edit:

see this link http://through-the-interface.typepad.com/through_the_interface/2006/08/cancelling_an_a.html
« Last Edit: October 27, 2007, 11:11:37 AM by Daniel »

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8706
  • AKA Daniel
Re: LispFunction and signaling an error
« Reply #13 on: October 27, 2007, 09:38:41 AM »
example

Code: [Select]
   // PostCommand("CANCELCMD");
    [DllImport("acad.exe", CharSet = CharSet.Unicode,
    CallingConvention = CallingConvention.Cdecl,
    EntryPoint = "?acedPostCommand@@YAHPB_W@Z")]
    extern static public int PostCommand(string cmd);

    [LispFunction("Test")]
    static public ResultBuffer errtest(ResultBuffer Rb)
    {
      ResultBuffer RetBuf = new ResultBuffer();
      try
      {
        if (Rb == null)
          return MyError("too few arguments");
       
        List<TypedValue> ArgList = new List<TypedValue>(Rb.AsArray());

        if (ArgList.Count > 1)
          return MyError("too many arguments");

        if (ArgList[0].TypeCode != (int)LispDataType.Text)
          return MyError("bad argument type: stringp " + ArgList[0].Value.ToString());

        RetBuf.Add(new TypedValue((int)LispDataType.Text, ArgList[0].Value));
      }
      catch
      {
        return MyError("Automation Error. method TEST  failed.");
      }
      return RetBuf;
    }

    public static ResultBuffer MyError(String str)
    {
      Editor ed = AcadApp.DocumentManager.MdiActiveDocument.Editor;
      ed.WriteMessage("\n; error: " + str + "\n");
      PostCommand("CANCELCMD");
      return null;
    }

Edit: used more lisp like error messages
« Last Edit: October 27, 2007, 01:36:41 PM by Daniel »

LE

  • Guest
Re: LispFunction and signaling an error
« Reply #14 on: October 27, 2007, 12:08:25 PM »
Just for reference - the only information available in the arx docs
Quote
Defining Methods That Can Be Called From AutoLISP
 
 

Managed applications can define methods so that they can be called by AutoLISP applications. To do so, the managed application tags the desired method with the Autodesk.AutoCAD.Runtime.LispFunction attribute. This attribute can be used in the same places and has the same properties as the CommandMethod attribute. The key difference lies in the signature form to which LispFunction may be applied. LispFunction is valid only for methods of the form

public ResultType MyHandler(ResultBuffer args) {
...
}
where ResultType can be any one of the following types:

int
double
TypedValue
ResultBuffer
string
Point2d
Point3d
bool
void
ObjectId
SelectionSet
The Autodesk.AutoCAD.Runtime.LispDataType enumeration defines .NET identifiers that represent the data types passed through AutoLISP ResultBuffer arguments.

For instance, the following C# code defines an AutoLISP-callable “Hello World” method:

using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.ApplicationServices;
...
[lispFunction("c:helloworld")]
public void hw(ResultBuffer args)
{
    Editor ed =
        Application.DocumentManager.MdiActiveDocument.Editor;
    ed.WriteMessage('\n' + "Hello World!" + '\n');
}