Using MFC and the Add Class from Typelib Wizard to Access AutoCAD ActiveX Automation

This method uses MFC and the Visual C++ Add Class from Typelib Wizard to read the AutoCAD type library (acax24enu.tlb). The sample program uses the COM ActiveX Automation interfaces of AutoCAD to create a circle in model space. The following sections provide procedures to guide you through the program setup and implementation:

To set up a new MFC project file

  1. Start Visual C++ and create a new MFC DLL project called AsdkMfcComSamp.
  2. In the Application Settings, select MFC extension DLL.
  3. Select Finish to create the project.

To begin adding specific ObjectARX functionality, you must adjust the project settings and add boilerplate code.

To add ObjectARX compatibility

Add the appropriate values to the project properties to make the project build as an ObjectARX program. Be sure that the ObjectARX \inc and \lib directories are in the project's search path. This program needs to link with the following libraries:

acad.lib
acdb24.lib
rxapi.lib

In your project's definitions (DEF) file, add the following lines to the EXPORTS section:

acrxEntryPoint        PRIVATE
acrxGetApiVersion     PRIVATE

Open the AsdkMfcComSamp.cpp source file and add the following code to make the program ObjectARX compatible:

#include <rxregsvc.h>
#include <aced.h>
#include <rxmfcapi.h>
static void initApp()
{
    acedRegCmds->addCommand(
        "ASDK_MFC_COM", 
        "AsdkMfcComCircle",
        "MfcComCircle", 
        ACRX_CMD_MODAL, 
        addCircleThroughMfcCom);
}
static void unloadApp()
{
    acedRegCmds->removeGroup("ASDK_MFC_COM");
}
extern "C" AcRx::AppRetCode acrxEntryPoint
    (AcRx::AppMsgCode msg, void* appId)
{
    switch(msg)
    {
    case AcRx::kInitAppMsg: 
        acrxDynamicLinker->unlockApplication(appId);
        acrxDynamicLinker->registerAppMDIAware(appId);
        initApp(); 
        break;
    case AcRx::kUnloadAppMsg: 
        unloadApp(); 
        break;
    default:
        break;
    }
    return AcRx::kRetOK;
}

Stub in your new AutoCAD command handler function by adding the following code to the AsdkMfcComSamp.cpp source file:

void addCircleThroughMfcCom()
{
}

In order to build your project without linker warnings, you must make MFC aware that your debug project links to libraries that are built with the release MFC libraries. This is accomplished by suspending the definition of the debug symbol only while the compiler processes the MFC header files.

To exclude debug MFC code from the debug build of your project

