Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

2D Wrapper for ATL's CComSafeArray

0.00/5 (No votes)
8 May 2003 1  
SafeArray Wrapper to simplify and speed up 2D CComSafeArray<>

Introduction

This is an attempt to make using 2D SAFEARRAYS palatable while providing C++ and VB like access. Starting in ATL7, Microsoft introduced a SAFEARRAY wrapper class, CComSafeArray<T> which provides some useful services like locking and destruction as well as providing vector access using the standard C++ "[]" notation. It is however, extremely slow and provides virtually no simplification for 2D (or more) arrays. Further, the documentation in MSDN creates a 3x3 array which it then populates with a 2x3 matrix totally obscuring any ability to see what goes on behind the scenes as the row count equals the column count.

This wrapper class attempted to address the following issues:

  • Provide Row Major (C++) and Column Major (VB) access mechanisms since VB seems to be the biggest user of 2D arrays but I am more comfortable with C++ ordering. Default is C++ like memory layout:SArray2<float,false,false>; a(3,4) has the same memory layout as float a[3][4].
  • Speed up the element access time. This is really important and I see a 40x improvement since CComSafeArray already locks the data.
  • Limit the wrapper to 2D arrays that are zero based. This is now the default with VB and required for native C++ arrays.
  • Provide VB like constructors as an option so that a declaration like SArray2<float,false,false> a(3,4) actually declares in the same way this dim a(3,4) as single does in VB. That has the same memory layout as a[5][4] in C++.
  • Inherits from CComSafeArray and avoids the use of any other state info which allows transparency with CComSafeArray. Methods that are not applicable to 2D SafeArrays have been masked by declaring them as private.
  • Is used with the ATL header file "atlsafe.h" which can be used anywhere, not just ATL projects.

The Wrapper

It's small enough to include:

// include ATL header that contains CComSafeArray<T>

// Writtten by M. Gray 5/03, donated to public domain, use at your own risk.

#include <atlsafe.h>


// Wrapper for ATL's CComSafeArray<T> providing 2D access.

// CComSafeArray<T> is great for 1D vectors but has awkward nD handling

template<typename T, bool rowMajor=true, bool extraElement=false>
class SArray2 : public CComSafeArray<T> {
    // These functions are overridden and made private since they are not 

    // useful for 2D arrays

    void Add(){}            // N/A

    void Create(){}            // N/A

    void GetAt(){}            // N/A

    void GetDimensions(){}    // N/A

    void GetLowerBound(){}    // N/A

    void SetAt(){}            // N/A

    void operator[](int){}        // N/A

public:
    SArray2(): CComSafeArray<T>(){}
    SArray2(UINT a_rows, UINT a_cols)
    {
        SAFEARRAYBOUND bounds[2];
        bounds[0].cElements = (UINT)extraElement + (rowMajor ? a_cols :
                                                               a_rows);
        bounds[0].lLbound = 0;
        bounds[1].cElements = (UINT)extraElement + (rowMajor ? a_rows
                                                             : a_cols);
        bounds[1].lLbound = 0;
        CComSafeArray<T>::Create(bounds ,2);
    }
    // Attach to safearray but check for 2D and zero base

    HRESULT Attach(const SAFEARRAY *psaSrc)
    {
        ATLASSERT(psaSrc->cDims==2 && psaSrc->rgsabound[0].lLbound==0 && 
                 psaSrc->rgsabound[1].lLbound==0);
        return CComSafeArray<T>::Attach(psaSrc);
    }
    T& operator()(UINT a_row, UINT a_col)
    {
        ATLASSERT(m_psa->rgsabound[rowMajor?0:1].cElements > a_row);
        ATLASSERT(m_psa->rgsabound[rowMajor?1:0].cElements > a_col);
        return static_cast<T *>(m_psa->pvData)
            [m_psa->rgsabound[1].cElements * (rowMajor ? a_row : a_col)
                                           + (rowMajor ? a_col : a_row)];
    }
    // this method will return the number of elements in a specific zero 

    // based dimension only

    ULONG GetCount(UINT uDim = 0) const 
              {return CComSafeArray<T>::GetCount(rowMajor ? 1-uDim : uDim);}
    // The zero based array dimension for which to get the upper bound. 

    // If omitted, the default is 0. 

    ULONG GetUpperBound(UINT uDim = 0) const 
           {return CComSafeArray<T>::GetUpperBound(rowMajor ? 1-uDim : uDim);}
    // A pointer to a SAFEARRAYBOUND structure that contains information on 

    // the number of elements and the lower bound of an array

    HRESULT Resize(const SAFEARRAYBOUND *pBound)
                                  {return CComSafeArray<T>::Resize(pBound);}
};

Using the code

This code is simple to use:

SArray2<float> sa(2,3);
sa(0,1)=1.3f;

The data portion of the SAFEARRAY has the same memory layout and meaning as float sa[2][3]

float sa[2][3]
sa[0][1]=1.3f;

To get an idea how ugly the code is using CComSafeArray directly, this accomplishes the same thing except slower:

SAFEARRAYBOUND bounds[2];
bounds[0].cElements = 3;
bounds[0].lLbound = 0;
bounds[1].cElements = 2;
bounds[1].lLbound = 0;

CComSafeArray<float> a;
a.Create(bounds ,2);
long index[2];
float fval=1.3f;
index[0]=1; // note: column major ordering of SafeArray Access

index[1]=0;
a.MultiDimSetAt(index, fval);

The included test harness provides a sample of various permuations and functions provided. It does nothing useful but shows various forms of row-major and column major declarations as well as the "k+1" DIM equivalents in VB.

History

Initial vers. 5/8/03

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here