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;
}

Leave a Reply

Your email address will not be published. Required fields are marked *