In the generated stdafx.h file, before the first MFC include statement (usually #include <afxwin.h>),add the following code:

#ifdef _DEBUG
#undef _DEBUG
#endif

From the Build menu, select Build AsdkMfcComSamp to verify that your project setup is correct.

Your project should build successfully, but at this point it doesn't do anything. The next step is to implement the command handler with ActiveX Automation calls.

First, you must decide which interfaces are necessary. To add a new circle to model space, the IAcadApplication, IAcadDocument, and IAcadModelSpace interfaces are required. You use the AutoCAD type library, acax24enu.tlb, to get the definitions of these interfaces, as shown in the next procedure.

To import AutoCAD ActiveX interfaces

  1. Choose Add Class from the Microsoft Visual C++ Project menu.
  2. Choose MFC Class From TypeLib to bring up the Add Class From TypeLib Wizard.
  3. Choose the AutoCAD 2021 Type Library from the drop-down list. (Or, choose the File radio button and browse to the acax24enu.tlb file.)
  4. Select the desired interfaces (in this case, IAcadApplication, IAcadDocument, and IAcadModelSpace) and use the arrow button to add them to the list of generated classes.
  5. Choose Finish. The wizard creates new header files for the interface wrapper classes and adds them to your project.
    Note: The AutoCAD ActiveX Automation interfaces are documented in the ActiveX and VBA Reference.

Now that you have imported the necessary interfaces, you are ready to implement the command-handling function.

To implement AutoCAD ActiveX Automation calls

  1. In the AsdkMfcComSamp.cpp file, include the header files for the interface wrapper classes.
  2. In the empty addCircleThroughMfcCom() function, add declarations for the three interface wrapper classes:
CAcadApplication IApp;
CAcadDocument IDoc;
CAcadModelSpace IMSpace;

Use the acedGetAcadWinApp() function to obtain the CWinApp MFC object for AutoCAD, and call the GetIDispatch method:

IDispatch *pDisp = acedGetAcadWinApp()->
GetIDispatch(TRUE);

Once you have the IDispatch object, attach it to the locally defined CAcadApplication object and make sure that AutoCAD is visible:

IApp.AttachDispatch(pDisp);
IApp.put_Visible(true);

Obtain the active document dispatch and attach it to the locally defined CAcadDocument object:

pDisp = IApp.get_ActiveDocument();
IDoc.AttachDispatch(pDisp);

Query the active document for model space:

pDisp = IDoc.get_ModelSpace();
IMSpace.AttachDispatch(pDisp);

A circle requires a center point and radius. To make this efficient and transparent to different programming languages, the COM interface uses the VARIANT type. A point is stored in a VARIANT as a SAFEARRAY. Add the following code to set up a SAFEARRAY and store it in a VARIANT:

SAFEARRAYBOUND rgsaBound;
rgsaBound.lLbound = 0L;
rgsaBound.cElements = 3;
SAFEARRAY* pStartPoint = NULL;
pStartPoint = SafeArrayCreate(VT_R8, 1, &rgsaBound);
// X value
//
long i = 0;
double value = 4.0;
SafeArrayPutElement(pStartPoint, &i, &value);
// Y value
//
i++;
value = 2.0;
SafeArrayPutElement(pStartPoint, &i, &value);
// Z value
//
i++;
value = 0.0;
SafeArrayPutElement(pStartPoint, &i, &value);
VARIANT pt1;
VariantInit(&pt1);
V_VT(&pt1) = VT_ARRAY | VT_R8;
V_ARRAY(&pt1) = pStartPoint;

Call the AddCircle method from the CAcadModelSpace object:

IMSpace.AddCircle(pt1, 2.0);

Here is the finished function, with clean-up code and exception handling added:

void addCircleThroughMfcCom()
{
    TRY
    {
        CAcadApplication IApp;
        CAcadDocument IDoc;
        CAcadModelSpace IMSpace;
        IDispatch *pDisp = acedGetAcadWinApp()->
            GetIDispatch(TRUE); // AddRef is called on the
                                // pointer
        IApp.AttachDispatch(pDisp); // does not call AddRef()
        IApp.put_Visible(true);
        pDisp = IApp.get_ActiveDocument(); // AddRef is called
        IDoc.AttachDispatch(pDisp);
        pDisp = IDoc.get_ModelSpace(); // AddRef is called
        IMSpace.AttachDispatch(pDisp);
        SAFEARRAYBOUND rgsaBound;
        rgsaBound.lLbound = 0L;
        rgsaBound.cElements = 3;
        SAFEARRAY* pStartPoint = NULL;
        pStartPoint = SafeArrayCreate(VT_R8, 1, &rgsaBound);
        // X value
        long i = 0;
        double value = 4.0;
        SafeArrayPutElement(pStartPoint, &i, &value);
        // Y value
        i++;
        value = 2.0;
        SafeArrayPutElement(pStartPoint, &i, &value);
        // Z value
        i++;
        value = 0.0;
        SafeArrayPutElement(pStartPoint, &i, &value);
        VARIANT pt1;
        VariantInit(&pt1);
        V_VT(&pt1) = VT_ARRAY | VT_R8;
        V_ARRAY(&pt1) = pStartPoint;
        IMSpace.AddCircle(pt1, 2.0);
        VariantClear(&pt1);
        // Release() is called implicitly on the local objects
    }
    CATCH(COleDispatchException,e) 
    {
        e->ReportError();
        e->Delete();
    }
    END_CATCH;
}

Rebuild your project.

Load the ARX file in AutoCAD, and enter MFCCOMCIRCLE at the command line to test your code.

COM programmers are normally required to call Release() on any interface pointer for which AddRef() previously has been called. In the example above, MFC relieves you of this responsibility by internally calling Release() on its local interface wrapper objects. However, you should be mindful of the need to release COM interface pointers when you are finished using them. The technique of explicitly releasing interface pointers is demonstrated in the following procedure.