It's quite common to want to call commands from one or other of AutoCAD's programming environments. While it's cleanest (from a purist's perspective) to use an API to perform the task you want, the quickest way - and the one which will appeal to the pragmatists (and the lazy) among us is often to call a sequence of commands. And there are, of course, a few places in AutoCAD where APIs have not been exposed, so scripting commands is the only way to achieve what you want.
Let's take the simple example of adding a line. Here's what you'd do from the different environments from a low-level API perspective:
- LISP - create an association list representing the entity and then (entmake) it
- VBA (or COM) - get the ModelSpace object from the active drawing and call AddLine (the COM API is probably the simplest in that respect)
- .NET and ObjectARX - open the block table and then the model-space block table record, create a new line object and append it to the model-space (and to the transaction, if you're using them), closing the various objects along the way
Having first started coding for AutoCAD with LISP (for R10), I know that the simplest way to do what you want from that environment is to call the LINE command, passing in the start and end points:
(command "_LINE" "0,0,0" "100,100,0" "")
LISP is great in that respect: as you're not able to define native commands (only LISP) functions, it's perfectly acceptable to use it to script commands to do what you want, rather than rely on low-level APIs.
ObjectARX in particular has potential issues with respect to defining native commands calling commands, as AutoCAD is only "re-entrant" up to 4 levels. Without going into specifics, it's basically best to avoid calling commands using acedCommand() from ObjectARX, unless the command is registered as a LISP function using acedDefun().
While you do have to be careful when calling commands from VBA or ObjectARX, there are a few options available to you.
ObjectARX
- ads_queueexpr()
- This old favourite is intended to be used from acrxEntryPoint() to execute a sequence of commands after (s::startup) has been called from LISP (as you are not able to use acedCommand() from this context)
- You need to declare it yourself (extern "C" void ads_queueexpr( ACHAR *);) before use
- It has been unsupported as long as I can remember, but is widely-used and mentioned in the Tips & Techniques section of the ObjectARX Developer's Guide
- AcApDocManager::sendStringToExecute()
- This function has the advantage of a few more options being available as arguments, mainly around where to execute the string (which document, and whether it be activated), and whether to echo the string to the command-line
- ::SendMessage()
- This is a standard Win32 platform API and so can, in effect, be used to drive AutoCAD from an external client. It uses a structure to pass the string that is often a bit tricky to set up (it became a migration issue when we switched to Unicode, for example)
- IAcadDocument::SendCommand()
- This COM method is the only way (other than acedCommand() or acedCmd()) to execute a command synchronously from AutoCAD (and even then it may not be completely synchronous if requiring user input)
- acedCommand()
- This is the ObjectARX equivalent to (command), and is genuinely synchronous. Unfortunately (as mentioned earlier) there are issues with using it directly from a natively-registered command, so I'd recommend only using it from acedDefun()-registered commands (see the ObjectARX documentation and the below sample for more details)
VBA (some of which also applies to VB)
- ThisDrawing.SendCommand
- This is the same as IAcadDocument::SendCommand() from C++
- SendKeys
- This is just a simple technique to send key-strokes to the command-line
- SendMessage
- This is just the Win32 API mentioned above, but declared and called from VB(A)
So, now for some sample code...
ObjectARX sample code
The first can be dropped into an ObjectARX Wizard-defined project (I used Visual Studio 2005 and ObjectARX 2007). You'll need to make sure "COM-client" is selected and you name your project "SendingCommands" (or you search and replace to change the name in the below code to something you prefer).
The code creates points along a line (from 0,0 to 5,5), using different techniques to send the command to AutoCAD. I would, of course, use the proper ObjectARX APIs to do this (creating an AcDbPoint etc.) - I just used this as an example of a command that could be sent.
It creates the first point on load, using ads_queueexpr(), and defines commands (TEST1, TEST2, TEST3 and TEST4) for the subsequent tests (the last being an acedDefun()-registered command).
#include "StdAfx.h"
#include "resource.h"
#define szRDS _RXST("Adsk")
extern "C" void ads_queueexpr( ACHAR *);
//----- ObjectARX EntryPoint
class CSendingCommandsApp : public AcRxArxApp {
public:
CSendingCommandsApp () : AcRxArxApp () {}
virtual AcRx::AppRetCode On_kInitAppMsg (void *pkt) {
// You *must* call On_kInitAppMsg here
AcRx::AppRetCode retCode =AcRxArxApp::On_kInitAppMsg (pkt) ;
return (retCode) ;
}
virtual AcRx::AppRetCode On_kUnloadAppMsg (void *pkt) {
// You *must* call On_kUnloadAppMsg here
AcRx::AppRetCode retCode =AcRxArxApp::On_kUnloadAppMsg (pkt) ;
return (retCode) ;
}
virtual AcRx::AppRetCode On_kLoadDwgMsg(void * pkt) {
AcRx::AppRetCode retCode =AcRxArxApp::On_kLoadDwgMsg (pkt) ;
ads_queueexpr( _T("(command\"_POINT\" \"1,1,0\")") );
return (retCode) ;
}
virtual void RegisterServerComponents () {
}
public:
// - AdskSendingCommands._SendStringToExecTest command (do not rename)
static void AdskSendingCommands_SendStringToExecTest(void)
{
acDocManager->sendStringToExecute(curDoc(), _T("_POINT 2,2,0 "));
}
static void SendCmdToAcad(ACHAR *cmd)
{
COPYDATASTRUCT cmdMsg;
cmdMsg.dwData = (DWORD)1;
cmdMsg.cbData = (DWORD)(_tcslen(cmd) + 1) * sizeof(ACHAR);
cmdMsg.lpData = cmd;
SendMessage(adsw_acadMainWnd(), WM_COPYDATA, NULL, (LPARAM)&cmdMsg);
}
// - AdskSendingCommands._SendMessageTest command (do not rename)
static void AdskSendingCommands_SendMessageTest(void)
{
SendCmdToAcad(_T("_POINT 3,3,0 "));
}
// - AdskSendingCommands._SendCommandTest command (do not rename)
static void AdskSendingCommands_SendCommandTest(void)
{
try {
IAcadApplicationPtr pApp = acedGetIDispatch(TRUE);
IAcadDocumentPtr pDoc;
pApp->get_ActiveDocument(&pDoc);
pDoc->SendCommand( _T("_POINT 4,4,0 ") );
}
catch(_com_error& e) {
acutPrintf(_T("\nCOM error: %s"), (ACHAR*)e.Description());
}
}
// ----- ads_test4 symbol (do not rename)
static int ads_test4(void)
{
acedCommand(RTSTR, _T("_POINT"), RTSTR,_T("5,5,0"), RTNONE);
acedRetVoid();
return (RSRSLT);
}
} ;
// ----------------------------------------------------------
ACED_ARXCOMMAND_ENTRY_AUTO(CSendingCommandsApp, AdskSendingCommands, _SendStringToExecTest, TEST1, ACRX_CMD_TRANSPARENT, NULL)
ACED_ARXCOMMAND_ENTRY_AUTO(CSendingCommandsApp, AdskSendingCommands, _SendMessageTest, TEST2, ACRX_CMD_TRANSPARENT, NULL)
ACED_ARXCOMMAND_ENTRY_AUTO(CSendingCommandsApp, AdskSendingCommands, _SendCommandTest, TEST3, ACRX_CMD_TRANSPARENT, NULL)
ACED_ADSCOMMAND_ENTRY_AUTO(CSendingCommandsApp, test4, true)
// ----------------------------------------------------------
IMPLEMENT_ARX_ENTRYPOINT(CSendingCommandsApp)
VBA sample code
This sample defines VBA macros that create points from 6,6 to 8,8, using techniques that mirror the ones shown in the ObjectARX sample.
Option Explicit On
Private Const WM_COPYDATA = &H4A
Private Type COPYDATASTRUCT
dwData As Long
cbData As Long
lpData As String
End Type
Private Declare Function SendMessage Lib "user32" Alias _
"SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal _
wParam As Long, ByVal lParam As Any) As Long
Public Sub SendMessageToAutoCAD(ByVal message As String)
Dim data As COPYDATASTRUCT
Dim str As String
str = StrConv(message, vbUnicode) 'converts to Unicode
data.dwData = 1
data.lpData = str
data.cbData = (Len(str) + 2)
SendMessage(ThisDrawing.Application.hwnd, WM_COPYDATA, 0, data)
End Sub
Public Sub Test4()
ThisDrawing.SendCommand("_point 6,6,0 ")
End Sub
Public Sub Test5()
SendKeys("_point 7,7,0 ")
End Sub
Public Sub Test6()
SendMessageToAutoCAD("_point 8,8,0 ")
End Sub

