Author Topic: Calling .net LispFunction while processing DBX  (Read 9128 times)

0 Members and 1 Guest are viewing this topic.

BlackBox

  • King Gator
  • Posts: 3770
Re: Calling .net LispFunction while processing DBX
« Reply #15 on: October 30, 2012, 02:21:41 PM »
FWIW -

Gile offers some LispException Classes at the bottom of this post that I have found to be very useful for my own LispFunction Methods.
"How we think determines what we do, and what we do determines what we get."

CADDOG

  • Newt
  • Posts: 82
  • wishbonesr
Re: Calling .net LispFunction while processing DBX
« Reply #16 on: October 30, 2012, 02:40:32 PM »
LispException

You read my mind.  Thanks RenderMan.

BlackBox

  • King Gator
  • Posts: 3770
Re: Calling .net LispFunction while processing DBX
« Reply #17 on: October 30, 2012, 02:43:43 PM »
LispException

You read my mind.  Thanks RenderMan.

Thank Gile, as I am but the mirror:

Quote
There are two ways of spreading light, to be the candle or the mirror that reflects it - Edith Wharton
"How we think determines what we do, and what we do determines what we get."

CADDOG

  • Newt
  • Posts: 82
  • wishbonesr
Re: Calling .net LispFunction while processing DBX
« Reply #18 on: October 30, 2012, 03:30:18 PM »
Absolutely, a big thank you to Gile.  I've followed many of his posts suggestions.

TheMaster

  • Guest
Re: Calling .net LispFunction while processing DBX
« Reply #19 on: October 30, 2012, 09:00:12 PM »
FWIW -

Gile offers some LispException Classes at the bottom of this post that I have found to be very useful for my own LispFunction Methods.

I can't remember how long ago it was that I started on this, but I know I never finished it. It's mostly useful with lisp functions that take 'fixed' argument lists. The plan was to extend it to support optional arguments, and repeatable arguments, but I never got around to that.   :oops:

Here's some examples from the console (without the stacktrace):

Code - Text: [Select]
  1.  
  2. Command: (foobar 1 2 3)
  3. System.ArgumentException:
  4.    Error: Bad argument type (index = 1)
  5.    Usage: (foobar int real string)
  6.  
  7. Command: (foobar 99 2.5 "hello")
  8. (foobar): Arguments are correct
  9. nil
  10.  
  11. Command: (foobar "fubar")
  12. System.ArgumentException:
  13.    Error: Incorrect number of arguments
  14.    Usage: (foobar int real string)
  15.  
  16.  


And here's the code:

