ObjectARX 2010: Dealing With Missing Exports

In the new ObjectARX 2010 SDK, Autodesk has added some new virtual member functions that are not exported as they should be. For example, the AcGiFaceData class has had two new virtual functions added for setting and getting the face transparency:

class AcGiFaceData: public AcRxObject
{

//[… deleted for brevity]
ACDB_PORT virtual AcDbObjectId* materials() const;
ACDB_PORT virtual AcGiMapper* mappers() const
virtual void setTransparency(const AcCmTransparency *transparency);
virtual
AcCmTransparency* transparency() const

private
:
AcGiImpFaceData *mpAcGiImpFaceData;
};

As you can see, whoever added the new functions neglected to prefix them with the ACDB_PORT macro. ACDB_PORT evaluates to __declspec(export), which tells the compiler to export the function. Since the macro is missing, the new functions are not exported from acdb18.dll.

Since these are virtual functions, you won’t have any problems calling them through a pointer to an AcGiFaceData object that was constructed by AutoCAD. The problem arises when you derive a class from AcGiFaceData. Since the functions are not exported, the linker has no way of resolving their address for creating the virtual function table of your derived class. This results in linker errors:

acrxEntryPoint.obj : error LNK2001: unresolved external symbol “public: virtual void __thiscall AcGiFaceData::setTransparency(class AcCmTransparency const *)” (?setTransparency@AcGiFaceData@@UAEXPBVAcCmTransparency@@@Z)
acrxEntryPoint.obj : error LNK2001: unresolved external symbol “public: virtual class AcCmTransparency * __thiscall AcGiFaceData::transparency(void)const ” (?transparency@AcGiFaceData@@UBEPAVAcCmTransparency@@XZ)

Following is an example that results in these errors:

class AcGiFaceDataEx: public AcGiFaceData
{

public
:
AcGiFaceDataEx() {}
~
AcGiFaceDataEx() {}
}
Test;

The only solution is to provide an implementation of the missing functions. In this case, it could be accomplished by something like this:

class AcGiFaceDataEx: public AcGiFaceData
{

AcCmTransparency* mpTransparency;
public
:
AcGiFaceDataEx() : mpTransparency( NULL ) {}
~
AcGiFaceDataEx() { delete mpTransparency; }
virtual
void setTransparency(const AcCmTransparency *transparency)
{

delete
mpTransparency;
mpTransparency = (transparency? new AcCmTransparency( *transparency ) : NULL);
}

virtual
AcCmTransparency* transparency() const { return mpTransparency; }
}
Test;

This will fix the linker errors, but there is no guarantee that it will work as intended. AutoCAD might access its internal transparency value directly without calling through the member functions, which means it would never “see” the transparency set through the replacement member functions. Furthermore, the addition of the new pointer member changes the size of the class, which causes AcGiFaceDataEx arrays to have a different memory footprint than AcGiFaceData arrays. Lastly, what if Autodesk fixes the problem in a future AutoCAD service pack?

The ideal solution should not change the size of the class. It should check at runtime whether the function is exported, then use the exported function if it exists. That way, code that is written now will use the exported function if and when it becomes available in a future version of AutoCAD. When the function is not exported, an alternate implementation must be provided. This is not an unusual scenario, and the solution I present for the specific case of AcGiFaceData can be adapted to the more general problem.

In the AcGiFaceData case, the missing functions are virtual functions. Knowing this, it is possible to use a trick to get the address of the real function. In the code below, the function getAcGiFaceData_vtable() constructs a temporary AcGiFaceData object, from which it extracts a pointer to the object’s virtual function table. The virtual function table is just an array of function pointers, so the address of the desired function can be obtained by indexing into the virtual function table. The question is, how far? By counting virtual functions and data members starting from the top of the class hierarchy: in this case, 6 virtual functions in AcRxObject plus 16 virtual functions in AcGiFaceData = 22.

Note that obtaining a function pointer this way relies on Visual C++ implementation details, but this is safe to do since all ObjectARX modules must be compiled in Visual C++.

Following is my solution to the missing AcGiFaceData functions:

