Invoking Unmanaged Methods from AutoCAD's .NET API

One of the greatest strength's of Autodesk's AutoCAD software is the extensive API access provided by the product.  For years now, it has been very common to use Lisp or VBA to extend the functionality of the various AutoCAD products in numerous ways.  However, recent years have seen the introduction of the Common Language Runtime (CLR) from Microsoft, and the introduction of a variety of programming languages that access this CLR via Microsoft's .NET Frameworks.  The most-popular of these languages include Visual Basic and C-Sharp.

The ability to program in a high-level language like Visual Basic or C-Sharp, along with the extensive support provided by the runtime system through the .NET frameworks, provides significant benefits to application developers.  One of these benefits is the ability to create "managed code", which reduces the complexities involved in managing computer resources.  As a result, Autodesk has provided a "managed API" that can be used to customize AutoCAD from a .NET environment.

Unfortunately, the managed API fails to expose all of the functionality available in the ObjectARX libraries.  From time to time, it may be necessary for you to access this "hidden" functionality from a C#.NET or VB.NET program.  This article illustrates one way to accomplish this task.  All examples are provided in C-Sharp, but it should be relatively easy to translate them to any other .NET language.

Preparing the Environment

In order to access the various entry points exposed to ObjectARX from a .NET language, we must first identify which methods or properties we wish to access, by reading the ObjectARX documentation.  Once we have identified a useful access point, we must identify something called the "mangled" names for the access point.  In order to do this, we will use a tool called dumpbin that should be installed by default when you install your IDE.  For example, dumpbin is installed along with Visual Studio.  If you are using the Express Editions of Visual Studio, you may have to download and install VS Express C++ Edition to get dumbin.

In order to use dumpbin easily, we want to add the location of the dumpbin.exe file to our Environment Path, so we may run it from any directory.  This may have happened during installation, or you may have to do it yourself.  You can test this to see if it is already setup by starting a Command Prompt window and typing "dumpbin" at the command line.  If you get an error message stating that the command could not be found, then we need to find dumpbin and add its directory to our Environment Path.

To do that, we'll open up our hard drive in Windows Explorer, then right-click on the "Program Files" directory and select "Search...".  We will then look for "dumpbin".  A sample of the possible results is shown below.

DUMPBIN search

It looks like there are three versions of dumpbin on this system.  Two of them look like they're for 64-bit systems, but I'm using a 32-bit system, so I'll go with the first one in the list.  We now need to add this directory to the Environment Path.

To do this, right-click on My Computer and select "Properties", then go to the "Advanced" tab and hit the "Environment Variables" button.  In the lower section of the dialog box ("System variables"), find the "PATH" system variable and hit the "Edit" button.  Now, add the above path to the end of the PATH variable, separated from the path before it by a semi-colon.  Make sure you do not erase or overtype any of the directories already in this path, or you may cause portions of your system to cease to function properly.

The image below illustrates this process:

Path setup

Now, if you go back to the Command Prompt window and type "dumpbin", you should see a list of command options in the window.

Note that, when you attempt to run dumpbin, you may get a warning message that other files could not be found.  For example, you may get a warning that "mspdb80.dll" could not be found.  If so, follow the same procedure as above.  Locate mspdb80.dll by searching Program Files, then add its directory to the Environment Path (it is probably at "C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE").

Identifying the "Mangled Name"

Now that we have configured our environment so that we can run dumpbin, we can use it to identify the mangled name for our desired ObjectARX entry point.  First, by using the ObjectARX documentation, we must identify the method name we wish to access, as well as the DLL that contains the method.

For this example, we are going to add access to the Scale() and SetScale() methods for the MLeader entity.  These methods are missing from the managed API.  By looking at the ObjectARX documentation, we find the method signatures for the methods:

Acad::ErrorStatus setScale (double scale);
double scale() const;

Now we only need to figure out which DLL contains the access point.  For the most part, the default AutoCAD entities (at least in the 2009 products) are located in the acdb17.dll file located in the AutoCAD install directory.  So let's take a look at the acdb17.dll file, and see what we find.

