.NET

Reply
Valued Contributor
kcimos
Posts: 91
Registered: ‎02-18-2010
Message 1 of 14 (1,778 Views)
Accepted Solution

synchronicity, friends, & acedcommand

1778 Views, 13 Replies
04-20-2012 08:21 AM

I had a VBA routine that worked using SENDCOMMAND.

my VB.NET version does not work using SENDCOMMAND (well, it does work, just not at the right time).

 

I've looking into the problem I have found that SENDCOMMAND is asynchronous in the document context but not in the application context. What does that mean? There is document.editor.SENDCOMMAND, but no application.SENDCOMMAND (or is there?).

 

So I tried using acedcommand (&/or acedcmd) but i'm getting an IDE error saying "AcEdCommand' is not accessible in this context because it is 'Friend' "

 

what is the proper syntax or methodology for using acedcommand?

I agree.
By my side, to get some beers at the store, I'd rather use AutoLISP with which you can easily script native commands.

Anyway, here's a little helper to use acedCmd with managed code (inspired by Tony Tanzillo's CommandLine (thanks again to him).
The Command method build the resultbuffer with the passed arguments.

EDIT: with A2013, replace "acad.exe" with "accore.dll" in the DllImport attribute arguments.

C#

 [System.Security.SuppressUnmanagedCodeSecurity]
