Implementing Static Property Interfaces

The easiest way to implement COM object wrappers that define static properties for custom objects is to use the Active Template Library (ATL). ATL makes it easy to create COM objects that support IDispatch. The most difficult part is integrating the ObjectARX custom object code with the ActiveX Server DLLs that ATL generates. Once the base object is working, however, it is simple to add properties that will appear in the Property Inspector.

See COM and ActiveX Automation, for information on how to create COM wrappers for your custom objects. Consult the Microsoft developer documentation for instructions on using the Microsoft compiler's interface and wizards to add ATL implementation elements to your interfaces.

To add properties

  1. Using the Microsoft ® Visual Studio tools, add a new ATL property to your interface. For instance, in the AsdkSquareWrapper_DG sample project, add the property to the IAsdkSquareWrapper definition.
  2. Set the property's type to double.
  3. Give the property a name, such as SquareSize. Leave the parameter list blank.
  4. When finished, open the implementation file for the IAsdkSquareWrapper COM object in the code editor.

    If you used a wizard for steps 1–3, you should find get_* and put_* function stubs in your implementation file for each read/write property you defined.

  5. In the get_* function stub for your property, add the appropriate code to return the property value. For example, here is the get_SquareSize() function from the AsdkSquareWrapper_DG sample:
STDMETHODIMP CAsdkSquareWrapper::get_SquareSize(double *pVal){
AcAxObjectRefPtr<AsdkSquare> pSq(&m_objRef,
AcDb::kForRead);
if (pSq.openStatus() != Acad::eOk)
return E_ACCESSDENIED;			
double size;
pSq->squareSideLength(size);
*pVal = size;
return S_OK;
}

In the put_* stub that the wizard created, add the appropriate code to set the property value. Here is the put_SquareSize()function from the AsdkSquareWrapper sample:

STDMETHODIMP CAsdkSquareWrapper::put_SquareSize(double 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->setSquareSideLength(newVal);
Fire_Notification(DISPID_SQUARESIZE);
return S_OK;
}

Compile and build your application.

To test your custom object's properties

  1. In AutoCAD, load your application and execute the command to create your custom entity. Make sure that your COM wrapper DLL is registered.
  2. Make sure the Properties palette is loaded by entering properties at the command line.
  3. Select your custom object. You should see and be able to change the entity-common properties and the side length in the Properties palette interface. If you are using the AsdkSquareWrapper sample, notice that the SquareSize property displays under the General category.

To categorize properties using only built-in categories

    You may not want all your properties to show up under the General category. This section demonstrates how to use built-in categories. Note that the CAsdkSquareWrapper class inherits ICategorizeProperties through the IOPMPropertyExtensionImpl interface.

  1. Add properties for the square center and ID number by following the To add properties procedure.
  2. In the COM class header file, change the derivation of the COM class to include IOPMPropertyExtensionImpl and IOPMPropertyExpander:
public IOPMPropertyExtensionImpl<CAsdkSquareWrapper>,
public IOPMPropertyExpander

Add the following interfaces to the end of the COM interface map:

COM_INTERFACE_ENTRY(IOPMPropertyExtension)
COM_INTERFACE_ENTRY(ICategorizeProperties)
COM_INTERFACE_ENTRY(IPerPropertyBrowsing)
COM_INTERFACE_ENTRY(IOPMPropertyExpander)

Add the declaration for the OPM property map to the public declarations of the COM class:

// IOPMPropertyExtension
//
BEGIN_OPMPROP_MAP()
    OPMPROP_ENTRY(0, 0x00000001, PROPCAT_Data, \
        0, 0, 0, "", 0, 1, IID_NULL, IID_NULL, "")
    OPMPROP_ENTRY(0, 0x00000003, PROPCAT_Geometry, \
        0, 0, 0, "", 0, 1, IID_NULL, IID_NULL, "")
END_OPMPROP_MAP()

Add the following two public inline functions to the class:

STDMETHOD(GetCategoryName)(
    THIS_
    /* [in]  */ PROPCAT propcat, 
    /* [in]  */ LCID lcid,
    /* [out] */ BSTR* pbstrName) 
    {return S_FALSE;}
virtual HINSTANCE GetResourceInstance()
{
    return _Module.GetResourceInstance();
}

Add public declarations for the following functions:

STDMETHOD(GetElementValue)(
    /* [in] */ DISPID dispID,
    /* [in] */ DWORD dwCookie,
    /* [out] */ VARIANT * pVarOut) ;
// Used for property expansion (currently variant types)
//
STDMETHOD(SetElementValue)(
    /* [in] */ DISPID dispID,
    /* [in] */ DWORD dwCookie,
    /* [in] */ VARIANT VarIn) ;       
