Click or drag to resize

CComSafeArray Basics

This topic describes common usage of the CComSafeArray smart data type.

Creating and Returning a SAFEARRAY

When implementing a method that should return a SAFEARRAY, either as [out, retval], [in, out] or [out], the SAFEARRAY should be allocated by the driver and freed by the client. When you know in advance the size of the array that you are creating, it is better to call Create so that the memory is allocated up front and no reallocation is necessary.

C++
HRESULT Acme4321::IAcme1234Table_GetData(SAFEARRAY** val)
{
  HRESULT hr = S_OK;

  CComSafeArray<double> csaData;

  // Create array with 3 elements
  csaData.Create(3);

  // Set the values of the 3 elements
  csaData[0] = 0.0;
  csaData[1] = 1.0;
  csaData[2] = 2.0;

  // Assign the SAFEARRAY* while transfering ownership  
  *val = csaData.Detach();

  return hr;
}

When you do NOT know in advance the size of the array that you are creating, the CComSafeArray can dynamically resize itself as you append items to it.

C++
HRESULT Acme4321::IAcme1234Table_GetData(SAFEARRAY** val)
{
  HRESULT hr = S_OK;

  CComSafeArray<double> csaData;

  // Create array with no elements
  csaData.Create();

  // Add elements
  csaData.Add(0.0);
  csaData.Add(1.0);
  csaData.Add(2.0);

  // Assign the SAFEARRAY* while transfering ownership  
  *val = csaData.Detach();

  return hr;
}

When you want to return an array with no elements, you have to call Create otherwise the internal SAFEARRAY* will be NULL.

C++
HRESULT Acme4321::IAcme1234Table_GetData(SAFEARRAY** val)
{
  HRESULT hr = S_OK;

  CComSafeArray<double> csaData;

  // Create array with no elements
  csaData.Create();

  // Assign the SAFEARRAY* while transfering ownership  
  *val = csaData.Detach();

  return hr;
}

Although using Detach is preferred (it doesn't allocate a new SAFEARRAY unnecessarily), it is sometimes more appropriate to use CopyTo, as in the following example. Since the SAFEARRAY we want to return is kept in a member variable, we do not want the internal SAFEARRAY* ownership transferred to the caller.

C++
class Acme1234
{
...
  CComSafeArray<double> m_csaData;
};

HRESULT Acme1234::IAcme1234Table_GetData(BSTR* val)
{
  HRESULT hr = S_OK;

  // Copy the array, this assumes the array was initialized previously
  hr = m_csaData.CopyTo(val);

  return hr;
}
Receiving a SAFEARRAY

When a driver calls a server that returns a SAFEARRAY, the client must free the SAFEARRAY that is allocated by the driver.

C++
void main()
{
  // Initialize COM, create instance of object...

  SAFEARRAY* psaData;
  hr = pDriver->GetData(&psaData);
  if (SUCCEEDED(hr))
  {
    CComSafeArray<double> csaData;
    csaData.Attach(psaData);

    // Process the data
    for (long i = 0; i < (long)csaData.GetLength(); i++)
    {
      double val = csaData[i];
      // Do something with val...
    }

    // Array will be freed by the destructor
  }

  // Release instance of object, terminate COM...
}

When receiving a SAFEARRAY as a parameter, it is important to use the SAFEARRAY data type and then to wrap it with a CComSafeArray. You should not attempt to pass a pointer to the CComSafeArray's internal SAFEARRAY*. The following example describes the problem. The example will compile, but will fail with an assertion at run-time.

C++
// WARNING: DO NOT DO THIS!
// This will assert at run-time, you are not allowed to change the internal 
// SAFEARRAY behind CComSafeArray's back.
CComSafeArray<double> csaData;
hr = pDriver->GetData(csaData.GetSafeArrayPtr());
if (SUCCEEDED(hr))
{
  // Process the data...
}

When receiving a SAFEARRAY* as a return value (this assumes using #import with wrapper interfaces), you have to be careful to not leak the SAFEARRAY when wrapping it with a CComSafeArray. CComSafeArray's constructor and assignment operator will make a COPY of the SAFEARRAY. The following example describes the problem. The SAFEARRAY will returned by the driver will leak, because owned by the CComSafeArray is a copy of that SAFEARRAY.

C++
// WARNING: DO NOT DO THIS!
// This will leak, the SAFEARRAY* returned by GetData is copied 
// into the CComSafeArray, so the one returned by GetData will never
// be destroyed.
CComSafeArray<double> csaData = pDriver->GetData();

// Process the data...

The correct way to receive a SAFEARRAY* as a return value is to use Attach so that no copy of the SAFEARRAY is made.

C++
// This is the correct way to call a method that returns a SAFEARRAY* as a return value
SAFEARRAY* psaData = pDriver->GetData();
if (psaData != NULL)
{
  CComSafeArray<double> csaData;
  csaData.Attach(psaData);

  // Process the data...

  // Array will be freed by the destructor
}
Passing a SAFEARRAY as Input

When a client calls a driver that accepts a SAFEARRAY, the client must allocate and free the SAFEARRAY.

C++
void main()
{
  // Initialize COM, create instance of object...

  CComSafeArray<double> csaData;
  csaData.Create(3);
  csaData[0] = 0.0;  
  csaData[1] = 1.0;  
  csaData[2] = 2.0;  

  // Even though it's an input parameter, the data type is SAFEARRAY**
  // which is why we use GetSafeArrayPtr() to get a pointer to the internal
  // SAFEARRAY*.
  hr = pDriver->SetData(csaData.GetSafeArrayPtr());
  if (SUCCEEDED(hr))
  {
  }

  // Array will be freed by the destructor

  // Release instance of object, terminate COM...
}

More information is available in the MSDN documentation for CComSafeArray.

See Also

Other Resources

Download a complete CHM version of this documentation here.