Code - C#: [Select]
  1.  
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Text;
  6. using Autodesk.AutoCAD.Runtime;
  7. using System.Reflection;
  8. using Autodesk.AutoCAD.DatabaseServices;
  9. using Autodesk.AutoCAD.EditorInput;
  10. using Autodesk.AutoCAD.ApplicationServices;
  11.  
  12. namespace LispHelpers
  13. {
  14.  
  15.    /// <summary>
  16.    ///
  17.    /// A crude implementation of contract enforcement for LISP
  18.    /// functions using Attributes.
  19.    ///
  20.    /// The example below should be self-explainatory.
  21.    ///
  22.    /// </summary>
  23.    public class RequiredArgumentsAttribute : System.Attribute
  24.    {
  25.       LispDataType[] argtypes = null;
  26.       public RequiredArgumentsAttribute( params LispDataType[] types )
  27.       {
  28.          this.argtypes = types ?? new LispDataType[0];
  29.       }
  30.  
  31.       public static TypedValue[] Check( ResultBuffer args, MethodBase method )
  32.       {
  33.          LispFunctionAttribute lfa = method.GetAttribute<LispFunctionAttribute>();
  34.          if( lfa == null )
  35.             throw new ArgumentException( "Method must have the [LispFunction] Attribute applied to it" );
  36.          RequiredArgumentsAttribute rqa = method.GetAttribute<RequiredArgumentsAttribute>();
  37.          LispDataType[] prototype = rqa.argtypes;
  38.          string func = lfa.GlobalName;
  39.          TypedValue[] values = args != null ? args.AsArray() : new TypedValue[0];
  40.          if( prototype == null )
  41.             return values;
  42.          if( values.Length == 0 && prototype.Length == 0 )
  43.             return values;
  44.          if( values.Length != prototype.Length )
  45.             Error( "Incorrect number of arguments", func, prototype );
  46.          for( int i = 0; i < values.Length; i++ )
  47.          {
  48.             if( ! CheckArg( values[i], prototype[i] ) )
  49.             {
  50.                Error( string.Format( "Bad argument type (index = {0})", i ), func, prototype );
  51.             }
  52.          }
  53.          return values;
  54.       }
  55.  
  56.       private static void Error( string msg, string func, LispDataType[] args )
  57.       {
  58.          string errorMsg = string.Format( "\n   Error: {0}\n   Usage: {1}\n\n", msg,
  59.             FormatArgs( func, args ) );
  60.          throw new ArgumentException( errorMsg );
  61.       }
  62.  
  63.       static bool CheckArg( TypedValue v, LispDataType type )
  64.       {
  65.          short code = (short) v.TypeCode;
  66.          object value = v.Value;
  67.          LispDataType argType = (LispDataType) code;
  68.          short typeCode = (short) type;
  69.          if( code == typeCode )
  70.             return true;
  71.          if( type.EqualsAny( LispDataType.Int16, LispDataType.Int32 ) &&
  72.             argType.EqualsAny( LispDataType.Int32, LispDataType.Int16 ) )
  73.             return true;
  74.          return false;
  75.       }
  76.  
  77.       static string FormatArgs( string func, LispDataType[] args )
  78.       {
  79.          if( args == null || args.Length == 0 )
  80.             return "(no arguments)";
  81.          return string.Format( "({0} {1})", func,
  82.             string.Join( " ", args.Select( a => typeNames[a] ).ToArray() ) );
  83.       }
  84.  
  85.       static Dictionary<LispDataType, string> typeNames = ( (LispDataType[]) Enum.GetValues( typeof( LispDataType ) ) ).ToDictionary(
  86.          v => v, v => v.ToString() );
  87.  
  88.       // currently not used
  89.       static Dictionary<LispDataType, Type> typeMap = new Dictionary<LispDataType, Type>();
  90.  
  91.       static Dictionary<Type, LispDataType> types = new Dictionary<Type, LispDataType>();
  92.  
  93.       static RequiredArgumentsAttribute()
  94.       {
  95.          // Not all types can be mapped to a LispDataType
  96.          types[typeof( ObjectId )] = LispDataType.ObjectId;
  97.          types[typeof( SelectionSet )] = LispDataType.SelectionSet;
  98.          types[typeof( Int32 )] = LispDataType.Int32;
  99.          types[typeof( Int16 )] = LispDataType.Int16;
  100.          types[typeof( double )] = LispDataType.Double;
  101.          types[typeof( string )] = LispDataType.Text;
  102.          
  103.          typeNames[LispDataType.ObjectId] = "ename";
  104.          typeNames[LispDataType.ListBegin] = "list";
  105.          typeNames[LispDataType.Angle] = "real";
  106.          typeNames[LispDataType.Int16] = "int";
  107.          typeNames[LispDataType.Int32] = "int";
  108.          typeNames[LispDataType.Text] = "string";
  109.          typeNames[LispDataType.T_atom] = "T/nil";
  110.          typeNames[LispDataType.DottedPair] = "alist";
  111.          typeNames[LispDataType.SelectionSet] = "Selection Set";
  112.          typeNames[LispDataType.Orientation] = "real";
  113.          typeNames[LispDataType.Double] = "real";
  114.       }
  115.    }
  116.  
  117.    public static class ExtensionMethods
  118.    {
  119.       public static TypedValue[] ValidateRequiredArguments( this ResultBuffer args, MethodBase method )
  120.       {
  121.          return RequiredArgumentsAttribute.Check( args, method );
  122.       }
  123.  
  124.       public static T GetAttribute<T>( this ICustomAttributeProvider provider ) where T : System.Attribute
  125.       {
  126.          T[] a = (T[]) provider.GetCustomAttributes( typeof( T ), false );
  127.          if( a != null && a.Length > 0 )
  128.             return (T) a[0];
  129.          else
  130.             return null;
  131.       }
  132.  
  133.       public static bool EqualsAny<T>( this T value, params T[] args )
  134.       {
  135.          return args.Contains( value );
  136.       }
  137.    }
  138.  
  139.    public static class RequiredArgumentsAttributeExample
  140.    {
  141.       /// Example: This function requires an int, a real, and a string,
  142.       /// in that order. e.g., (foobar 99 2.5 "Hello") Note that range
  143.       /// checking is currently not performed on integers and Int32
  144.       /// and Int16 are treated the same (TODO).
  145.  
  146.       [RequiredArguments( LispDataType.Int32, LispDataType.Double, LispDataType.Text )]
  147.       [LispFunction( "foobar" )]
  148.       public static object FooBar( ResultBuffer args )
  149.       {
  150.          // validate all arguments:
  151.          TypedValue[] arguments = args.ValidateRequiredArguments( MethodInfo.GetCurrentMethod() );
  152.  
  153.          Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage( "(foobar): Arguments are correct\n" );
  154.          return null;
  155.       }
  156.    }
  157. }
  158.  
  159.  

