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 <atlsafe.h>
template<typename T, bool rowMajor=true, bool extraElement=false>
class SArray2 : public CComSafeArray<T> {
void Add(){}
void Create(){}
void GetAt(){}
void GetDimensions(){}
void GetLowerBound(){}
void SetAt(){}
void operator[](int){}
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);
}
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)];
}
ULONG GetCount(UINT uDim = 0) const
{return CComSafeArray<T>::GetCount(rowMajor ? 1-uDim : uDim);}
ULONG GetUpperBound(UINT uDim = 0) const
{return CComSafeArray<T>::GetUpperBound(rowMajor ? 1-uDim : uDim);}
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;
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