.NET
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

How to call Lisp functions from .Net

20 REPLIES 20
Reply
Message 1 of 21
Anonymous
5567 Views, 20 Replies

How to call Lisp functions from .Net

I have a Lisp routine which gets the field code from attribute. I need to run this function when changing attribute field codes as this is not exposed in .Net API !. I can not use SendStringToExecute as it waits until the .Net program is finished. How do I go about that.
I tried acedCmd with DllImport but no success as it doesn't run AutoLisp routines.
Thanks,
20 REPLIES 20
Message 2 of 21
Anonymous
in reply to: Anonymous

If yours lisp-function was registring by (vl-acad-defun 'my_lisp_function) it can be called from ObjectARX with help of acedInvoke(...) function:
resbuf *rb_in = acutBuildList(RTSTR,"my_lisp_function",...,0);
resbuf *rb_out = NULL;
acedInvoke(rb_in,&rb_out);
IMHO you can try to do that from .NET application. Message was edited by: Alexander Rivilis
Message 3 of 21
Anonymous
in reply to: Anonymous

I am not guru in .NET 😞
It is interesting how you can dllimport this function? Message was edited by: Alexander Rivilis
Message 4 of 21
Anonymous
in reply to: Anonymous

//
// acedInvoke.cs Copyright 2005 www.caddzone.com
//

using System;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.EditorInput;
using System.Runtime.InteropServices;
using AcadApp = Autodesk.AutoCAD.ApplicationServices.Application;

// Sample: calling acedInvoke from managed code.
//
// To call LISP functions via acedInvoke, they must be
// defined with a C: prefix, or they must be registered
// using (vl-acad-defun)
//
// The test code below below requires the following LISP:
//
// (defun testfunc ( a b )
// (princ "\nTESTFUNC called: )
// (prin1 a)
// (prin1 b)
// ;; return a list of results:
// (list 99 "Just ditch the LISP and forget this nonsense" pi)
// )
//
// ;; register the function to be externally callable:
//
// (vl-acad-defun 'testfunc)
//
//

namespace CADDZone.AutoCAD.Samples
{
public class AcedInvokeSample
{
public const int RTLONG = 5010; // adscodes.h
public const int RTSTR = 5005;

public const int RTNORM = 5100;

public AcedInvokeSample()
{
}

[System.Security.SuppressUnmanagedCodeSecurity]
[DllImport("acad.exe", CallingConvention = CallingConvention.Cdecl)]
extern static private int acedInvoke(IntPtr args, out IntPtr result);

// Helper for acedInvoke()

public static ResultBuffer InvokeLisp(ResultBuffer args, ref int stat)
{
IntPtr rb = IntPtr.Zero;
stat = acedInvoke(args.UnmanagedObject, out rb);
if( stat == (int) PromptStatus.OK && rb != IntPtr.Zero )
return (ResultBuffer)
DisposableWrapper.Create(typeof(ResultBuffer), rb, true);
return null;
}

static void PrintResbuf(ResultBuffer rb)
{
string s = "\n-----------------------------";
foreach( TypedValue val in rb )
s += string.Format("\n{0} -> {1}", val.TypeCode,
val.Value.ToString());
s += "\n-----------------------------";
AcadApp.DocumentManager.MdiActiveDocument.
Editor.WriteMessage(s);
}

public static void TestInvokeLisp()
{
ResultBuffer args = new ResultBuffer();
int stat = 0;

args.Add(new TypedValue(RTSTR, "testfunc"));
args.Add(new TypedValue(RTLONG, 100));
args.Add(new TypedValue(RTLONG, 200));

ResultBuffer res = InvokeLisp(args, ref stat);
if( stat == RTNORM && res != null )
{
PrintResbuf(res);
res.Dispose();
}
}
}
}

--
http://www.caddzone.com

AcadXTabs: MDI Document Tabs for AutoCAD 2004/2005/2006
http://www.acadxtabs.com

wrote in message news:5094659@discussion.autodesk.com...
I am not guru in .NET 😞
It is interesting how you can dllimport this function?

Message was edited by: Alexander Rivilis
Message 5 of 21
Anonymous
in reply to: Anonymous

Thank you, Tony!!!
Message 6 of 21
Anonymous
in reply to: Anonymous

Tony! I've tryed to compile and test your's sample. It complie with VS 2005, but report internal error while compiling VS 2002 (in function PrintResbuf). I try to replace foreach statement with:
[code]
ResultBufferEnumerator en = rb.GetEnumerator();
while (!en.MoveNext()) {
s += string.Format("\n{0} -> {1}", en.Current.TypeCode,
en.Current.Value.ToString());
s += "\n-----------------------------";
AcadApp.DocumentManager.MdiActiveDocument.
Editor.WriteMessage(s);
}
[/code]
But this code allso generete internal _compile_ error. 😞

Message was edited by: Alexander Rivilis:

O! I've find solution for VS 2002:
[code]
public static void PrintResbuf(ResultBuffer rb) {
string s = "\n-----------------------------";
IEnumerator en = rb.GetEnumerator();
while( en.MoveNext() ) {
TypedValue val = (TypedValue)en.Current;
s += string.Format("\n{0} -> {1}", val.TypeCode,
val.Value.ToString());
s += "\n-----------------------------";
}
AcadApp.DocumentManager.MdiActiveDocument.
Editor.WriteMessage(s);
}
[/code]

Message was edited by: Alexander Rivilis
Message 7 of 21
Anonymous
in reply to: Anonymous

Thanks a lot guys.
My problem with acedInvoke was actually (vl-acad-defun 'testfunc)
part. I tried it before with even defun c:functions but somehow it didn't work in mine but with (vl-acad-defun ...) it does now.
Thanks again. Message was edited by: h_najmi
Message 8 of 21
Anonymous
in reply to: Anonymous

This is a problem in VS2002.

You can use this:

Instead of

ResultBuffer rb = ///
foreach( TypedValue val in rb )
{
}

You can use

foreach( TypedValue val in rb.GetEnumerator() )
{
}

--
http://www.caddzone.com

AcadXTabs: MDI Document Tabs for AutoCAD 2004/2005/2006
http://www.acadxtabs.com

wrote in message news:5094997@discussion.autodesk.com...
Tony! I've tryed to compile and test your's sample. It complie with VS 2005, but report internal error while compiling VS 2002 (in function PrintResbuf). I try to replace foreach statement with:
[code]
ResultBufferEnumerator en = rb.GetEnumerator();
while (!en.MoveNext()) {
s += string.Format("\n{0} -> {1}", en.Current.TypeCode,
en.Current.Value.ToString());
s += "\n-----------------------------";
AcadApp.DocumentManager.MdiActiveDocument.
Editor.WriteMessage(s);
}
[/code]
But this code allso generete internal _compile_ error. 😞

Message was edited by: Alexander Rivilis:

O! I've find solution for VS 2002:
[code]
public static void PrintResbuf(ResultBuffer rb) {
string s = "\n-----------------------------";
IEnumerator en = rb.GetEnumerator();
while( en.MoveNext() ) {
TypedValue val = (TypedValue)en.Current;
s += string.Format("\n{0} -> {1}", val.TypeCode,
val.Value.ToString());
s += "\n-----------------------------";
}
AcadApp.DocumentManager.MdiActiveDocument.
Editor.WriteMessage(s);
}
[/code]

Message was edited by: Alexander Rivilis
Message 9 of 21
Anonymous
in reply to: Anonymous

Sorry, Tony, but VS 2002 compiler find error in this code:

foreach statement cannot operate on variables of type 'Autodesk.AutoCAD.DatabaseServices.ResultBufferEnumerator' because 'Autodesk.AutoCAD.DatabaseServices.ResultBufferEnumerator' does not contain a definition for 'GetEnumerator', or it is inaccessible
Message 10 of 21
Anonymous
in reply to: Anonymous

Try this:

ResultBuffer resbuf = ///

IEnumerable enum = (IEnumerable) resbuf;

foreach( TypedValue val in enum )
{
///
}

--
http://www.caddzone.com

AcadXTabs: MDI Document Tabs for AutoCAD 2004/2005/2006
http://www.acadxtabs.com

wrote in message news:5095820@discussion.autodesk.com...
Sorry, Tony, but VS 2002 compiler find error in this code:

foreach statement cannot operate on variables of type 'Autodesk.AutoCAD.DatabaseServices.ResultBufferEnumerator' because 'Autodesk.AutoCAD.DatabaseServices.ResultBufferEnumerator' does not contain a definition for 'GetEnumerator', or it is inaccessible
Message 11 of 21
Anonymous
in reply to: Anonymous

Hi, Tony Tanzillo!

TT> Try this:
TT>
TT> ResultBuffer resbuf = ///
TT>
TT> IEnumerable enum = (IEnumerable) resbuf;
TT>
TT> foreach( TypedValue val in enum )
TT> {
TT> ///
TT> }

Ok! 🙂 Two variants which is compiled with VS2002 .NET compliler without errors:
=========Beginning of code==============
public static void PrintResbuf(ResultBuffer rb)
{
string s = "\n-----------------------------";
IEnumerable en = (IEnumerable) rb;
foreach (TypedValue val in en) {
s += string.Format("\n{0} -> {1}", val.TypeCode,
val.Value.ToString());
s += "\n-----------------------------";
}
AcadApp.DocumentManager.MdiActiveDocument.
Editor.WriteMessage(s);
}
=========The end of the code================

=========Beginning of the code==============
public static void PrintResbuf(ResultBuffer rb)
{
string s = "\n-----------------------------";
IEnumerator en = rb.GetEnumerator();
while( en.MoveNext() ) {
TypedValue val = (TypedValue)en.Current;
s += string.Format("\n{0} -> {1}", val.TypeCode,
val.Value.ToString());
s += "\n-----------------------------";
}

AcadApp.DocumentManager.MdiActiveDocument.
Editor.WriteMessage(s);
}
=========The end of the code================

Best Regards,
Alexander Rivilis.
Message 12 of 21
alexmore
in reply to: Anonymous

Hi Tony!

I try to use  this code in AutoCAD 2009, but i have no result.

My LISP code

(defun testfunc (/ a b)
(princ "\nTESTFUNC called: ")
(prin1 a)
(prin1 b)
;; return a list of results:
(list 99 "Just ditch the LISP and forget this nonsense" pi)
)
(vl-acad-defun 'testfunc)

I loaded dll, run LISP function in command line (testfunc) and have @"TESTFUNC called: nilnil(99 "Just ditch the LISP and forget this nonsense"
3.14159)". But when i run command in dll ("testinvoke" in my code), I have no messages. And in debugger i saw  "set" return "-5001". What I am doing in wrong way?

Message 13 of 21
_gile
in reply to: alexmore

Try this:

 

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
using System;
using System.Runtime.InteropServices;

namespace PInvokeAcadinvokeSample
{
    public class Commands
    {
        [System.Security.SuppressUnmanagedCodeSecurity]
        [DllImport("acad.exe", CallingConvention = CallingConvention.Cdecl)]
        extern static private int acedInvoke(IntPtr rbIn, out IntPtr rbOut);

        public static ResultBuffer InvokeLisp(ResultBuffer resbuf)
        {
            IntPtr rb = IntPtr.Zero;
            acedInvoke(resbuf.UnmanagedObject, out rb);
            return (ResultBuffer)DisposableWrapper.Create(typeof(ResultBuffer), rb, true);
        }

        [CommandMethod("TEST")]
        public void Test()
        {
            ResultBuffer args = new ResultBuffer(new TypedValue((int)LispDataType.Text, "testfunc"));
            ResultBuffer returnValue = InvokeLisp(args);
            if (returnValue != null)
            {
                var ed = Application.DocumentManager.MdiActiveDocument.Editor;
                foreach (TypedValue value in returnValue.AsArray())
                {
                    ed.WriteMessage("\n{0}", value.Value);
                }
            }
        }
    }
}


Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 14 of 21
alexmore
in reply to: _gile

Thank you for the help, but AutoCAD show an error like this:

"System.Reflection.TargetInvocationException: The destination of the call created an exception. ---> System.InvalidOperationException: The operation is invalid due to the current state of the object."

Message 15 of 21
_gile
in reply to: alexmore

Can you give more details about the context in which you call the TEST command.



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 16 of 21
ActivistInvestor
in reply to: alexmore

The code @_gile posted requires that there be a LISP function named "testfunc" defined and registered using vl-acad-defun.

 

 

(vl-acad-defun
    (defun testfunc ()
        (princ "\n(testfunc) called...")
        "Here is the result of testfunc"
    )
)

 


@alexmorewrote:

Thank you for the help, but AutoCAD show an error like this:

"System.Reflection.TargetInvocationException: The destination of the call created an exception. ---> System.InvalidOperationException: The operation is invalid due to the current state of the object."



 

Message 17 of 21
JamesMaeding
in reply to: Anonymous

I recall there being a problem with calling acedInvoke and modeless dialogs.

These days, I have only found one approach that is rock solid, though a bit more involved.

In a nutshell:

1) make a string which is some serialized list of input data, in my case I use xml.

That way I can send in a lot of params without wrapping in dxf codes which acad messes up in some cases.

2) call a lisp from .net that sets the string to a global var in my lisp program using AcAp.Invoke(pargs)