#pragma warning(push)
#pragma warning(disable: 4608)
template < typename Src, typename Dest >
Dest force_cast( Src src )
{

union
_convertor { Dest d; Src s; _convertor() : d(0), s(0) {} } convertor;
convertor.s = src;
return
convertor.d;
}

#pragma warning(pop)

static
FARPROC* getAcGiFaceData_vtable()
{

static
FARPROC* rfVTable = *(FARPROC**)&AcGiFaceData();
return
(rfVTable? rfVTable : NULL);
}

void AcGiFaceData::setTransparency( const AcCmTransparency* transparency )
{

typedef
void (AcGiFaceData::*F_setTransparency)( const AcCmTransparency* );
static
F_setTransparency pfSetTransparency = force_cast< FARPROC, F_setTransparency >(GetProcAddress( GetModuleHandleA( “acdb18.dll”), “?setTransparency@AcGiFaceData@@UEAAXPEBVAcCmTransparency@@@Z” ));
if
( !pfSetTransparency )
{

static
FARPROC* rfVTable = getAcGiFaceData_vtable();
if
( rfVTable )
pfSetTransparency = force_cast< FARPROC, F_setTransparency >( rfVTable[22] );
}

if
( pfSetTransparency )
(
this->*pfSetTransparency)( transparency );
}

AcCmTransparency* AcGiFaceData::transparency() const
{

typedef
AcCmTransparency* (AcGiFaceData::*F_transparency)() const;
static
F_transparency pfTransparency = force_cast< FARPROC, F_transparency >(GetProcAddress( GetModuleHandleA( “acdb18.dll”), “?transparency@AcGiFaceData@@UEBAPEAVAcCmTransparency@@XZ” ));
if
( !pfTransparency )
{

static
FARPROC* rfVTable = getAcGiFaceData_vtable();
if
( rfVTable )
pfTransparency = force_cast< FARPROC, F_transparency >( rfVTable[23] );
}

if
( pfTransparency )
return
(this->*pfTransparency)();
return
NULL;
}

Debugging ObjectARX: Break on Exception

I have presented a class entitled High Octane ObjectARX at Autodesk University the past two years. In 2006 the focus was on project organization, and last year I focused more on techniques for supporting multiple versions of AutoCAD with a single Visual Studio solution, touching briefly on testing and profiling. For 2008 I plan to focus on debugging.

To get you in the mood, I decided to blog about a capability of Visual Studio that often goes unnoticed and unused: breaking on an exception. This works just like breaking on a code breakpoint, except the debugger breaks execution *before* the exception handler gets control, thus allowing you to get a clearer picture of what was happening immediately before the exception occurred.

In an ObjectARX project, all kinds of exceptions can occur. Sometimes they are perfectly normal exceptions caused by and handled by AutoCAD. Many times, especially during development, your code triggers an exception that causes AutoCAD to crash, and it’s not obvious what caused things to go haywire.

When unexpected things happen while running your code under the debugger, the first order of business is to inspect the debug output window to determine what happened. The debug output window (Debug->Windows->Output) displays a message whenever an exception occurs. Sometimes a series of exceptions occur, usually all caused by the same root problem. Breaking when the first exception occurs will likely yield the most useful information about the cause.

Once you examine the debug output window to determine the type of exception that occurred, open the Exceptions dialog (Debug->Exceptions) and tell Visual Studio to break when the exception is thrown:

Visual Studio contains a prepopulated list of common exceptions to choose from, or you can add new ones if you need to break on an exception that is not already listed. Many times, breaking when the exception is thrown allows you to inspect the stack trace to determine which one of your functions is to blame — and in the vast majority of cases, it will be your code that is at fault!

The Day the ObjectARX SDK Died

Like that day almost 50 years ago, could today be the day that ObjectARX has died?

There have been a flurry of posts in the ObjectARX discussion group about problems downloading the new ObjectARX 2009 SDK. At first I dismissed the problems as new release hiccups, and expected things to get resolved in short order. Seeing that there were no new complaints this morning, I headed over to http://www.objectarx.com/ to download the new ObjectARX 2009 SDK. Instead of the new ObjectARX 2009 SDK, I received the following:

