Deriving from IAcadBaseObject2Impl

The AsdkSquareWrapper_DG SDK sample doesn't create or manipulate non-database-resident objects. If you examine its source code, you may notice that the CAsdkSquareWrapper wrapper class does not appear to derive from either IAcadBaseObjectImpl or IAcadBaseObject2Impl. Nevertheless, it is set up to support objects that are not database resident. This is possible because the wrapper class derives from IAcadEntityDispatchImpl which, in turn, derives from IAcadObjectDispatchImpl. The IAcadObjectDispatchImpl class inherits the IAcadBaseObject2Impl implementation. If your object derives from either IAcadEntityDispatchImpl or IAcadObjectDispatchImpl, it does not need to derive explicitly from IAcadBaseObject2Impl.

IAcadBaseObject and IAcadBaseObject2 differ primarily in how they identify objects. A database-resident object can be identified uniquely by its object ID. This ID is the key that IAcadBaseObject uses to reference the object it wraps. An object that does not reside in a database is not assigned an object ID, so IAcadBaseObject2 substitutes a pointer to an AcDbObject instance.

To support this abstraction, the Properties palette API provides the AcAxObjectRef class and the AcAxObjectRefPtr template class. An AcAxObjectRef object encapsulates a reference to an AcDbObject instance. This class supports both resident and non-resident objects. AcAxObjectRefPtr is a “smart-pointer” class that makes it simple for applications to handle both types of objects. Code usages of the AcDbObjectPointer template class may be replaced with AcAxObjectRefPtr references in applications that need to support non-database-resident objects.

The AsdkSquareWrapper_DG SDK sample, which implements non-database-resident support, provides the code examples for the following procedure.

To implement support for non-database-resident objects

In the header file for your COM wrapper, add a COM interface entry to your COM map for the IAcadBaseObject2 interface:

BEGIN_COM_MAP(CAsdkSquareWrapper)
...
COM_INTERFACE_ENTRY(IAcadBaseObject)
COM_INTERFACE_ENTRY(IAcadBaseObject2)
...
END_COM_MAP()

Notice that the COM map exposes both IAcadBaseObject and IAcadBaseObject2.

In the header file for your COM wrapper, declare the desired overrides for IAcadBaseObject2 implementation methods:

STDMETHOD(ForceDbResident)(VARIANT_BOOL *forceDbResident);
STDMETHOD(AddToDb)(AcDbObjectId& objId, 
    AcDbObjectId ownerId = AcDbObjectId::kNull, 
    TCHAR* keyName = NULL);
STDMETHOD(CreateObject)(AcDbObjectId ownerId = AcDbObjectId::kNull,
    TCHAR *keyName = NULL);

Replace any AcDbObjectPointer instances with AcAxObjectRefPtr. For instance:

AcDbObjectPointer<AsdkSquare> pSq(m_objId, AcDb::kForRead);

is replaced by

AcAxObjectRefPtr<AsdkSquare> pSq(&m_objRef, AcDb::kForRead);

In the CPP file for your COM wrapper, implement the needed IAcadBaseObject2Impl methods. You must implement the ForceDbResident(), AddToDb(), and CreateObject() functions if you support objects that are not database resident. The AsdkSquareWrapper_DG SDK sample defines these methods as shown below:

HRESULT CAsdkSquareWrapper::ForceDbResident(VARIANT_BOOL *forceDbResident) 
{
    if (NULL == forceDbResident)
        return E_POINTER;
    *forceDbResident = ACAX_VARIANT_FALSE;
    return S_OK;
}
HRESULT CAsdkSquareWrapper::AddToDb(AcDbObjectId& objId,
    AcDbObjectId ownerId, TCHAR* keyName)
{
    try 
    {
        AcAxDocLock docLock(ownerId);
        Acad::ErrorStatus es;
        AcAxObjectRefPtr<AsdkSquare> pSq(&m_objRef,
            AcDb::kForRead);
        AcDbBlockTableRecordPointer pBlockTableRecord(ownerId,
            AcDb::kForWrite);
        if((es = pBlockTableRecord.openStatus()) != Acad::eOk)
            throw es;
        if((es = pBlockTableRecord->appendAcDbEntity(objId,
            pSq.object())) != Acad::eOk)
            throw es;
    }
    catch(const Acad::ErrorStatus)
    {
        return Error(L"Failed to add square to database",
            IID_IAsdkSquareWrapper ,E_FAIL);
    }
    return SetObjectId(objId);
}
HRESULT CAsdkSquareWrapper::CreateObject(AcDbObjectId ownerId,
    TCHAR *keyName) 
{
    try 
    {
        Acad::ErrorStatus es;
        AcDbObjectPointer<AsdkSquare> pSq;
        if((es = pSq.create()) != Acad::eOk)
            throw es;
        pSq->setDatabaseDefaults(ownerId.database());
        AsdkSquare *pTmp = NULL;
        pSq.release(pTmp);
        SetObject((AcDbObject*&)pTmp);
    }
    catch(const Acad::ErrorStatus)
    {
        return Error(L"Failed to create square",
            IID_IAsdkSquareWrapper, E_FAIL);
    }
    return S_OK;
}

Call the IAcadBaseObject2Impl::Fire_Notification() function when you modify the wrapped object. For instance, every put_* property access or method should explicitly call this function, as shown below:

STDMETHODIMP CAsdkSquareWrapper::put_Number(short newVal)
{
    AcAxDocLock docLock(m_objRef.objectId(),
        AcAxDocLock::kNormal);
    if(docLock.lockStatus() != Acad::eOk &&
        docLock.lockStatus() != Acad::eNoDatabase)
        return E_ACCESSDENIED;
    AcAxObjectRefPtr<AsdkSquare> pSq(&m_objRef,
        AcDb::kForWrite);
    if (pSq.openStatus() != Acad::eOk)
        return E_ACCESSDENIED;
    pSq->setSquareId(newVal);
Fire_Notification(DISPID_NUMBER);
return S_OK; }

The Fire_Notification() function informs connected listeners of changes. Listeners are objects that implement IAcadObjectEvents and IPropertyNotifySink interfaces. This mechanism is very important for synchronizing Properties palette edits with command-line input, as described in Properties Palette Interaction with Commands.

Database-resident objects do not need to call the Fire_Notification() function. Their notifications are handled by AutoCAD reactors.