Smart types


Smart types are objects that behave like types. They are C++ class implementations that encapsulate a data type, wrapping it with operators and functions that make working with the underlying type easier and less error prone. When these smart types encapsulate an interface pointer, they are referred to as smart pointers. Smart pointers work with the IUnknown  interface to ensure that resource allocation and deallocation are correctly managed. They accomplish this by various functions, construct and destruct methods, and overloaded operators.
Smart types can make the task of working with COM interfaces and data types easier, since many of the API calls are moved into a class implementation; however, they must be used with caution and never without a clear understanding of how they are interacting with the encapsulated data type.
The smart types supplied with the C++ API consist of:
  • _com_ptr_t - This class encapsulates a COM interface pointer, creating a smart pointer.
  • CComBSTR - This class encapsulates the BSTR data type.
  • CComVariant - This class encapsulates the VARIANT data type.
To define a smart pointer for an interface, you can use the macro _COM_SMARTPTR_TYPEDEF like this:
[C++]
_COM_SMARTPTR_TYPEDEF(IFoo, __uuidof(IFoo));
The compiler expands this as follows:
[C++]
typedef _com_ptr_t < _com_IIID < IFoo, __uuidof(IFoo) >  > IFooPtr;
Once declared, it is simply a matter of declaring a variable as the type of the interface and appending Ptr to the end of the interface. Below are some common uses of this smart pointer that you will see in the numerous C++ samples.
[C++]
// Get a CLSID GUID constant
extern "C" const GUID __declspec(selectany)CLSID_Foo =  \
  {
  0x2f3b470c, 0xb01f, 0x11d3, 
  {
    0x83, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
  }
};

// Declare Smart Pointers for IFoo, IBar and IGak interfaces
_COM_SMARTPTR_TYPEDEF(IFoo, __uuidof(IFoo));
_COM_SMARTPTR_TYPEDEF(IBar, __uuidof(IBar));
_COM_SMARTPTR_TYPEDEF(IGak, __uuidof(IGak));

HRESULT SomeClass::Do()
{
  // Create Instance of Foo class and QueryInterface (QI) for IFoo interface
  IFooPtr ipFoo;
  HRESULT hr = ipFoo.CreateInstance(CLSID_Foo);
  if (FAILED(hr))
    return hr;

  // Call method on IFoo to get IBar
  IBarPtr ipBar;
  hr = ipFoo->get_Bar(&ipBar);
  if (FAILED(hr))
    return hr;

  // QI IBar interface for IGak interface
  IGakPtr ipGak(ipBar);

  // Call method on IGak
  hr = ipGak->DoSomething();
  if (FAILED(hr))
    return hr;

  // Explicitly call Release()
  ipGak = 0;
  ipBar = 0;

  // Let destructor call IFoo's Release
  return S_OK;
}
Beware! If you have used smart pointers before, you might have seen differences in the implementation of the equality ("==") operator for smart pointer comparisons. The COM specification states object identification is performed by comparing the pointer values of IUnknown. The smart pointers will perform necessary QI and comparison when using the "==" operator.
Beware! Don't use the & operator to pass data. Instead, put your smart pointer in a container structure and pass the address of the container structure. If you are using one of the Motif widget controls, the widget itself functions as a container and can be passed.
When working with CComBSTR, use the text mapping L"" to declare constant OLECHAR  strings. To display a CComBSTR at the command line, use wcerr. You will need to include iostream to use wcerr.
[C++]
CComBSTR bsName(L "Matt");
std::wcerr << L "The name is " << (BSTR)bsName << std::endl;
CComVariant derives directly from the VARIANT data type, meaning that there is no overloading with its implementation, which in turn simplifies its use. It has a rich set of constructors and functions that make working with VARIANTs straightforward; there are even methods for reading and writing from streams. Be sure to call the Clear method before reusing the variable.
[C++]
ipFoo->put_Name(CComBSTR(L "NewName"));
if FAILED(hr)
)return hr; 

// Create a VT_I4 variant (signed long)
CComVariant vValue(12); 