the pargs is a result buffer with function name and serialized string

3) call my c: lisp function that actually does something, example: CommandLine.PostCommand("(C:INS-SEGMENT-NET) ").

It gets its input params from the global var I set, deserializing the string to a list.

4) it sets another global var which I retrieve with a lisp I call from .net with AcAp.Invoke

 

Sounds crazy, but it is critical to use CommandLine.PostCommand when synchronous behavior is desired from modeless palette.

Of course, my most powerful progs use those, so I have a few critical lisp functions I have not rewritten in .net yet.

 

the actual code in use looks like this, complete with conditionals for bricscad:

public static bool InsertSegsGlist(int index, CE.Point stPt, CE.Glist inGlist, int BrgStyle, out CE.Point newStPt, out CE.Glist newGlist, out List<int> insIndicies) {
            bool res = false;
            newGlist = new Glist();
            newStPt = null;
            insIndicies = new List<int>();
            using (DocumentLock doclock = AcAp.DocumentManager.MdiActiveDocument.LockDocument()) {
                try {
                    List<object> stPtLst = new List<object>() { stPt.X, stPt.Y };
                    List<object> gsegs = new List<object>() { null };
                    if (inGlist.Segments.Count > 0)
                        gsegs = inGlist.ToListObject();
                    List<object> infoLst = new List<object>() { index, stPtLst, gsegs, BrgStyle };
                    List<object> pLst = new List<object>() { "INS-SEGMENT-NET-GVAR", infoLst };
                    string xmlstr = HANestedAList.ObjPropListToListReadString(pLst);

                    //set lisp global var with string
                    ResultBuffer pargs = new ResultBuffer();
                    pargs.Add(new TypedValue(5005, "C:SET-CTGLOBALVAR"));
                    pargs.Add(new TypedValue(5005, xmlstr));
#if ACAD
                    AcAp.Invoke(pargs); //send data
                    XAppAcad.FocusToDwgView();
                    //must tell acad to call command from .net to run the lisp, this is better than PostCommand - not!
                    //AcAp.DocumentManager.MdiActiveDocument.SendStringToExecute("CmdInsertSegsGlist ", false, false, true);
                    CommandLine.PostCommand("(C:INS-SEGMENT-NET) "); //use because lisp command fails with invoke method
#endif
#if BCAD
                    Bricscad.Global.Editor.Invoke(pargs);
                    XAppAcad.FocusToDwgView();
                    pargs = new ResultBuffer();
                    pargs.Add(new TypedValue(5005, "C:INS-SEGMENT-NET"));
                    Bricscad.Global.Editor.Invoke(pargs);
#endif
                    //now get global var back
                    //RETURNS LAYER NAME, LIST OF GEOMETRIC DATA AND ENTITY NAMES FOR HIGHLIGHTING
                    List<object> retInfo = HALispVars.GetCTGVar("INS-SEGMENT-NET-GVAR", XAppAcad.ListDelim, XAppAcad.DPair);
                    if (retInfo != null) {
                        List<object> pt = (List<object>)retInfo[0];
                        newStPt = new CivilEngine.Point((double)pt[0], (double)pt[1]);
                        newGlist.FillFromObjList((List<object>)retInfo[1]);
                        List<object> oints = (List<object>)retInfo[2];
                        insIndicies = oints.Cast<int>().ToList();
                        return true;
                    }
                    else
                        return false;
                }
                catch { }
                return res;
            }
        }

