template <typename T, VARTYPE _vartype = _ATL_AutomationType<T>::type> class CComSafeArray { ... public: LPSAFEARRAY m_psa; }
m_psa是CComSafeArray唯一的state
#define DEFINE_AUTOMATION_TYPE_FUNCTION(ctype, typewrapper, oleautomationtype) \ template <> \ struct _ATL_AutomationType<ctype> { \ typedef typewrapper _typewrapper;\ enum { type = oleautomationtype }; \ static void* GetT(const T& t) { \ return (void*)&t; \ } \ };
DEFINE_AUTOMATION_TYPE_FUNCTION用于将C++类型转换成VARTYPE
DEFINE_AUTOMATION_TYPE_FUNCTION(CHAR, CHAR, VT_I1) DEFINE_AUTOMATION_TYPE_FUNCTION(SHORT, SHORT, VT_I2) DEFINE_AUTOMATION_TYPE_FUNCTION(INT, INT, VT_I4) DEFINE_AUTOMATION_TYPE_FUNCTION(LONG, LONG, VT_I4) DEFINE_AUTOMATION_TYPE_FUNCTION(LONGLONG, LONGLONG, VT_I8) DEFINE_AUTOMATION_TYPE_FUNCTION(BYTE, BYTE, VT_UI1) DEFINE_AUTOMATION_TYPE_FUNCTION(USHORT, USHORT, VT_UI2) DEFINE_AUTOMATION_TYPE_FUNCTION(UINT, UINT, VT_UI4 DEFINE_AUTOMATION_TYPE_FUNCTION(ULONG, ULONG, VT_UI4) DEFINE_AUTOMATION_TYPE_FUNCTION(ULONGLONG, ULONGLONG, VT_UI8) DEFINE_AUTOMATION_TYPE_FUNCTION(FLOAT, FLOAT, VT_R4) DEFINE_AUTOMATION_TYPE_FUNCTION(DOUBLE, DOUBLE, VT_R8) DEFINE_AUTOMATION_TYPE_FUNCTION(DECIMAL, DECIMAL, VT_DECIMAL) DEFINE_AUTOMATION_TYPE_FUNCTION(VARIANT, CComVariant, VT_VARIANT) DEFINE_AUTOMATION_TYPE_FUNCTION(CY, CY, VT_CY)
于是_ATL_Automation_Type<long>::type=VT_I4
Constructors and Destructor
template只是确定array的元素类型,还需要确定维数和各维的size
有7个不同的构造函数用于这个工作,除了第一个Default无参构造函数外(m_psa=NULL),其余的函数都将使用到CComSafeArrayBound用于确定这些细节:
class CComSafeArrayBound : public SAFEARRAYBOUND { CComSafeArrayBound(ULONG ulCount = 0, LONG lLowerBound = 0) { ... } CComSafeArrayBound& operator=(const CComSafeArrayBound& bound) { ... } CComSafeArrayBound& operator=(ULONG ulCount) { ... } ULONG GetCount() const { ... } ULONG SetCount(ULONG ulCount) { ... } LONG GetLowerBound() const { ... } LONG SetLowerBound(LONG lLowerBound) { ... } LONG GetUpperBound() const { ... } };
例如:
explicit CComSafeArray(ULONG ulCount, LONG lLBound = 0) : m_psa(NULL) { CComSafeArrayBound bound(ulCount, lLBound); HRESULT hRes = Create(&bound); if (FAILED(hRes)) AtlThrow(hRes); }
Create是一层API的helper:
HRESULT Create(const SAFEARRAYBOUND *pBound, UINT uDims = 1) { ATLASSERT(m_psa == NULL); ATLASSERT(uDims > 0); HRESULT hRes = S_OK; m_psa = SafeArrayCreate(_vartype, uDims, const_cast<LPSAFEARRAYBOUND>(pBound)); if (NULL == m_psa) hRes = E_OUTOFMEMORY; else hRes = Lock(); return hRes; }
建立1维数组的例子:
// create a 1-D zero-based SAFEARRAY of long with 10 elements CComSafeArray<long> sa(10); // create a 1-D one-based SAFEARRAY of double with 5 elements CComSafeArray<double> sa(5,1);
也可以使用这个来建立1D数组:
explicit CComSafeArray(const SAFEARRAYBOUND& bound);
这个例子就不如上述例子简洁:
CComSafeArrayBound bound(5,1); // 1-D one-based array CComSafeArray<long> sa(bound);
使用下面的函数来建立多维数组:
explicit CComSafeArray(const SAFEARRAYBOUND *pBound, UINT uDims = 1);
例子:
// 3-D array with all dimensions // left-most dimension has 3 elements CComSafeArrayBound bound1(3); // middle dimension has 4 elements CComSafeArrayBound bound2(4); // right-most dimension has 5 elements CComSafeArrayBound bound3(5); // equivalent C-style array indices would be [3][4][5] CComSafeArrayBound rgBounds[] = { bound1, bound2, bound3 }; CComSafeArray<int> sa(rgBounds, 3);
从已有数组:
CComSafeArray(const SAFEARRAY *psaSrc) : m_psa(NULL); CComSafeArray(const SAFEARRAY& saSrc) : m_psa(NULL); CComSafeArray(const CComSafeArray& saSrc) : m_psa(NULL);
例子:
CComSafeArray<int> saSrc(5); // source is 1-D array of 5 ints // allocate storage for 1-D array of 5 ints // and copy contents of source CComSafeArray<int> saDest(saSrc);
析构函数调用Detroy():
HRESULT Destroy() { HRESULT hRes = S_OK; if (m_psa != NULL) { hRes = Unlock(); if (SUCCEEDED(hRes)) { hRes = SafeArrayDestroy(m_psa); if (SUCCEEDED(hRes)) m_psa = NULL; } } return hRes; }
请注意Unlock()函数,用于释放SAFEARRAY的计数
Assignment
CComSafeArray<T>& operator=(const CComSafeArray& saSrc) { *this = saSrc.m_psa; return *this; } CComSafeArray<T>& operator=(const SAFEARRAY *psaSrc) { ATLASSERT(psaSrc != NULL); HRESULT hRes = CopyFrom(psaSrc); if (FAILED(hRes)) AtlThrow(hRes); return *this; }
CopyFrom会先释放内部m_psa
例子:
CComSafeArray<long> sa1(10); // do something interesting with sa1 CComSafeArray<long> sa2(5); // free contents of sa1, duplicate contents // of sa2 and put into sa1 sa1 = sa2;
The Detach and Attach Methods
HRESULT Attach(const SAFEARRAY *psaSrc) { ATLENSURE_THROW(psaSrc != NULL, E_INVALIDARG); VARTYPE vt; HRESULT hRes = ::ATL::AtlSafeArrayGetActualVartype( const_cast<LPSAFEARRAY>(psaSrc), &vt); ATLENSURE_SUCCEEDED(hRes); ATLENSURE_THROW(vt == GetType(), E_INVALIDARG); hRes = Destroy(); m_psa = const_cast<LPSAFEARRAY>(psaSrc); hRes = Lock(); return hRes; } LPSAFEARRAY Detach() { Unlock(); LPSAFEARRAY pTemp = m_psa; m_psa = NULL; return pTemp; }
注意Destroy()也用到Unlock()方法,这个属于Win16的遗留
例如以下代码是错误的:
SAFEARRAY *psa = ::SafeArrayCreateVector(VT_I4, 0, 10); // BAD - this pointer may not be valid! int *pData = reinterpret_cast<int *>(pda->pvData); // BOOM (maybe) pData[0] = 5;
应该使用Lock:
SAFEARRAY *psa = ::SafeArrayCreateVector(VT_I4, 0, 10); // GOOD - this will allocate the actual storage for the data ::SafeArrayLock(psa); // Now the pointer is valid int *pData = ( int * )(pda->pvData); pData[0] = 5; // Unlock after we're done ::SafeArrayUnlock( psa );
实际上Lock为SAFEARRAY分配存储空间,并且设置pvData。CComSafeArrayy构造函数调用Lock(),析构函数调用Destroy(),后者调用Unlock()。
Attach用于处理[in]SAFEARRAY,例如:
STDMETHODIMP SomeClass::AverageArray(/* [in] */ SAFEARRAY* psa, /* [out] */ LONG* plAvg) { if (!plAvg) return E_POINTER; CComSafeArray<long> sa; // Note: no type check is done // against psa type sa.Attach(psa); // we're pointing at the same // memory as psa ... perform some calculations sa.Detach(); // Must detach here or risk a crash return S_OK; }
Detach用于处理[out]SAFEARRAY,例如:
STDMETHODIMP SomeClass::get_Array(/* [out] */ SAFEARRAY** ppsa) { if (!ppsa) return E_POINTER; CComSafeArray<long> sa(10); ... populate sa instance // no resources released when we leave scope // and no copying performed *ppsa = sa.Detach(); return S_OK; }
Attach/Detach不会做数据的Copy,下边的代码是错误的:
STDMETHODIMP SomeClass::DontDoThis(SAFEARRAY* psa) { // We have two references to the safearray CComSafeArray<long> sa1, sa2; sa1.Attach(psa); sa2.Attach(psa); // manipulate the array here // BUG: Don't do this sa2.Destroy( ); }
千万不要对同一SAFEARRAY采用多个Attach
CComSafeArray Operations
LONG GetLowerBound(UINT uDim = 0) const; LONG GetUpperBound(UINT uDim = 0) const; ULONG GetCount(UINT uDim = 0) const; UINT GetDimensions() const; VARTYPE GetType() const ; bool IsSizable() const;
其中类成员fFeatures控制IsSizable(),默认应该=TRUE
LPSAFEARRAY* GetSafeArrayPtr() { return &m_psa; }
HRESULT Resize(ULONG ulCount, LONG lLBound = 0); HRESULT Resize(const SAFEARRAYBOUND *pBound);
HRESULT Resize(const SAFEARRAYBOUND *pBound) { ATLASSUME(m_psa != NULL); ATLASSERT(pBound != NULL); if (!IsSizable()) { return E_FAIL; } HRESULT hRes = Unlock(); if (SUCCEEDED(hRes)) { hRes = SafeArrayRedim(m_psa, const_cast<LPSAFEARRAYBOUND>(pBound)); if (SUCCEEDED(hRes)) { hRes = Lock(); } } return hRes; }
这个函数有bug,应该检查返回的HRESULT
HRESULT Add(const T& t, BOOL bCopy = TRUE); HRESULT Add(ULONG ulCount, const T *pT, BOOL bCopy = TRUE); HRESULT Add(const SAFEARRAY *psaSrc);
Add流程:Create,Resize,SetAt
CComSafeArray Element Accessors
const typename _ATL_AutomationType<T>::_typewrapper& GetAt(LONG lIndex) const { ATLASSUME(m_psa != NULL); if(m_psa == NULL) AtlThrow(E_FAIL); LONG lLBound = GetLowerBound(); ATLASSERT(lIndex >= lLBound); ATLASSERT(lIndex <= GetUpperBound()); if ((lIndex < lLBound) || (lIndex > GetUpperBound())) AtlThrow(E_INVALIDARG); return ( (_ATL_AutomationType<T>::_typewrapper*) m_psa->pvData )[lIndex-lLBound]; } _ATL_AutomationType<T>::_typewrapper& GetAt(LONG lIndex) { // code identical to const version }
HRESULT SetAt(LONG lIndex, const T& t, BOOL bCopy = TRUE) { bCopy; ATLASSERT(m_psa != NULL); LONG lLBound = GetLowerBound(); ATLASSERT(lIndex >= lLBound); ATLASSERT(lIndex <= GetUpperBound()); ((T*)m_psa->pvData)[lIndex-lLBound] = t; return S_OK; }
例子:
CComSafeArray<long> sa(5); long lNewVal = 14; // replace the 4th element with the value 14 sa.SetAt(3, lNewVal);
template<> HRESULT CComSafeArray<BSTR>::SetAt(LONG lIndex, const BSTR& strData, BOOL bCopy) { // validation code omitted for clarity BSTR strOrg = ((BSTR*)m_psa->pvData)[lIndex-lLBound]; if (strOrg) ::SysFreeString(strOrg); if (bCopy) { BSTR strTemp = ::SysAllocString(strData); if (NULL == strTemp) return E_OUTOFMEMORY; ((BSTR*)m_psa->pvData)[lIndex-lLBound] = strTemp; } else ((BSTR*)m_psa->pvData)[lIndex-lLBound] = strData; return S_OK; }
例子:
BSTR bstr1 = ::SysAllocString(OLESTR("Go Longhorns!")); BSTR bstr2 = ::SysAllocString(OLESTR("ATL Rocks!")); CComSafeArray<BSTR> sa(5); sa.SetAt(2, bstr1, true); // sa generates its own copy of bstr1 sa.SetAt(3, bstr2, false); // sa assigns element to bstr2 ::SysFreeString(bstr1); // ok, sa still has a copy ::SysFreeString(bstr2); // wrong!!! we don't own bstr2
例子:
IUnknown* pUnk1, pUnk2;
// assign both pointers to refer to an object
CComSafeArray<IUnknown*> sa(5);
sa.SetAt(2, pUnk1, true); // sa calls AddRef on pUnk1
sa.SetAt(3, pUnk2, false); // sa assigns element to pUnk2
// without AddRefing
pUnk1->Release(); // ok, refcount non-zero because
// of sa AddRef
pUnk2->Release(); // wrong!!! we don't own pUnk2
HRESULT MultiDimGetAt(const LONG *alIndex, T& t) { ATLASSERT(m_psa != NULL); return SafeArrayGetElement(m_psa, const_cast<LONG*>(alIndex), &t); } HRESULT MultiDimSetAt(const LONG *alIndex, const T& t) { ATLASSERT(m_psa != NULL); return SafeArrayPutElement(m_psa, const_cast<LONG*>(alIndex), _ATL_AutomationType<T>::GetT(t)); }
例子:
// 2-D array with all dimensions // left-most dimension has 3 elements CComSafeArrayBound bound1(3); // right-most dimension has 4 elements CComSafeArrayBound bound2(4); // equivalent C-style array indices would be [3][4] CComSafeArrayBound rgBounds[] = { bound1, bound2 }; CComSafeArray<int> sa(rgBounds, 2); int rgIndElement1[] = { 0, 1 }; // access element at sa[1][0] int rgIndElement2[] = { 3, 2 }; // access element at sa[2][3] long lVal = 0; // retrieve value at sa[1][0] sa.MultiDimGetAt(rgIndElement1, lVal); // multiply value by 2 and store it // in element located at sa[2][3] sa.MultiDimSetAt(rgIndElement2, lVal*=2);
CComSafeArray Operators
const typename _ATL_AutomationType<T>::_typewrapper& operator[](int nIndex) const { return GetAt(nIndex); } typename _ATL_AutomationType<T>::_typewrapper& operator[](int nIndex) { return GetAt(nIndex); } const typename _ATL_AutomationType<T>::_typewrapper& operator[](LONG nIndex) const { return GetAt(nIndex); } typename _ATL_AutomationType<T>::_typewrapper& operator[](LONG nIndex) { return GetAt(nIndex); }
例子:
CComSafeArray<int> sa(5); ATLASSERT(sa[2] == 0); sa[2] = 17; ATLASSERT(sa[2] == 17);
operator const SAFEARRAY *() const { return m_psa; } operator LPSAFEARRAY() { return m_psa; }
CComSafeArray并未定义operator&(),所以下面的代码会发生编译错误:
HRESULT CreateANewSafeArray( SAFEARRAY** ppsa ) { *ppsa = SafeArrayCreateVector(VT_I4, 1, 15 ); return S_OK; } HRESULT UseCreatedSafeArray( ) { CComSafeArray< int > sa; HRESULT hr = CreateANewSafeArray( &sa ); }
可以使用:
HRESULT UseCreatedSafeArray( ) { SAFEARRAY *psa = null; HRESULT hr = CreateANewSafeArray( &psa ); CComSafeArray< int > sa; sa.Attach( psa ); }
也可以:
HRESULT UseCreatedSafeArray( ) { CComSafeArray< int > sa; HRESULT hr = CreateANewSafeArray(sa.GetSafeArrayPtr()); }