General Software

CAD Clinic: AutoCAD Commands in C#

15 Jul, 2005 By: Mike Tuersley Cadalyst

Use C# to Create Simple AutoCAD commands


Welcome back! This month we're going to create the same two simple AutoCAD commands that we did last month, but this time in C# (pronounced C-sharp). As we go through this process, you may want to reference last month's column. As with last month's code, you can use any editor to create these commands -- including Windows' NotePad -- because no graphics are involved. I will use Microsoft's Visual Studio this month since I used SharpDevelop last month. This way you can compare the screen shots to see how similar both editors are.

For this article, we are going to recreate the classic "Hello World" example and throw the text into AutoCAD's command line and into an MText object just like last month. The code is derived from the Autodesk sample available within the AutoCAD 2006 install, AutoCAD 2006 ObjectARX toolkit and from the Autodesk website. The twist this month is we will use C# instead of VB.NET.

To begin, I will open Microsoft Visual Studio 2003. You can use Microsoft Visual Studio 2002, 2005 or the free beta Microsoft Visual Express C# 2005. All offer a very similar interface. At the start page, I will select Visual C# Projects and a Class Library (figure 1).

figure
Figure 1. Selecting a new project.

Next I will add the references to our project just like with Visual Basic 6, VBA or last month's VB.NET project. As was the case last month, you will need to browse to AutoCAD's installed directory and select the managed libraries: ACDBMGD.DLL and ACMGD.DLL (figures 2 and 3). You do not select the AutoCAD 2006 type library in the COM tab! That would create a COM InterOp project similar to VB6-based programming. This project is going to be a .NET managed project, so you need to hook into the MGD files.

figure
Figure 2. Get to the Add Reference command by right-clicking.

figure
Figure 3. Browsing to add the MGD files.

Okay, so now you should be in the code window and ready to start writing code. Below is the first example that will write "Hello World from C#" to the AutoCAD command line.

using System;
using System.Runtime.InteropServices; 
using Autodesk.AutoCAD.Runtime; 
using Autodesk.AutoCAD.ApplicationServices; 
using acadApp = Autodesk.AutoCAD.ApplicationServices.Application; 

public class HelloWorld
{
  [Autodesk.AutoCAD.Runtime.CommandMethod("HELLO")] 
  public void HelloCommand() 
  { 
    acadApp.DocumentManager.MdiActiveDocument.Editor.WriteMessage(_
      "Hello World FROM C#!"); 
    acadApp.UpdateScreen(); 
  }
}

So right about now, all the VBers (who migrated to it from AutoLISP) are thinking "Oh, no! More parenthesis!" And, hopefully, the LISPers are thinking this doesn't look so bad. It's actually not bad at all once you really look at the code line by line and compare it to last month's VB.NET code. This code is doing exactly the same stuff in each line -- it's just written slightly different or backwards in some cases. Let's look at what I mean:

using Autodesk.AutoCAD.Runtime;

By pulling the line above out from the beginning section of code and examining it, you can see it looks almost like last month's:

Imports Autodesk.AutoCAD.Runtime

The only two differences are C#:

1. Uses the USING statement instead of VB.NET's IMPORTS statement
2. Requires a semicolon ";" at the end of the line of code

The next block of code is our class object. Just as in VB6-based and the VB.NET programming, our DLL needs a class so it can be called from AutoCAD. Then I have the actual subroutine, HelloCommand, where I write the message to AutoCAD's command line. Notice the subroutine has a descriptor attached to it just like last month that makes our subroutine accessible from within AutoCAD. The difference is that C# uses brackets "[","]":

[Autodesk.AutoCAD.Runtime.CommandMethod("HELLO")]

The very next line is our function declaration. Unlike VB, C# does not distinguish between subroutines and functions. Remember the difference between a subroutine and a function? A function can return a value while a subroutine never does. With C#, every subroutine is a function. The syntax is simply: Scope | Return value [data type] | Subroutine name. So, our HelloCommand is public and since it has no return value, the data type is set to void:

public void HelloCommand()

In C# all related code, or code blocks, is contained within matching curly brackets "{","}" similar to other C languages. So the code after the class declaration is all wrapped together, and then all the code after the function declaration is wrapped together, etc.

The rest of the code in the HelloCommand function matches last month's VB.NET code with the addition that we have added our semicolons.