TheMaster

  • Guest
Re: Calling .net LispFunction while processing DBX
« Reply #20 on: October 30, 2012, 09:13:54 PM »
The ObjectId class has a Database property, that returns the containing database, and that's what you would use to access anything else in the same database. (vlax-vla-object->ename) along with the Database property of the ObjectId is also the key to solving the more general problem of passing a database that was opened via ObjectDBX in LISP or another ActiveX consumer, to managed code. You just pass the entity name of any object in that database, and the ObjectId that comes out on the managed side, gives you the containing Database.
There are times when even the solution is frustrating.  Fighting for hours, sometimes even days, while the simplest of properties and direct routes evade.

I think I love you man.   :kewl:

Seriously though; Knowing you can get to the object when in dbx opens the door for so many other things I wanted to do.

Thank you.

Code - C#: [Select]
  1. [LispFunction("cd:ename-xref-isresolved")]
  2. public bool Ename_xref_isResolved(ResultBuffer rb)
  3. {
  4.     if (rb == null)
  5.     { return false; }
  6.     TypedValue[] args = rb.AsArray();
  7.     if (args.Length > 1 || (LispDataType)args[0].TypeCode != LispDataType.ObjectId)
  8.     { return false; }
  9.     ObjectId ObjId = (ObjectId)args[0].Value;
  10.     using (Transaction tr = ObjId.Database.TransactionManager.StartTransaction())
  11.     {
  12.         try
  13.         {
  14.             BlockTableRecord btr = (BlockTableRecord)tr.GetObject(ObjId, OpenMode.ForRead, false);
  15.             if (btr.IsResolved)
  16.                 return true;
  17.         }
  18.         catch (System.Exception)
  19.         {
  20.             tr.Abort();
  21.             return false;
  22.         }
  23.     }
  24.     return false;
  25. }

You're welcome.

One minor issue with the code you're showing is that it should throw exceptions when the arguments are not what they should be, rather than just returning false. False means the xref is not resolved, but that doesn't tell the calling code there's a bug that must be fixed. Throwing an exception in a LispFunction triggers an error in the calling LISP code, which is what should happen if the arguments weren't correct.

You can use Gile's solution or something like the class I posted just now to check the arguments coming from LISP and raise an error if they're not what you expect.

CADDOG

  • Newt
  • Posts: 82
  • wishbonesr
Re: Calling .net LispFunction while processing DBX
« Reply #21 on: November 01, 2012, 02:04:09 PM »
I hear ya.
Until I was pointed to the above goodies, I wan't quite sure how to throw an error that lisp would interpret (hadn't experimented either).  Not to mention, I skipped Chapter 7 in my book ' Wrox - Beginning Visual C# 2010' for Error Handling.

I'm leaning toward how clean this keeps the actual functions...
MethodInfo is new to me as well.  Back to the books.  Thanks again.

TheMaster

  • Guest
Re: Calling .net LispFunction while processing DBX
« Reply #22 on: November 01, 2012, 07:46:57 PM »
After I posted that code, I came across a design prototype for a much more complete code contract API for LISP, that was never actually implemented:

Here's an excerpt from the file (which is not currently working)