[DllImport("acad.exe", EntryPoint = "acedCmd",
CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
extern static private int acedCmd(IntPtr resbuf);
/// <summary>
/// Call an AutoCAD command (runs synchronously).
/// </summary>
/// <param name="args">The command name followed by command inputs.</param>
public static void Command(params object[] args)
{
ResultBuffer resbuf = new ResultBuffer();
foreach (object obj in args)
{
switch (obj.GetType().Name)
{
case "String":
resbuf.Add(new TypedValue((int)LispDataType.Text, obj)); break;
case "Int16":
resbuf.Add(new TypedValue((int)LispDataType.Int16, obj)); break;
case "Int32":
resbuf.Add(new TypedValue((int)LispDataType.Int32, obj)); break;
case "Double":
resbuf.Add(new TypedValue((int)LispDataType.Double, obj)); break;
case "Point2d":
resbuf.Add(new TypedValue((int)LispDataType.Point2d, obj)); break;
case "Point3d":
resbuf.Add(new TypedValue((int)LispDataType.Point3d, obj)); break;
case "ObjectId":
resbuf.Add(new TypedValue((int)LispDataType.ObjectId, obj)); break;
case "ObjectId[]":
foreach (ObjectId id in (ObjectId[])obj)
resbuf.Add(new TypedValue((int)LispDataType.ObjectId, id));
break;
case "ObjectIdCollection":
foreach (ObjectId id in (ObjectIdCollection)obj)
resbuf.Add(new TypedValue((int)LispDataType.ObjectId, id));
break;
case "SelectionSetDelayMarshalled":
case "SelectionSetFullyMarshalled":
resbuf.Add(new TypedValue((int)LispDataType.SelectionSet, obj)); break;
default:
throw new InvalidOperationException("Unsupported type in Command() method");
}
}
acedCmd(resbuf.UnmanagedObject);
}

 VB

 <System.Security.SuppressUnmanagedCodeSecurity()> _
<DllImport("acad.exe", EntryPoint:="acedCmd", CharSet:=CharSet.Unicode, CallingConvention:=CallingConvention.Cdecl)> _
Private Shared Function acedCmd(resbuf As IntPtr) As Integer
End Function
'''<summary>
''' Call an AutoCAD command (runs synchronously).
''' </summary>
''' <param name="args">The command name followed by command inputs.</param>
Public Shared Sub Command(ParamArray args As Object())
Dim resbuf As New ResultBuffer()
For Each obj As Object In args
Select Case obj.GetType().Name
Case "String"
resbuf.Add(New TypedValue(CInt(LispDataType.Text), obj))
Exit Select
Case "Int16"
resbuf.Add(New TypedValue(CInt(LispDataType.Int16), obj))
Exit Select
Case "Int32"
resbuf.Add(New TypedValue(CInt(LispDataType.Int32), obj))
Exit Select
Case "Double"
resbuf.Add(New TypedValue(CInt(LispDataType.[Double]), obj))
Exit Select
Case "Point2d"
resbuf.Add(New TypedValue(CInt(LispDataType.Point2d), obj))
Exit Select
Case "Point3d"
resbuf.Add(New TypedValue(CInt(LispDataType.Point3d), obj))
Exit Select
Case "ObjectId"
resbuf.Add(New TypedValue(CInt(LispDataType.ObjectId), obj))
Exit Select
Case "ObjectId[]"
For Each id As ObjectId In DirectCast(obj, ObjectId())
resbuf.Add(New TypedValue(CInt(LispDataType.ObjectId), id))
Next
Exit Select
Case "ObjectIdCollection"
For Each id As ObjectId In DirectCast(obj, ObjectIdCollection)
resbuf.Add(New TypedValue(CInt(LispDataType.ObjectId), id))
Next
Exit Select
Case "SelectionSetDelayMarshalled", "SelectionSetFullyMarshalled"
resbuf.Add(New TypedValue(CInt(LispDataType.SelectionSet), obj))
Exit Select
Case Else
Throw New InvalidOperationException("Unsupported type in Command() method")
End Select
Next
acedCmd(resbuf.UnmanagedObject)
End Sub

 

Using examples (VB):

Draws a line.

Command("_.line", New Point3d(10.0, 20.0, 0.0), New Point3d(80.0, 50.0, 0.0), "")

 Freezes the "0" layer in the selected viewport.

Command("_.vplayer", "_freeze", "0", "_select", "\", "", "")

 Note:

- an empty string ("") means a validation (Enter)
- an anti-slash ("\" with VB or "\\" with C#) means a pause for user input.

Moderator
Alexander.Rivilis
Posts: 1,449
Registered: ‎04-09-2008
Message 2 of 14 (1,771 Views)

Re: synchronicity, friends, & acedcommand

04-20-2012 10:09 AM in reply to: kcimos

Synchronously Send (and wait for) commands in AutoCAD using C# .NET


Пожалуйста не забывайте про Утвердить в качестве решения! Утвердить в качестве решения и Give Kudos!Баллы
Please remember to Accept Solution! Accept as Solution and Give Kudos!Kudos

Valued Contributor
kcimos
Posts: 91
Registered: ‎02-18-2010
Message 3 of 14 (1,759 Views)

Re: synchronicity, friends, & acedcommand

04-20-2012 11:11 AM in reply to: kcimos

Thanks for the link, but my C knowledge is very weak, I'm using VB.

 

what does this do:

 

private static extern int acedCmd(System.IntPtrvlist);

 

is it declaring a integer variable named acedcmd? no, not with an arguement.

 

so is it prototyping a function named acedcmd that has an arguement in the form of a system.intptrvlist type and returns an integer? & the intptrvlist is essentially the list of strings that you want to send?

 

but isn't acedcmd aldready a function?





Valued Contributor
tonofsteel
Posts: 93
Registered: ‎12-04-2009
Message 4 of 14 (1,754 Views)

Re: synchronicity, friends, & acedcommand

04-20-2012 11:23 AM in reply to: kcimos
[DllImport("acad.exe", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl, EntryPoint = "acedCmd")]
private static extern int acedCmd(System.IntPtr vlist);

 The whole of the above is the code needed for PInvoke of acedCmd.  The attribute (stuff between []) is the import of the command and it decorates the private static extern int acedCmd(System.IntPtr vlist) so the acedCmd is available in your code.

 

The System.IntPtr vlist is actually a result buffer that is created a few lines below:

 

ResultBuffer rb = new ResultBuffer();

  // RTSTR = 5005
 
  rb.Add(new TypedValue(5005, "_.INSERT"));
 
  // start the insert command
 
  acedCmd(rb.UnmanagedObject);

After you have the PInvoke and extern declared you can use the acedCmd as above, the result buffer holds a string (code 5005) and in this case the string is the insert command.  This is how the acedCmd is used, you cant just send it a string directly, you need to use result buffers. 

 

If you look at the documentation for result buffer you will see the different codes and how they work.  If you google PInvoke you can get more details on how it works and what exactly is going on.

 

From the documentation the returned int:

 

The return value of acedCmd() does not indicate the success or failure of a command. The function usually returns RTNORM. It returns RTCAN if the user cancels the command by pressing [Ctrl]+[C] or [Ctrl]+[Break]. It returns RTERROR or RTREJ to indicate a failure of undefined origin, not a command failure.

 

http://docs.autodesk.com/ACDMAC/2012/ENU/ObjectARX%20Reference/index.html?frmname=topic&frmfile=aced...

 

RTNORM and RTCAN etc are integers defined somewhere else as constants in the ObjectARX files.  Not sure off the top of my head where though.

 

 

 One more edit, the private static extern int acedCmd(System.IntPtr vlist)

You see the System.IntPtr vlist in there, and from the documentation above you read:

 

int acedCmd(const struct resbuf * rbp);

 

Pointer to a result-buffer list, in which each buffer specifies a data item whose value is passed to AutoCAD as if it were entered at the Command prompt
 
This is why the System.IntPtr vlist is in the command declaration, the underlying command you are Pinvoke'ing defines it takes a pointer to a result buffer, so this needs to be in the command declaration as it is.
*Expert Elite*
_gile
Posts: 2,114
Registered: ‎04-29-2006
Message 5 of 14 (1,747 Views)

Re: synchronicity, friends, & acedcommand

04-20-2012 11:53 AM in reply to: tonofsteel

Hi,

 

IMO, you'd rather try to hard code what the native command do.

Most of the time it's more easy than understanding how to P/invoke unmanaged methods and it's a good way to learn the API.

Gilles Chanteau
Valued Contributor
kcimos
Posts: 91
Registered: ‎02-18-2010
Message 6 of 14 (1,731 Views)

Re: synchronicity, friends, & acedcommand

04-20-2012 03:18 PM in reply to: kcimos

yeah, looks like I might have to. The thing is that I already have a fully functional VBA routine that has a lot going on. To match the funtionality of VPLAYER, I'm going to have to do a great deal of additional work involving searching layertables & retrieving IDs. Just a lot of extra work to replicate something that already works in VBA. + the viewport object's methods don't cover all of the functinality of VPLAYER, therefore I have to write additional alogorithms to replicate the functionality that my VBA routine provided via VPLAYER. 

 

It is rather irritating.

 

VB.NET, for me, is like using a jet fighter to go to the store to get some beer instead of using a car (VBA), when you have a car that runs & you know how to drive very well, but someone (microsoft) won't let you drive it anymore.

 

 

*Expert Elite*
_gile
Posts: 2,114
Registered: ‎04-29-2006
Message 7 of 14 (1,709 Views)

Re: synchronicity, friends, & acedcommand

04-21-2012 01:08 AM in reply to: kcimos

I agree.
By my side, to get some beers at the store, I'd rather use AutoLISP with which you can easily script native commands.

Anyway, here's a little helper to use acedCmd with managed code (inspired by Tony Tanzillo's CommandLine (thanks again to him).
The Command method build the resultbuffer with the passed arguments.

EDIT: with A2013, replace "acad.exe" with "accore.dll" in the DllImport attribute arguments.

C#

        [System.Security.SuppressUnmanagedCodeSecurity]
        [DllImport("acad.exe", EntryPoint = "acedCmd",
            CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
        extern static private int acedCmd(IntPtr resbuf);
 
        /// <summary>
        /// Call an AutoCAD command (runs synchronously).
        /// </summary>
        /// <param name="args">The command name followed by command inputs.</param>
        public static void Command(params object[] args)
        {
            ResultBuffer resbuf = new ResultBuffer();
            foreach (object obj in args)
            {
                switch (obj.GetType().Name)
                {
                    case "String":
                        resbuf.Add(new TypedValue((int)LispDataType.Text, obj)); break;
                    case "Int16":
                        resbuf.Add(new TypedValue((int)LispDataType.Int16, obj)); break;
                    case "Int32":
                        resbuf.Add(new TypedValue((int)LispDataType.Int32, obj)); break;
                    case "Double":
                        resbuf.Add(new TypedValue((int)LispDataType.Double, obj)); break;
                    case "Point2d":
                        resbuf.Add(new TypedValue((int)LispDataType.Point2d, obj)); break;
                    case "Point3d":
                        resbuf.Add(new TypedValue((int)LispDataType.Point3d, obj)); break;
                    case "ObjectId":
                        resbuf.Add(new TypedValue((int)LispDataType.ObjectId, obj)); break;
                    case "ObjectId[]":
                        foreach (ObjectId id in (ObjectId[])obj)
                            resbuf.Add(new TypedValue((int)LispDataType.ObjectId, id)); 
                        break;
                    case "ObjectIdCollection":
                        foreach (ObjectId id in (ObjectIdCollection)obj)
                            resbuf.Add(new TypedValue((int)LispDataType.ObjectId, id));
                        break;
                    case "SelectionSetDelayMarshalled":
                    case "SelectionSetFullyMarshalled":
                        resbuf.Add(new TypedValue((int)LispDataType.SelectionSet, obj)); break;
                    default:
                        throw new InvalidOperationException("Unsupported type in Command() method");
                }
            }
            acedCmd(resbuf.UnmanagedObject);
        }

 VB

        <System.Security.SuppressUnmanagedCodeSecurity()> _
        <DllImport("acad.exe", EntryPoint:="acedCmd", CharSet:=CharSet.Unicode, CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Function acedCmd(resbuf As IntPtr) As Integer
        End Function
 
        '''<summary>
        ''' Call an AutoCAD command (runs synchronously).
        ''' </summary>
        ''' <param name="args">The command name followed by command inputs.</param>
        Public Shared Sub Command(ParamArray args As Object())
            Dim resbuf As New ResultBuffer()
            For Each obj As Object In args
                Select Case obj.GetType().Name
                    Case "String"
                        resbuf.Add(New TypedValue(CInt(LispDataType.Text), obj))
                        Exit Select
                    Case "Int16"
                        resbuf.Add(New TypedValue(CInt(LispDataType.Int16), obj))
                        Exit Select
                    Case "Int32"
                        resbuf.Add(New TypedValue(CInt(LispDataType.Int32), obj))
                        Exit Select
                    Case "Double"
                        resbuf.Add(New TypedValue(CInt(LispDataType.[Double]), obj))
                        Exit Select
                    Case "Point2d"
                        resbuf.Add(New TypedValue(CInt(LispDataType.Point2d), obj))
                        Exit Select
                    Case "Point3d"
                        resbuf.Add(New TypedValue(CInt(LispDataType.Point3d), obj))
                        Exit Select
                    Case "ObjectId"
                        resbuf.Add(New TypedValue(CInt(LispDataType.ObjectId), obj))
                        Exit Select
                    Case "ObjectId[]"
                        For Each id As ObjectId In DirectCast(obj, ObjectId())
                            resbuf.Add(New TypedValue(CInt(LispDataType.ObjectId), id))
                        Next
                        Exit Select
                    Case "ObjectIdCollection"
                        For Each id As ObjectId In DirectCast(obj, ObjectIdCollection)
                            resbuf.Add(New TypedValue(CInt(LispDataType.ObjectId), id))
                        Next
                        Exit Select
                    Case "SelectionSetDelayMarshalled", "SelectionSetFullyMarshalled"
                        resbuf.Add(New TypedValue(CInt(LispDataType.SelectionSet), obj))
                        Exit Select
                    Case Else
                        Throw New InvalidOperationException("Unsupported type in Command() method")
                End Select
            Next
            acedCmd(resbuf.UnmanagedObject)
        End Sub

 

Using examples (VB):

Draws a line.

Command("_.line", New Point3d(10.0, 20.0, 0.0), New Point3d(80.0, 50.0, 0.0), "")

 Freezes the "0" layer in the selected viewport.

Command("_.vplayer", "_freeze", "0", "_select", "\", "", "")

 Note:

- an empty string ("") means a validation (Enter)
- an anti-slash ("\" with VB or "\\" with C#) means a pause for user input.

Gilles Chanteau
Valued Contributor
kcimos
Posts: 91
Registered: ‎02-18-2010
Message 8 of 14 (1,666 Views)

Re: synchronicity, friends, & acedcommand

04-25-2012 10:30 AM in reply to: kcimos

thank you so much, that works in a very awesome manner.

Mentor
matus.brlit
Posts: 246
Registered: ‎03-11-2008
Message 9 of 14 (1,571 Views)

Re: synchronicity, friends, & acedcommand

06-01-2012 06:14 AM in reply to: kcimos

how can I use LISP functions or custom commands with that Tony's method?

I can't figure that out

*Expert Elite*
_gile
Posts: 2,114
Registered: ‎04-29-2006
Message 10 of 14 (1,548 Views)

Re: synchronicity, friends, & acedcommand

06-03-2012 03:22 AM in reply to: matus.brlit

If you want to run LISP functions, you'd rather use the Application.Invoke() method (A2011 or later) or P/Invoke the unmanaged acedInvoke() method.

Gilles Chanteau
Post to the Community

Have questions about Autodesk products? Ask the community.

New Post
Announcements
Are You Going To Be @ AU 2014? Feel free to drop by our AU topic post and share your plans, plug a class that you're teaching, or simply check out who else from the community might be in attendance. Ohh and don't forgot to stop by the Autodesk Help | Learn | Collaborate booths in the Exhibit Hall and meet our community team if you get a chance!