Okay, just like last month, to test this program, compile it and then fire up AutoCAD 2006. Type in NETLOAD and load the DLL you just created. Once it loads, type HELLO into AutoCAD's command prompt and "Hello World!" should echo back after you press the Enter key. Fairly easy for our first C# app!

The second program from last month took the Hello World example one step further by writing the text into an MText entity that we also created. Here is the full source code for it:

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

public class HelloWorld 
{ 
  [Autodesk.AutoCAD.Runtime.CommandMethod("HELLOTEXT")] 
  public void HelloTextCommand() 
  { 		
    Database acadDB = HostApplicationServices.WorkingDatabase; 
    Autodesk.AutoCAD.DatabaseServices.TransactionManager 
	     acadTransMgr = acadDB.TransactionManager;
    Transaction acadTrans = acadTransMgr.StartTransaction();
    MText acadMText = new MText(); 
    try 
    { 
      BlockTableRecord acadBTR = (BlockTableRecord)acadTrans.GetObject
	       (acadDB.CurrentSpaceId, OpenMode.ForWrite);
      acadMText.Contents = "Hello World!!"; 
      acadBTR.AppendEntity(acadMText);
      acadTrans.AddNewlyCreatedDBObject(acadMText, true); 
      acadTrans.Commit(); 
    } 
    catch 
    { 
      acadTrans.Abort(); 
    } 
    finally 
    {
      if (acadMText != null)
          acadMText.Dispose();
    }
  } 
}

Now let's skip down to a variable declaration since all the code before it matches our first example. I'll pick on the simplest variable which is our MText entity:

MText acadMText = new MText();

This is where things get backward if you come from an AutoLISP or VB background. In both of those languages, I would use a command to declare, or state, that I am creating a variable (SETQ or DIM). Then I would state the name that I am assigning to my variable as well as the data type if I were in VB. With C#, I declare my data type first and then my variable name. There is no command needed to create the variable. So to simplify the code above, I could have written it like this:

MText acadMText;
acadMText = new MText;

I took a short cut and assigned the default value at the same time I declared the variable. This is where one of the biggest changes comes into play for LISPers and VBers. In C#, there is no such thing as a global variable. In both of those languages, it is very common practice to float global variables so subroutines, modules, classes, etc., all have access to the same variable without having to worry about passing it around. Matter of fact, in C# there is no such thing as a module (in VB) -- everything is a class.

Looking at the remainder of the code, the function follows the same transaction sequence that was demonstrated last month in VB.NET. What is different is how we test the MText entity to see if it exists before we dispose of it (clear it from memory). In C#, equals "=" is a double equal sign "==" and not equal "<>" is an exclamation mark with an equal sign "!=". A single equals sign "=" is an assignment operator in C# for which AutoLISP and VB make no distinction between the dual use of equal. Finally, the equivalent of NOTHING is NULL.

One more little gotcha with C# that is not apparently obvious at first is that C# is case sensitive. So if I was to write the following code, I would end up with a compile error:

MText acadMText;
acadMtext = new MText;

Notice how I used a capital "T" in the variable declaration but a lowercase "t" in the assignment statement. That's a particular hindrance at first if you are like me and used to declaring variables in mixed case, then typing them in all lowercase and using Visual Basic's Auto-Case Converter to verify that the variable name was typed correctly. If it wasn't, the variable remains all lowercase.

Kick It Up A Notch!
So to state the obvious, this article is by no means a definitive guide to writing C# applications. There are a lot more differences between it and VB. My intent was to introduce you to C# and hopefully entice you to explore it further on your own and in future CAD Clinic articles. I highly recommend checking out a copy of C# for Dummies by Stephen Randy Davis (2002, Hungry Minds, Inc.; ISBN 0-7645-0814-8) from your local library if you want to further investigate this powerful language. This book is a great stepping stone to gaining entry level knowledge.


About the Author: Mike Tuersley


AutoCAD Tips!

Lynn Allen

Autodesk Technical Evangelist Lynn Allen guides you through a different AutoCAD feature in every edition of her popular "Circles and Lines" tutorial series. For even more AutoCAD how-to, check out Lynn's quick tips in the Cadalyst Video Gallery. Subscribe to Cadalyst's free Tips & Tools Weekly e-newsletter and we'll notify you every time a new video tip is published. All exclusively from Cadalyst!
Follow Lynn on Twitter Follow Lynn on Twitter


Poll
At your company, who has the most say in CAD-related software purchasing?
CAD manager
CAD users
IT personnel
vice-president/department manager
CEO/company owner
Submit Vote