Subscribe via RSS
Isn't this what the labs demonstrate? :) Just wondering about the target group of people of this blog.
Posted by: s.kaiser | August 11, 2006 at 03:33 PM
Hi Stephan,
Thanks for your comment, but I'm not sure what you mean...
1) Which labs?
2) Do you mean this specific entry or the blog as a whole?
Regards,
Kean
Posted by: Kean | August 11, 2006 at 03:50 PM
Hi, Kean!
Additional methods:
1) int acedPostCommand(char const *cmd);
2) PostMessage() to commandline window - useful in order cancel active command from external application: http://www.autocad.ru/cgi-bin/f1/board.cgi?t=26850Nn#20060506023520
Posted by: Alexander Rivilis | August 12, 2006 at 01:25 PM
Hi Alexander,
Thanks - these two are also definitely worth mentioning, and - as I'm sure you're aware - both are useful for cancelling running commands that have paused for user input.
Please keep the comments coming! :-)
Kean
Posted by: Kean | August 14, 2006 at 01:23 PM
Thanks for the helpful information.
As one that has been using acedCommand() and acedCmd() via P/Invoke for some time with great success, I don't think its entirely fair to discourage the use of these APIs from registered command handlers, only because the specifics relating command level nesting were left out.
Perhaps a better strategy would be to get into those specifics, to help readers understand the limitations and caveats of the use of these APIs.
For most practicle purposes, where the need is to execute a built-in AutoCAD command like PEDIT or what have you, and where there is little or no potential that others will attempt to script the registered command via the same API or the LISP (command) function, there is little danger that the command depth nesting limit will be reached.
But there are also other issues relating to the use of acedCommand() which wasn't covered in your article, that can also result in problems when that API is improperly used from the handler of a registered command. Namely, any command which is scripted via calls to acedCommand(), must be exited before the handler that calls acedCommand() exits. Failing to do that usually results in a fatal condition.
Another somewhat obscure issue relating to the use of all the various asynchronous methods you covered, is that their use can often corrupt or disable undo and/or redo, and prevent the user from redoing an operation that've undone. This usually occurs when attempting to use one of the asynchronous methods to execute commands that involve user interaction via the command line.
The files below provide convienent wrappers for acedCmd(), that allow those whose needs are relatively simple to avoid the hideous issues that plague all of the asynchronous forms of command execution, and thanks for covering some of the issues relating to the use of acedCommand from registered command handlers.
http://www.caddzone.com/CommandLine.cs
http://www.caddzone.com/CommandLine.vb
Posted by: Tony Tanzillo | March 24, 2007 at 10:54 PM
Kean,
Sorry for the double post, I mistakenly posted this comment in the wrong topic earlier.
Anyway to repeat the question: Is there a way to send a command from VBA to command line, such that you call a lisp interface exposed through ARX which takes some input parameters too?
For example: I have a function written in ARX that is exposed through LISP. So you can type on command line (doSomethingWithThisFunction selectionset name)
where:
doSomethingWithThisFunction is the name of function exposed through LISP interface from ARX
selectionset is an AutoCAD selectionset
name is just a string
Problem is when I use ThisDrawing.SendCommand () from VBA it treates the LISP function and its arguments as three separate commands
To sum it up I need to be able to call a command line function from VBA, such that the function also takes some parameters.
Is that possible?
Thanks
Posted by: vector | April 02, 2007 at 06:55 PM
Vector,
This is altogether possible: you just need to format the SendCommand() string appropriately. Make sure you use an opening parenthesis and pass the quotes for the string (and probably an apostrophe before the variable name containing the selection set).
The other option is to expose your function as a command that gets the selection set (which you would now pass with an exclamation mark: "!") and the string using acedSsGet() and acedGetString().
Regards,
Kean
Posted by: Kean | April 03, 2007 at 10:09 AM
Kean,
I tired using send command using the following syntax in VBA
Dim objNewSS As Object
objNewSS.SelectOnScreen
Dim tagNum As String
tagNum = "UDE-121"
strincom = "(at_UserDefinedFromSelSetAndTag 'objNewSS tagNum)" & vbCr
ThisDrawing.SendCommand strincom
When I do that I get the following error on the command line.
"; error: invalid data type or data overflow: OBJNEWSS"
Remember that this function is working perfectly from LISP when I do the following
(setq OBJ_SS (ssget) ; pick objects one at a time
NDX 0
)
(if OBJ_SS
;; if there is a selection set, do...
(progn
(while (< NDX (sslength OBJ_SS))
(setq OBJ (ssname OBJ_SS NDX)
NDX (+ NDX 1)
)
(setq tagnum "UDE-121")
(at_UserDefinedFromSelSetAndTag OBJ_SS tagnum)
)
)
;; otherwise, no selection set
(progn
(alert "Nothing selected.\nExiting.")
(exit)
)
Could it be that the send command can only process data of type string and has no clue what a selection set type, or an object type means? Any help would be appreciated.
Thanks,
Vector
Posted by: vector | April 04, 2007 at 04:04 PM
Vector,
Your problem is that you're trying to pass VBA-defined variables by name through LISP to ObjectARX. Unlike with LISP symbols, VBA variable names are not exposed to AutoCAD itself - neither LISP or ObjectARX will understand them.
For the string argument, this is easy - just change your code to this:
strincom = "(at_UserDefinedFromSelSetAndTag """ + tagNum + """)" & vbCr
For the selection set, it's trickier. The simplest is probably to use acedSsGet("P") from your ObjectARX application to pick up the most recent selection set. Otherwise it gets very diffidult (another option would be to use COM client code in your ObjectARX app to query the contents of a named selection set in VBA).
Speaking of named selection sets - I assume you've simplified out a line such as this from your code:
Set objNewSS = ThisDrawing.SelectionSets.Add("SS")
Regards,
Kean
Posted by: Kean | April 05, 2007 at 03:57 AM
Kean,
I tried another approach. It worked partially
Set objNewSS = ThisDrawing.SelectionSets.Add("SS")
Then used
strincom = "(at_UserDefinedFromSelSetAndTag objNewSS.Name tagNum)" & vbCr
ThisDrawing.SendCommand strincom
This worked and I could get access to "SS" name entity in ARX. However, when I tried to get selection set in the ARX, using the resbuf filter, it fails to get the selection set. For example
struct resbuf rb;
rb.restype = 0; // Entity name filter
rb.resval.rstring = EName; //Ename is coming
//fromVBA properly
rb.rbnext = NULL;
if (ads_ssget("_X",NULL, NULL, &rb, selSet) == RTNORM)
{
The check fails and the code won't execute here
---
---
---
}
Now what?!?! Any Ideas?
Posted by: vector | April 05, 2007 at 03:59 PM
Vector,
That's why I didn't suggest it: the "SS" name doesn't correspond to a symbol that's accessible from LISP or ObjectARX (that I can tell). Your best bet is to omit the argument altogether and rely on the previous selection, as I suggested (hopefully that's an option for you). Otherwise things get messy.
Kean
Posted by: Kean | April 05, 2007 at 06:06 PM
Kean,
I tried another approach and it solved my problem. I am posting it here for the benefit of other users.
VBA doesn't expose a handle for the AcadSelectionset object, that's true. This is kind of a sticky area that makes for some messy code. There are a couple of other options, but they basically require doing most of it in lisp, either having code execute from the lisp environment, or running lisp from vba.
In the lisp you can access the activex SS. For example, in VBA create your
SS and name it "SSforLisp". Then in lisp:
(vl-load-com)
(setq ss-vba (vla-item
(vla-get-selectionsets
(vla-get-activedocument
(vlax-get-acad-object)))
"SSforLisp"));; gets the ActiveX SS
(setq ss-lisp (ssadd));;creates a new ename-based ss
(vlax-for ent ss-vba
(ssadd (vlax-vla-object->ename ent) ss-lisp)
);;add each object's ename to the ename-ss
Thanks,
Vector
Posted by: vector | April 06, 2007 at 08:47 AM
Vector,
Great - glad you found a solution!
This is basically the approach I meant when I said "(another option would be to use COM client code in your ObjectARX app to query the contents of a named selection set in VBA)."
My assumption was that you were working in C++, but this is essentially the same technique from LISP.
Kean
Posted by: Kean | April 06, 2007 at 08:53 AM
Hi
I am try to create a selection select in vb and then use the sendcommand function to run a autocad command and pick the selectionset created in vb as the previous selectionset.
I do not seem to be able to get the selectionset created in vb to be the previous set. Do you have any ideas how to do it for pass a selectionset from vb to a command with the send command.
Sub Example_SelectwithVB()
' This example adds objects to a selectionset with vb
On Error Resume Next
'SSetColl.Item("CURRENT").Delete
ThisDrawing.SelectionSets.Item("TEST_SSET").Delete
' Create the selection set
Dim ssetObj As AcadSelectionSet
Set ssetObj = ThisDrawing.SelectionSets.Add("TEST_SSET")
' Add objects to a selection set by prompting user to select on the screen
'ssetObj.SelectOnScreen
' Iterate through the model space collection.
' Collect the objects found into an array of objects
' to be added to the selection set.
ReDim ssobjs(0 To ThisDrawing.ModelSpace.Count - 1) As AcadEntity
Dim I As Integer
For I = 0 To ThisDrawing.ModelSpace.Count - 1
Set ssobjs(I) = ThisDrawing.ModelSpace.Item(I)
Next
' Add the array of objects to the selection set
ssetObj.AddItems ssobjs
ssetObj.Update
ThisDrawing.Regen acActiveViewport
MsgBox "A new SelectionSet called " & ssetObj.Name & " has been added to the SelectionSets collection.", vbInformation, "SelectionSets Example"
'Once the selectionset has been created in the previous code in vb it becomes the previous
'selectionset at the command line and can be accessed with the select p enter enter series
'of commands
ThisDrawing.SendCommand "AecIsolateObjects" + vbCr
ThisDrawing.SendCommand "P" + vbCr + vbCr
End Sub
Regards
Justin
Posted by: Justin Ralston | April 16, 2007 at 10:52 AM
Hmm... SelectOnScreen will modify the previous selection set (as it's using AutoCAD's selection process), but selection sets created programmatically do not.
In this case, I'd suggest either using SelectOnScreen (which is probably not what you need), or sending a much more complex text string to the command lines, one that contains a LISP command using (ssget "X") to select the various entities you want and save the selection set in a LISP variable. You should then be able to plug this into AecIsolateObjects using "!ss".
Posted by: Kean | April 24, 2007 at 07:37 AM
hi there
I have been scripting by using VBA for sometime. is there any other language(s) other than lisp. how about CSharp ? it it possible to write a code and use with Autocad as we do with VBA?
thanks
Posted by: LC | June 06, 2007 at 07:47 PM
hi kean
I am facing problem with VBA System when ever i am
opening a cad drawing there is message is coming
"VBA System... execution error execution error.
please find some solution
thanks
awes
Posted by: awes | February 12, 2008 at 05:34 PM
Hi Awes,
Sorry - it's not clear to me what the problem is. I suggest posting a more detailed description to the AutoCAD Visual Basic Customization Discussion Group.
Regards,
Kean
Posted by: Kean | February 12, 2008 at 05:59 PM
hi kean
regarding "VBA System... execution error execution error.
I think it's a computer virus that is stored in the macros of a vba project. if we open a drawing that contains this virus, a virus can become active and be transmitted to your computer.
from that point on, every drawing file you save can become infected with the virus. when other users open the infected drawing file, the virus can also be transmitted to their computers. so when ever we open any drawing file this execution error comes before starting the drawing and then it's normal. I want to remove this message, i tried antivirus but it's not detacting. please find some solution and advice.
Posted by: awes | February 14, 2008 at 08:42 PM
This sounds unlikely - I've never come across an AutoCAD VBA virus (but there's a first time for everything, I suppose).
This isn't really the right place to get help on this: I suggest contacting your reseller, or otherwise someone on the discussion group I mentioned in my last comment.
Kean
Posted by: Kean | February 15, 2008 at 07:41 AM
Has anyone successfully been able to replicate and port this to VB.NET and been able to run it on Vista? Been struggling for hours without being able to send a WM_COPYDATA using SendCommand with a COPYDATASTRUCT to AutoCAD. Any ideas or samples would be highly appreciated.
Thanks!
Posted by: Anders | March 04, 2008 at 10:31 AM
dear kean.
I found you as a professional programmer so I believe thats a good idea to consult with you. I am going to filter the points of the dwg file using selectionsets and select function. the problem is that i am going to filter them by symbol ( as an example select those points which have symbol tree. but i find no groupcode to filter it. I have got no Idea how to do it. I will really be grateful if you give me a hint.
Posted by: Shahin | July 18, 2008 at 04:16 PM
by the way i forgot to mention i am using vba
Posted by: Shahin | July 18, 2008 at 04:17 PM
Shahin - please post your questions to the discussion groups, or via the ADN website, if you're a member. I don't have the luxury of spending time providing specialised support for people, unless it's very much related to a post of mine.
Kean
Posted by: Kean | July 18, 2008 at 04:28 PM
How can i call superhatch command with all input paramters using vb.net? I just don't want to prompt the dialog boxes of superhatch to get inputs from user. I want to provide it programmatically.
For eg: similar to this
doc.SendCommand("_superhatch par1,par2.....")
For rectangle, its :
doc.SendCommand("_rec 10,10 20,20")
But dont know the fromat to pass parameters for superhatch.
Can anybody help me on this?
Thanks,
Sandip
Posted by: sandip bhoir | November 28, 2008 at 10:15 AM