internal protected virtual unsafe Human() : mostlyHarmless
I'm just here for the Shelties

Message 18 of 21
alexmore
in reply to: _gile

Hello!

What exactly do you mean - environment, software, full code?

The error may be due to my local settings etc.

 

Message 19 of 21
alexmore
in reply to: ActivistInvestor

Hello!

I try to use your code.

Application is loaded, i saw ther result:

"Command: (testfunc)
(testfunc) called..."Here is the result of testfunc""

I loaded dll by _netload, set command "_test" in command line and i have an error like this:

""System.Reflection.TargetInvocationException: The destination of the call created an exception. ---> System.InvalidOperationException: The operation is invalid due to the current state of the object.""

I will  to use  acedEvaluateLisp, it's ok, but in this way i must to print only one line of the code but not  LSP file fully. 

Anyway, Thank you for the help. 

Message 20 of 21
ActivistInvestor
in reply to: alexmore

I can't tell you why that happens (sorry, no time to test it), but it could be because the code you're using doesn't check the result of acedInvoke(), or if there was a value assigned to the ResultBuffer out parameter.

 

Here's @_gile code with those checks:

 

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
using System;
using System.Runtime.InteropServices;

namespace PInvokeAcadinvokeSample
{
    public class Commands
    {
        [System.Security.SuppressUnmanagedCodeSecurity]
        [DllImport("acad.exe", CallingConvention = CallingConvention.Cdecl)]
        extern static private int acedInvoke(IntPtr rbIn, out IntPtr rbOut);

        public static ResultBuffer InvokeLisp(ResultBuffer resbuf)
        {
            IntPtr rb = IntPtr.Zero;
            Interop.CheckAds(acedInvoke(resbuf.UnmanagedObject, out rb));
            if(rb != IntPtr.Zero)
               return (ResultBuffer)DisposableWrapper.Create(typeof(ResultBuffer), rb, true);
            else
               return null;
        }

        [CommandMethod("TEST")]
        public void Test()
        {
            ResultBuffer args = new ResultBuffer(new TypedValue((int)LispDataType.Text, "testfunc"));
            ResultBuffer returnValue = InvokeLisp(args);
            if (returnValue != null)
            {
                var ed = Application.DocumentManager.MdiActiveDocument.Editor;
                foreach (TypedValue value in returnValue.AsArray())
                {
                    ed.WriteMessage("\n{0}", value.Value);
                }
            }
        }
    }
}

 

 

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

Autodesk DevCon in Munich May 28-29th


Autodesk Design & Make Report

”Boost