By typing simply "dumpbin" at the command line, we can see the various options for the command.  In order to see the various entry points exposed by the API, we must use the "/exports" flag.  Also, if we try doing this on acdb17.dll, we'll get an extremely long list that will probably overrun the screen buffer.  So we will also use the "/out" flag to send the output to a text file we can examine in a text editor.  In order to do this, we type the following command in the command line:

dumpbin /exports acdb17.dll /out:acdb17dump.txt

And here is a screenshot of the process.  Note that this image was captured using Civil-3D 2009, but the same basic process applies to any AutoCAD product.  Simply browse to the correct installation directory for the AutoCAD product you are using, and enter the dumpbin command as shown:

DUMPBIN command line entry

This creates a text file named "acdb17dump.txt" that we can open in any text editor.  If we do so, we'll see an extremely long list of exposed functions.  Scroll down the list until you find the various Scale() methods.  As we can see from the signature, one of these exposed methods is on the MLeader entity - that's the one we want.

DUMPBIN Output 1

Similarly, we can find the mangled name for the SetScale() method a little further down the list:

DUMPBIN Output 2

Creating the C#.NET Wrappers around the ObjectARX code

Now that we've identified the mangled names for our access points, we will create C-Sharp methods that invoke our target methods in acdb17.dll.

using System;
using System.Runtime.InteropServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
 
namespace DatabaseExtensions
{
    public class DatabaseExtensions
    {
        private static class AcDbMLeader
        {
            // this line improves performance by relaxing stack traces
            // during the call to the unmanaged code; only need to
            // include this line once for each class
            [System.Security.SuppressUnmanagedCodeSecurity]

            // method sig from ObjectARX:
            // double scale() const;

            [DllImport("acdb17.dll", CallingConvention = CallingConvention.ThisCall, CharSet = CharSet.Unicode, EntryPoint = "?scale@AcDbMLeader@@QBENXZ")]
            public static extern double scale(IntPtr mleader);

            // method sig from ObjectARX:
            // Acad::ErrorStatus setScale (double scale);

            [DllImport("acdb17.dll", CallingConvention = CallingConvention.ThisCall, CharSet = CharSet.Unicode, EntryPoint = "?setScale@AcDbMLeader@@QAE?AW4ErrorStatus@Acad@@N@Z")]
            public static extern ErrorStatus setScale(IntPtr mleader, double scale);
        }

        public static double GetMLeaderScale(MLeader mldr)
        { return AcDbMLeader.scale(mldr.UnmanagedObject); }

        public static void SetMLeaderScale(MLeader mldr, double scale)
        { AcDbMLeader.setScale(mldr.UnmanagedObject, scale); }
    }
}

In the code above, the AcDbMLeader subclass encapsulates our calls to the unmanaged code in acdb17.dll.  Note how the mangled names we located with dumpbin are used as the "EntryPoint" in the [DllImport] attribute.  As is typical when using a standard function call to invoke a method on a target object, a pointer to the target object (in this case, the MLeader) is passed as the first argument in the function call.  The succeeding arguments should match the method signature we found in the ObjectARX documentation.

Finally, we define the public static methods GetMLeaderScale() and SetMLeaderScale(), which are the methods we use in our code.

Using the ObjectARX Wrappers

Our new wrapper is now ready to be used in our code just like any other method.  For example, the snippet of code below will create an MLeader, and if it is not using an Annotative MLeader style, it sets the MLeader scale based on the current CANNOSCALE:

MLeader mleader = new MLeader();
mleader.SetDatabaseDefaults();
double scale = 1.0 / (double)Autodesk.AutoCAD.ApplicationServices.Application.GetSystemVariable("CANNOSCALEVALUE");
if (mleader.Annotative != AnnotativeStates.True)
    DatabaseExtensions.SetMLeaderScale(mleader, scale);

This method may be used to access any method in any DLL on your system, as long as that method has been made available to external calls.  It is not the only way to interact with the unmanaged world from the managed world, but it is a relatively simple and effective way to get around those holes in Autodesk's managed API.