Maybe it’s a technical problem with the web site, and the download just didn’t start, I thought. I checked my email, and found this from Autodesk:

Thank you for your interest in development tools for Autodesk’s products. If you are not already an Autodesk developer partner, be sure to visit the Autodesk Developer Network website at http://www.autodesk.com/joinadn to learn more. As a member, you can access timely technical information, training and support to help you stay competitive.

If you are looking for self-help resources for your Autodesk software-based development efforts, visit our Support page at http://www.autodesk.com/support or access the ObjectARX newsgroup at news://discussion.autodesk.com/autodesk.autocad.objectarx.

Regards,

The Application Development Team

Putting two and two together, it looks to me like the ObjectARX SDK is no longer freely available. It’s not altogether surprising that Autodesk would try something like this in an (almost certainly misguided) attempt to make it more difficult for organizations like the Open Design Alliance to reverse engineer the API.

If this is true, I foresee a major sea change with a ripple effect that will change the face of third party add-on development. It’s a risky legal maneuver, first of all. Any time a large corporation like Autodesk picks and chooses who it will do business with, it risks running afoul of the Sherman Antitrust Act. Secondly, the practical effect of limiting access to Autodesk’s SDK will be to give Open Design Alliance more influence vis à vis its DRX SDK.

For now, there is not too much of an immediate impact. AutoCAD 2009 is binary compatible with AutoCAD 2007 and AutoCAD 2008, so software developed with those SDKs will work in AutoCAD 2009 (of course without utilizing the new features). Therefore, development of AutoCAD 2009 software can continue with only a minor hit. The problem will not be felt until the next release of AutoCAD, which presumably will no longer be binary compatible.

Where is all this headed? I’m still holding out hope that it’s all a big misunderstanding; otherwise we’re headed for some upheaval in the AutoCAD add-on industry.

[Update: as of early Friday morning the ObjectARX 2009 SDK download is now working!]

AU 2007 Post Mortem

The Hot List

  • Carl Bass munching on fast food in the food court, and spotted throughout the week yakking with average folks in the halls.
  • Staggered classes reduced the lunchtime crush experienced at AU 2006.
  • More ObjectARX programming classes this year. I’m pushing for creating a new “Developer” track for AU 2008.
  • The Matt Murphy Head (peace be upon him).
  • Meeting a lot of familiar faces and a few new ones!

The Cold List

  • The food was worse this year than even AU ’97 in Los Angeles. At least AU ’97 had a Burger King nearby for someone on a budget; here in Las Vegas I ended up spending several hundred dollars over the course of the week just for food.
  • Pete Kelsey’s band, Dr. Ruth, didn’t play. Rumor has it that the AU organizers refused to give them space. Dr. Ruth has become an AU staple, and it just wasn’t the same without them.
  • Shaan Hurley’s gut-wrenching regimen for clearing 20 year old meat byproducts from various body cavities.

I presented the first of two ObjectARX roundtable discussions this year (the second one, hosted by Stephen Preston, also covered .NET). This was an experimental format. I liked it, and I thought it went well. If you were there, I’d like to hear your suggestions for making it better next year. I will be following up with some more posts covering topics raised in this and other AU classes.

VC Build Hook utility for Visual Studio 2008

The new Visual Studio 2008 has been released, and to celebrate, I’ve updated VC Build Hook. The updated utility allows you to use the new IDE while still building with older versions of the build tools.

Why is this necessary? If you build your AutoCAD 2007/2008 ObjectARX application with the Visual C/C++ 9.0 tools, AutoCAD will display a warning message about an incompatible module when you try to load it. You can use VC Build Hook to target all previous AutoCAD versions from a single Visual Studio 2008 solution (if you have the correct version of Visual C/C++ installed alongside Visual Studio 2008).

In my testing so far, existing Visual Studio 2005 projects build without problems in Visual Studio 2008 when VC Build Hook is installed and the new ‘BuildToolVersion’ property is set to the correct version.