Polymorphic bit flags in C++

I often encounter cases in ObjectARX programming where numerous boolean flags need to be persisted as part of an AutoCAD database object. For filing and for passing around to other functions, it’s most efficient to package those bit flags into a single unsigned integer. Below is an example that demonstrates how to use a union inside a containing class that can be simultaneously used as either a single unsigned integer or as individual booleans.


// first declare a compact bitflag structure as bool values
struct FlagsAsBools
{
bool flag1 : 1;
bool flag2 : 1;
bool flag3 : 1;
bool flag4 : 1;
bool flag5 : 1;
bool flag6 : 1;
bool flag7 : 1;
bool flag8 : 1;
bool flag9 : 1;
bool flag10 : 1;
bool flag11 : 1;
bool flag12 : 1;
};

// now the containing class with useful constructors and operators
class Flags
{
protected:
// and the hidden union that actually contains the flags
union _Flags
{
FlagsAsBools asBools;
Adesk::UInt32 asUInt; //space for up to 32 flags
} mFlags;
public:
Flags()
{
mFlags.asUInt = 0;
}
Flags(Adesk::UInt32 flags)
{
mFlags.asUInt = flags;
}
Flags(const FlagsAsBools& flags)
{
mFlags.asBools = flags;
}
operator Adesk::UInt32 () const { return mFlags.asUInt; }
const FlagsAsBools& asBools() const { return mFlags.asBools; }
FlagsAsBools& asBools() { return mFlags.asBools; }
};

// now a class that contains the flags
class Settings
{
AcString msSomeStringSetting;
bool mbSomeBoolSetting;
Flags mFlags;
public:
Settings () : mbSomeBoolSetting(false) {}

LPCTSTR someStringSetting() const { return msSomeStringSetting; }
void setSomeStringSetting(LPCTSTR someStringSetting) { msSomeStringSetting = someStringSetting; }
bool someBoolSetting() const { return mbSomeBoolSetting; }
void setSomeBoolSetting(bool someBoolSetting) { mbSomeBoolSetting = someBoolSetting; }
Flags flags() const { return mFlags; }
void setFlags(Flags flags) { mFlags = flags; }
};

// now use the flags as an argument to a function in different forms
void DoStuffBasedOnFlags( Adesk::UInt32 flags )
{
if( Flags(flags).asBools().flag7 )
{// do something when flag7 is set
}
}

void DoOtherStuffBasedOnFlags( const Flags& flags )
{
if( flags.asBools().flag1 )
{// do something when flag1 is set
}
}

void TestIt()
{
Settings& MySettings = GetSettings();
Flags flags = MySettings.flags();
if( flags.asBools().flag4 )
{// do something when flag4 is set
}
DoStuffBasedOnFlags( flags );
DoOtherStuffBasedOnFlags( flags );
flags.asBools().flag2 = true;
MySettings.setFlags( flags ); //save the new settings
}

Checking function return values

A lot of you are guilty of not checking return values from API function calls. I’ll bet you have a good excuse, like “it’s a lot of extra typing for no reason”, or “that function should never return an error code”.

I check return values religiously, use asserts liberally, and never assume that the implementation details of a function won’t change in the future. I think you should do the same.

In all my years of programming, I’ve never heard someone complain that their project failed because they spent too much extra time typing code to verify return values — but I have heard of projects failing because sloppy code made it almost impossible to diagnose and fix problems.

I see a lot of “sample code” posted on the internet in forums and blogs that omit error checking for brevity or to make the code clearer to read. That’s a bad idea, in my opinion. Sample code should be good code, not just clean code.

I will grant you that there are cases where error checking is not needed. For example, in the following code there is no need to verify the result of the call to acdbGetObjectId() because failure will be immediately evident in the call to acdbOpenObject() when idSelected is still null:

Acad::ErrorStatus MyFunction()
{
ads_name anameSelected;
ads_point apointSelected;
int nResult = acedEntSel( ACRX_T(“nSelect an object: “), anameSelected, apointSelected );
if( nResult != RTNORM ) return eNotAnEntity; //or whatever
AcDbObjectId idSelected;
acdbGetObjectId( idSelected, anameSelected ); //ignoring return value!
assert( !idSelected.isNull() ); //check it nevertheless in the debug build
AcDbEntity* pEnt = NULL;
Acad::ErrorStatus es = acdbOpenObject( pEnt, idSelected, AcDb::kForRead );
if( es != Acad::eOk ) return es;
// do something with pEnt
pEnt->close();
//I confess that I don’t bother checking the
//result of AcDbObject::close() unless it matters
return Acad::eOk;
}

There are legitimate cases where error checking is not needed, but I think especially sample code posted on the internet should encourage good programming habits by checking the result of every function that returns one.

Visual Studio 2010 Native Multi-Targeting

I was hoping that Microsoft would address the need to target earlier versions of Visual Studio within the Visual Studio 2010 IDE, but since they decided to punt, I tackled the problem myself. I’ve released my solution as an open source project named Daffodil for Visual Studio.

With Daffodil installed on your system along with earlier versions of Visual Studio, you can use the new native multi-targeting feature in Visual Studio 2010 to target those earlier versions. This makes it possible to use Visual Studio 2010 for building ObjectARX projects, for example. Daffodil performs essentially the same function that my VC Build Hook utility did in Visual Studio 2008 and 2005.

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