// Used for property expansion (currently variant types)
//
STDMETHOD(GetElementStrings)( 
    /* [in] */ DISPID dispID,
    /* [out] */ OPMLPOLESTR __RPC_FAR *pCaStringsOut,
    /* [out] */ OPMDWORD __RPC_FAR *pCaCookiesOut) ;
//Used for property expansion (currently variant types)
//
STDMETHOD(GetElementGrouping)(
    /* [in] */ DISPID dispID,
    /* [out] */ short *groupingNumber) ;
// Used for property expansion (currently variant types)
//
STDMETHOD(GetGroupCount)(
    /* [in] */ DISPID dispID,
    /* [out] */ long *nGroupCnt) ;
STDMETHOD(GetPredefinedStrings)(
    /* [in] */ DISPID dispID,
    /* [out] */ CALPOLESTR *pCaStringsOut,
    /* [out] */ CADWORD *pCaCookiesOut);
STDMETHOD(GetPredefinedValue)(
    /* [in] */ DISPID dispID, 
    /* [out] */ DWORD dwCookie, 
    /* [out] */ VARIANT *pVarOut);

Add the implementation for the function in the CPP source file. These examples are for the AsdkSquare object:

STDMETHODIMP CAsdkSquareWrapper::GetElementValue(
    /* [in] */ DISPID dispID,
    /* [in] */ DWORD dwCookie,
    /* [out] */ VARIANT * pVarOut)
{
    if (pVarOut == NULL)
        return E_POINTER;
    AcAxObjectRefPtr<AsdkSquare> pSq(&m_objRef, AcDb::kForRead);
    if (pSq.openStatus() != Acad::eOk)
        return E_ACCESSDENIED;			
    if (dispID == DISPID_CENTERPOINT) {
        AcGePoint3d acgePt;
         pSq->squareCenter(acgePt);
         AcAxPoint3d acaxPt(acgePt);
         ::VariantCopy(pVarOut,&CComVariant(acaxPt[dwCookie]));
     }
     return S_OK;
 }
 
 STDMETHODIMP CAsdkSquareWrapper::SetElementValue(
   /* [in] */ DISPID dispID,
   /* [in] */ DWORD dwCookie,
   /* [in] */ VARIANT VarIn)
 {
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::kForRead);
    if (pSq.openStatus() != Acad::eOk)
        return E_ACCESSDENIED;			
    if (dispID == DISPID_CENTERPOINT) {
        AcGePoint3d acgePt;
        pSq->squareCenter(acgePt);
        AcAxPoint3d acaxPt(acgePt);
 
        acaxPt[dwCookie] = V_R8(&VarIn);
        pSq->upgradeOpen();
        pSq->setSquareCenter(acaxPt);
   Fire_Notification(DISPID_CENTERPOINT);
    }
    return S_OK;
 }
 
 STDMETHODIMP CAsdkSquareWrapper::GetElementStrings( 
   /* [in] */ DISPID dispID,
   /* [out] */ OPMLPOLESTR __RPC_FAR *pCaStringsOut,
   /* [out] */ OPMDWORD __RPC_FAR *pCaCookiesOut)
 {
   if (dispID == DISPID_CENTERPOINT)
   {
        long size;
        size = 3;
        pCaStringsOut->pElems = 
            (LPOLESTR *)::CoTaskMemAlloc(sizeof(LPOLESTR) * size);
        pCaCookiesOut->pElems = 
            (DWORD *)::CoTaskMemAlloc(sizeof(DWORD) * size);
        for (long i=0;i<size;i++)
            pCaCookiesOut->pElems[i] = i;
        pCaStringsOut->cElems = size;
        pCaCookiesOut->cElems = size;
        pCaStringsOut->pElems[0] = ::SysAllocString(L"Center X");
        pCaStringsOut->pElems[1] = ::SysAllocString(L"Center Y");
        pCaStringsOut->pElems[2] = ::SysAllocString(L"Center Z");
    }
    return S_OK;
}
STDMETHODIMP CAsdkSquareWrapper::GetElementGrouping(
    /* [in] */ DISPID dispID,
    /* [out] */ short *groupingNumber)
{
    return E_NOTIMPL;
}
STDMETHODIMP CAsdkSquareWrapper::GetGroupCount(
    /* [in] */ DISPID dispID,
    /* [out] */ long *nGroupCnt)
{
    return E_NOTIMPL;
}
STDMETHODIMP CAsdkSquareWrapper::GetPredefinedStrings(
    DISPID dispID, CALPOLESTR *pCaStringsOut, 
    CADWORD *pCaCookiesOut)
{
    return E_NOTIMPL;
}
STDMETHODIMP CAsdkSquareWrapper::GetPredefinedValue(
    DISPID dispID, DWORD dwCookie, VARIANT *pVarOut)
{
    return E_NOTIMPL;
}