// Change its data type to a string
  hr = vValue.ChangeType(VT_BSTR); if (FAILED(hr)
)return hr;
Some method calls in idl are marked as being optional and take a variant parameter. However, in C++ (and VC++) these parameters still have to be supplied. To signify that a parameter value is not supplied a variant is passed specifying an error code or type DISP_E_PARAMNOTFOUND:
[C++]
CComBSTR documentFilename(L "World.mxd");

CComVariant noPassword;
noPassword.vt = VT_ERROR;
noPassword.scode = DISP_E_PARAMNOTFOUND;
HRESULT hr = ipMapControl->LoadMxFile(documentFilename, noPassword);
However, if you do have a value that you want to pass in for the variant, use the smart type, CComVariant.
[C++]
int val = 1;
CComVariant smartVal(val);
ipRowBuffer->put_Value(2, smartVal);
When working with CComBSTR and CComVariant, the Detach function releases the underlying data type from the smart type and can be used when passing a result as an [out] parameter of a method. The use of the Detach method with CComBSTR is shown below:
[C++]
HRESULT CFoo::get_Name(BSTR *name)
{
  if (name == 0)
    return E_POINTER;
  CComBSTR bsName(L "FooBar");
  *name = bsName.Detach();
}
Beware! CComVariant(VARIANT_TRUE) will create a short integer variant (type VT_I2) and not a boolean variant (type VT_BOOL) as expected. You can use CComVariant(true) to create a boolean variant.
Beware! CComVariant myVar(ipSmartPointer) will result in a variant type of boolean (VT_BOOL) and not a variant with an object reference (VT_UNKNOWN) as expected. It is better to pass unambiguous types to constructors, i.e. types which are not themselves smart types with overloaded cast operators.
[C++]
// Perform QI it IUnknown
IUnknownPtr ipUnk = ipSmartPointer;
// Ensure we use IUnknown* constructor of CComVariant
CComVariant myVar2(ipUnk.GetInterfacePtr());
A common practice with smart pointers is to use Detach to return an object from a method call. When returning an interface pointer the COM standard is to increment reference count of the [out] parameter inside the method implementation. It is the callers responsibility to call Release when the pointer is no longer required. Consequently care must be taken to avoid calling Detach directly on a member variable. A typical pattern is shown below:
[C++]
HRESULT CFoo::get_Bar(IBar **pVal)
{
  if (pVal == 0)
    return E_POINTER;

  // Constructing a local smart pointer using another smart pointer
  // results in an AddRef (if pointer is not 0).
  IBarPtr ipBar(m_ipBar);

  // Detach will clear the local smart pointer and the 
  // interface is written into the output parameter.
  *pVal = ipBar.Detach();

  // This can be combined into one line 
  // *pVal = IBarPtr(m_ipBar).Detach(); 
  return S_OK;
}
The above pattern has the same result as the following code, note that a conditional test for a 0 pointer is required before AddRef can be called, calling AddRef (or any method) on a 0 pointer will result in an access violation exception and typically crash the application:
[C++]
HRESULT CFoo::get_Bar(IBar **pVal)
{
  if (pVal == 0)
    return E_POINTER;

  // copy the interface pointer (no AddRef) into the output parameter
  *pVal = m_ipBar;

  // Make sure interface pointer is non 0 before calling AddRef
  if (*pVal)
    *pVal->AddRef();

  return S_OK;
}
When using a smart pointer to receive an object from from an [out] parameter on a method, use the smart pointer "&" de-reference operator. This will cause the previous interface pointer in the smart pointer to be released. The smart pointer is then populated with the new [out] value. The implementation of the method will have already incremented the object reference count. This will be released when the smart pointer goes out of scope:
[C++]
{
  IFooPtr ipFoo1, ipFoo2;
  ipFoo1.CreateInstance(CLSID_Foo);
  ipFoo2.CreateInstance(CLSID_Foo);

  // Initialize ipBar Smart pointer from Foo1
  IBarPtr ipBar;
  ipFoo1->get_Bar(&ipBar);

  // The "&" de-reference will call Release on ipBar
  // ipBar is then repopulate with a new instance of IBar
  ipFoo2->get_Bar(&ipBar);
}

// ipBar goes out of scope and the smart pointer destructor calls Release