Code - C#: [Select]
  1.  
  2. // Lisp Code Contract design prototype
  3.  
  4.    namespace Argument
  5.    {
  6.       /// Proposed attribute classes:
  7.  
  8.       public abstract class ArgumentAttribute : System.Attribute {}
  9.      
  10.       // VariantAttribute accepts multiple types:
  11.       public class VariantAttribute : ArgumentAttribute {}
  12.      
  13.       // Type-specific attributes:
  14.       public class IntegerAttribute : ArgumentAttribute {}
  15.      
  16.       public class StringAttribute : ArgumentAttribute {}
  17.      
  18.          // A string argument that must be the name of a layer, linetype, etc.
  19.          public class SymbolNameAttribute : StringAttribute {}
  20.          
  21.             // Example of further specialization:
  22.             public class LayerNameAttribute : SymbolNameAttribute {}
  23.            
  24.          // A string argument that must be the name of a file
  25.          public class FileNameAttribute : StringAttribute {}
  26.          
  27.       public class ObjectIdAttribute : ArgumentAttribute {}
  28.      
  29.       public class Point3dAttribute : ArgumentAttribute {}
  30.      
  31.       public class DoubleAttribute : ArgumentAttribute {}
  32.      
  33.       // A double argument representing an angle/orientation:
  34.       public class AnglularAttribute : DoubleAttribute {}
  35.  
  36.       // A list argument:
  37.       public class ListArgumentAttribute : ArgumentAttribute {}
  38.      
  39.       // The above design supports specialization of any
  40.       // type deriving from ArgumentAttribute, that can
  41.       // deal with more-specialized requirements, and be
  42.       // easily reused.
  43.    }
  44.  
  45. // Example
  46.  
  47.       /// (foobar <string> <ename> <point> [<int> [<real>]] ):
  48.       ///
  49.       /// In all ArgumentAttribute-based attributes, the first
  50.       /// integer argument is the 0-based index of the argument's
  51.       /// position, which must be distinct. This is required
  52.       /// because the .NET runtime does not preserve the order
  53.       /// in which attribues appear in source code.
  54.  
  55.       /// First argument must be a string that cannot be empty
  56.       /// or contain only white space:
  57.      
  58.       [Argument.String( 0, AcceptEmpty = false, AcceptOnlyWhiteSpace = false )]
  59.  
  60.       /// Second argument must be the entity name of a LINE, LWPOLYLINE,
  61.       /// or ARC entity. ExactMatch = true specifies that derived types
  62.       /// are rejected.
  63.      
  64.       /// Either of these can be used:
  65.      
  66.       /// Specifying the names of the runtime classes:
  67.       [Argument.ObjectId( 1, "AcDbPolyline", "AcDbLine", "AcDbArc", ExactMatch = true )]
  68.  
  69.       /// - or ----      
  70.      
  71.       /// Specifying the managed wrapper types:
  72.       [Argument.ObjectId( 1, typeof( Polyline ), typeof( Line ), typeof( Arc ), ExactMatch = true )]
  73.  
  74.       // Third argument must be a point list (x y z),
  75.       [Argument.Point3d( 2 )]
  76.  
  77.       // Fourth argument is optional. If supplied, it must be a positive integer:
  78.      
  79.       [Argument.Integer( 3, Optional = true, AcceptNegative = false, AcceptZero = false )]
  80.      
  81.       // Fifth argument is optional. If supplied, it must be a double
  82.       // in the range 0.0 to 100.0. If not supplied, a default value
  83.       // of 50.0 will be inserted into the resulting TypedValue[]
  84.       // array automatically:
  85.      
  86.       [Argument.Double( 4, Optional = true, Min = 0.0, Max = 100.0, Default = 50.0 )]
  87.  
  88.       [LispFunction( "foobar" )]
  89.       public static object FooBar( ResultBuffer args )
  90.       {
  91.          // validate all arguments:
  92.          TypedValue[] arguments = args.GetValidatedValues( MethodInfo.GetCurrentMethod() );
  93.  
  94.          Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage( "(foobar): Success\n" );
  95.          return null;
  96.       }
  97.  
  98. /// Sans comments:
  99.  
  100.       [LispFunction( "foobar" )]
  101.       [Argument.String( 0, AcceptEmpty = false, AcceptOnlyWhiteSpace = false )]
  102.       [Argument.ObjectId( 1, typeof( Polyline ), typeof( Line ), typeof( Arc ), ExactMatch = true )]
  103.       [Argument.Point3d( 2 )]
  104.       [Argument.Integer( 3, Optional = true, AcceptNegative = false, AcceptZero = false )]
  105.       [Argument.Double( 4, Optional = true, Min = 0.0, Max = 100.0, Default = 50.0 )]
  106.       public static object FooBar( ResultBuffer args )
  107.       {
  108.          TypedValue[] arguments = args.GetValidatedValues( MethodInfo.GetCurrentMethod() );
  109.          Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage( "(foobar): Success\n" );
  110.          return null;
  111.       }
  112.  
  113.  
« Last Edit: November 01, 2012, 08:11:10 PM by TT »

CADDOG

  • Newt
  • Posts: 82
  • wishbonesr
Re: Calling .net LispFunction while processing DBX
« Reply #23 on: November 06, 2012, 02:53:00 PM »
Much more precise.  Is that a teaser??!!  Do you want to collaborate? Is there a collaborative effort already in place?

TheMaster

  • Guest
Re: Calling .net LispFunction while processing DBX
« Reply #24 on: November 06, 2012, 06:06:28 PM »
Much more precise.  Is that a teaser??!!  Do you want to collaborate? Is there a collaborative effort already in place?

No, not a teaser, it's just my sketchy ideas for what I wanted to do, but never got around to it.

If someone wants to take a stab at it, I'll be happy to help.