WindowsXP-SP1/ds/adsi/router/caccess.cxx

737 lines
25 KiB
C++

//-------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1992 - 1995
//
// File: caccess.cxx
//
// Contents: Microsoft OleDB/OleDS Data Source Object for ADSI
//
// CImpIAccessor object implementing the IAccessor interface.
//
// History: 10-01-96 shanksh Created.
//-------------------------------------------------------------------------
#include "oleds.hxx"
#pragma hdrstop
//-------------------------------------------------------------------------
// CImpIAccessor::CImpIAccessor
//
// CImpIAccessor constructor.
//
//-------------------------------------------------------------------------
CImpIAccessor::CImpIAccessor (
void *pParentObj, //@parm IN | Parent object pointer
LPUNKNOWN pUnkOuter //@parm IN | Outer Unknown pointer
)
{
// Initialize simple member vars
_pUnkOuter = pUnkOuter;
_pObj = pParentObj;
_pextbuffer = NULL;
_dwStatus = 0;
_pIDC = NULL;
ENLIST_TRACKING(CImpIAccessor);
// This section is for serializing &CreateAccessor.
InitializeCriticalSection(&_criticalsectionAccessor);
return;
}
//-------------------------------------------------------------------------
// CImpIAccessor::~CImpIAccessor
//
// @mfunc CImpIAccessor destructor.
//
// @rdesc NONE
//-------------------------------------------------------------------------
CImpIAccessor::~CImpIAccessor (
void
)
{
ULONG_PTR hAccessor, hAccessorLast;
PADSACCESSOR pADsaccessor, pADsaccessorBadHandle;
// Cleanup only if there's anything to clean.
if( _pextbuffer ) {
// Get a pointer to the special "BadHandle" accessor.
_pextbuffer->GetItemOfExtBuffer(hAccessorBadHandle, &pADsaccessorBadHandle);
// Get the number of available accessor handles.
_pextbuffer->GetLastItemHandle(hAccessorLast);
// Loop through the reminding accessor handles deleting those which
// are referenced only by this object and refcount decrementing the
// other ones
for (hAccessor =1; hAccessor <=hAccessorLast; hAccessor++) {
// Get a pointer to the next accessor.
_pextbuffer->GetItemOfExtBuffer(hAccessor, &pADsaccessor);
// For valid accessors just delete them.
if( pADsaccessor != pADsaccessorBadHandle )
DeleteADsAccessor(pADsaccessor);
}
// Now delete the buffer which holds accessor handles.
delete _pextbuffer;
}
if( _pIMalloc )
_pIMalloc->Release();
if( _pIDC )
_pIDC->Release();
// Get rid of the critical section.
DeleteCriticalSection(&_criticalsectionAccessor);
return;
}
//-------------------------------------------------------------------------
// CImpIAccessor::FInit
//
// @mfunc Initialize the Accessor implementation object.
//
// @rdesc Did the Initialization Succeed
// @flag S_OK initialization succeeded,
// @flag E_OUTOFMEMORY initialization failed because of
// memory allocation problem,
//-------------------------------------------------------------------------
STDMETHODIMP
CImpIAccessor::FInit (
void
)
{
HRESULT hr;
PADSACCESSOR pADsaccessorBadHandle;
// Prepare a special, "Bad Handle" accessor.
pADsaccessorBadHandle = (PADSACCESSOR)((BYTE *)&_dwGetDataTypeBadHandle);
_dwGetDataTypeBadHandle = ACCESSORTYPE_INERROR;
// Create the ExtBuffer array to hold pointers to malloc'd accessors.
_pextbuffer = (LPEXTBUFF) new CExtBuff;
if (_pextbuffer == NULL ||
!(_pextbuffer->FInit(
sizeof(PADSACCESSOR),
&pADsaccessorBadHandle
) ))
RRETURN( E_OUTOFMEMORY );
if( !_pIDC ) {
hr = CoCreateInstance(
CLSID_OLEDB_CONVERSIONLIBRARY,
NULL,
CLSCTX_INPROC_SERVER,
IID_IDataConvert,
(void **)&_pIDC
);
if ( FAILED(hr) ) {
RRETURN (hr);
}
}
hr = CoGetMalloc(MEMCTX_TASK, &_pIMalloc);
RRETURN (hr);
}
//-------------------------------------------------------------------------
// CImpIAccessor::AddRefAccessor
//
// @mfunc Adds a reference count to an existing accessor.
//
// @rdesc Did release of the accessor Succeed
// @flag S_OK | accessor addrefed successfully,
// @flag DB_E_BADACCESSORHANDLE | accessor could not be addrefed
// because its handle was invalid.
//-------------------------------------------------------------------------
STDMETHODIMP CImpIAccessor::AddRefAccessor(
HACCESSOR hAccessor, //@parm IN | handle of the accessor to release
DBREFCOUNT *pcRefCounts //@parm OUT | count of references
)
{
PADSACCESSOR pADsaccessor, pADsaccessorBadHandle;
HRESULT hr;
// Clear in case of error below.
if( pcRefCounts )
*pcRefCounts = 0;
CAutoBlock cab( &(_criticalsectionAccessor) );
// Get a pointer to the accessor.
_pextbuffer->GetItemOfExtBuffer(hAccessor, &pADsaccessor);
// Get a pointer to the special "BadHandle" accessor.
_pextbuffer->GetItemOfExtBuffer(hAccessorBadHandle, &pADsaccessorBadHandle);
// If the handle is valid
if (pADsaccessor != pADsaccessorBadHandle) {
if (pADsaccessor->cRef <= 0)
BAIL_ON_FAILURE( hr = E_FAIL );
pADsaccessor->cRef++;
if (pcRefCounts)
*pcRefCounts = pADsaccessor->cRef;
RRETURN( S_OK );
}
// otherwise complain about a bad handle.
else
RRETURN( DB_E_BADACCESSORHANDLE );
error:
RRETURN( hr );
}
//-------------------------------------------------------------------------
// CImpIAccessor::CreateAccessor
//
// @mfunc Creates an accessor, which is a set of bindings that can be used to
// send data to or retrieve data from the data cache. The method performs
// extensive validations on the input bindings which include verification of
// the access type requested, column availability, data conversions involved,
// etc.
//
// @rdesc Returns one of the following values:
// S_OK | creation of the accessor succeeded,
// E_INVALIDARG | creation of the accessor failed because
// phAccessor was a NULL pointer,
// E_OUTOFMEMORY | creation of the accessor failed because of
// memory allocation problem,
// DB_E_CANTCREATEACCESSOR | DBROWSETFLAGS_MULTIPLEACCESSOR flag was not
// set and a row-fetching method has already
// been called.
// OTHER | other result codes returned by called functions.
//-------------------------------------------------------------------------
STDMETHODIMP CImpIAccessor::CreateAccessor(
DBACCESSORFLAGS dwAccessorFlags,
DBCOUNTITEM cBindings,
const DBBINDING rgBindings[],
DBLENGTH cbRowSize,
HACCESSOR *phAccessor,
DBBINDSTATUS rgStatus[]
)
{
PADSACCESSOR pADsaccessor;
ULONG_PTR hADsaccessor;
HRESULT hr;
ULONG dwGetDataType, dwSetDataType;
DBCOUNTITEM ibind;
BOOL fProviderOwned;
DBBINDSTATUS bindstatus;
ULONG wMask, wBaseType;
if (phAccessor)
*phAccessor = (HACCESSOR)0;
// At the creation of the CAutoBlock object a critical section
// is entered. Concurrent calls to CreateAccessor are entirely
// legal but their interleaving cannot be allowed. The critical
// section is left when this method terminate and the destructor
// for CAutoBlock is called.
CAutoBlock cab( &(_criticalsectionAccessor) );
// phAccessor must never be NULL, non-zero bindings count implies a
// non-NULL array of bindings.
if( phAccessor == NULL || (cBindings && rgBindings == NULL) )
RRETURN( E_INVALIDARG );
// cBindings is 0 and was called on the Command.
if( cBindings == 0 )
RRETURN( DB_E_NULLACCESSORNOTSUPPORTED );
// dwAccessorFlags cannot take any other values than those legal.
if( dwAccessorFlags & (~DBACCESSOR_VALID_FLAGS) )
RRETURN( DB_E_BADACCESSORFLAGS );
// Compute internal types for use by methods using accessors,
DetermineTypes(
dwAccessorFlags,
&dwGetDataType,
&dwSetDataType
);
// and detect possible inconsistencies in AccessorFlags.
if( dwGetDataType == GD_ACCESSORTYPE_INERROR ||
dwSetDataType == SD_ACCESSORTYPE_INERROR )
RRETURN( DB_E_BADACCESSORFLAGS );
// fix conformance test failure. If BYREF accessors are not supported,
// (indicated by DBPROP_BYREFACCESSORS being FALSE), we should return
// the right error
else if( (DB_E_BYREFACCESSORNOTSUPPORTED == dwGetDataType) ||
(DB_E_BYREFACCESSORNOTSUPPORTED == dwSetDataType) )
RRETURN( DB_E_BYREFACCESSORNOTSUPPORTED );
// Initialize the status array to DBBINDSTATUS_OK.
if( rgStatus )
memset(rgStatus, 0x00, (size_t)(cBindings*sizeof(DBBINDSTATUS)));
hr = S_OK;
fProviderOwned = FALSE;
// Perform validations that apply to all types of accessors.
for (ibind=0; ibind < cBindings; ibind++) {
bindstatus = DBBINDSTATUS_OK;
if( rgBindings[ibind].dwMemOwner != DBMEMOWNER_PROVIDEROWNED &&
rgBindings[ibind].dwMemOwner != DBMEMOWNER_CLIENTOWNED )
bindstatus = DBBINDSTATUS_BADBINDINFO;
// The part to be bound must specify one or more out of three values, and
if( (rgBindings[ibind].dwPart &
(DBPART_VALUE |DBPART_LENGTH |DBPART_STATUS)) == 0 ||
// nothing else.
(rgBindings[ibind].dwPart &
~(DBPART_VALUE |DBPART_LENGTH |DBPART_STATUS)) ||
// Is it a good type to bind to?
!IsGoodBindingType(rgBindings[ibind].wType) )
bindstatus = DBBINDSTATUS_BADBINDINFO;
// DBTYPE_BYREF, DBTYPE_VECTOR and DBTYPE_ARRAY cannot be combined.
else if( (wMask = (rgBindings[ibind].wType & TYPE_MODIFIERS)) &&
wMask != DBTYPE_BYREF &&
wMask != DBTYPE_VECTOR &&
wMask != DBTYPE_ARRAY )
bindstatus = DBBINDSTATUS_BADBINDINFO;
else if ((wBaseType = (rgBindings[ibind].wType & ~TYPE_MODIFIERS)) ==
DBTYPE_EMPTY ||
wBaseType == DBTYPE_NULL)
bindstatus = DBBINDSTATUS_BADBINDINFO;
// DBTYPE_ARRAY and DBTYPE_VECTOR are not supported types
else if( (rgBindings[ibind].wType & DBTYPE_ARRAY) ||
(rgBindings[ibind].wType & DBTYPE_VECTOR) )
bindstatus = DBBINDSTATUS_UNSUPPORTEDCONVERSION;
// dwFlags was DBBINDFLAG_HTML and the type was not a String
else if ( rgBindings[ibind].dwFlags &&
(rgBindings[ibind].dwFlags != DBBINDFLAG_HTML ||
(wBaseType != DBTYPE_STR &&
wBaseType != DBTYPE_WSTR &&
wBaseType != DBTYPE_BSTR)) )
{
// Set Bind status to DBBINDSTATUS_BADBINDINFO
bindstatus = DBBINDSTATUS_BADBINDINFO;
}
else if( rgBindings[ibind].wType == (DBTYPE_RESERVED | DBTYPE_BYREF) )
bindstatus = DBBINDSTATUS_BADBINDINFO;
else if( rgBindings[ibind].dwMemOwner == DBMEMOWNER_PROVIDEROWNED ) {
if( (rgBindings[ibind].wType & TYPE_MODIFIERS) == 0
&& rgBindings[ibind].wType != DBTYPE_BSTR )
bindstatus = DBBINDSTATUS_BADBINDINFO;
else
fProviderOwned = TRUE;
}
if( bindstatus != DBBINDSTATUS_OK )
hr = DB_E_ERRORSOCCURRED;
if( rgStatus )
rgStatus[ibind] = bindstatus;
}
// Check for errors in the bindings
BAIL_ON_FAILURE( hr );
if( fProviderOwned &&
(dwGetDataType == GD_ACCESSORTYPE_READ ||
dwGetDataType == GD_ACCESSORTYPE_READ_OPTIMIZED) )
dwGetDataType = GD_ACCESSORTYPE_READ_COLS_BYREF;
// Allocate space for the accessor structure.
pADsaccessor = (ADSACCESSOR *) new BYTE
[(size_t)(sizeof(ADSACCESSOR) + (cBindings ? (cBindings-1) : 0)*
sizeof(DBBINDING))];
if( pADsaccessor == NULL )
RRETURN( E_OUTOFMEMORY );
if( cBindings )
memcpy(
&(pADsaccessor->rgBindings[0]),
&rgBindings[0],
(size_t)(cBindings*sizeof(DBBINDING))
);
pADsaccessor->cBindings = cBindings;
// For accessors valid for writing fill out a list of columns
// so that notifications about their changes can be properly issued.
if( dwSetDataType == SD_ACCESSORTYPE_READWRITE && cBindings ) {
pADsaccessor->rgcol = new DBORDINAL [ (size_t)cBindings ];
if( pADsaccessor->rgcol == NULL ) {
dwSetDataType = SD_ACCESSORTYPE_INERROR;
}
else
for (ibind =0; ibind <cBindings; ibind++)
(pADsaccessor->rgcol)[ibind] = rgBindings[ibind].iOrdinal;
}
else
pADsaccessor->rgcol = NULL;
// Allocate DBOBJECT structures supporting IUnknown types.
for (ibind =0; ibind <cBindings; ibind++)
pADsaccessor->rgBindings[ibind].pObject = NULL;
// Set accessor flags now, so that possible accessor cleanup routine would
// know about additional structures for IUnknown types support.
pADsaccessor->dwFlags = dwAccessorFlags;
// Insert the new accessor pointer into the extensible buffer in which
// accessors are stored.
if( FAILED(hr = _pextbuffer->InsertIntoExtBuffer(
&pADsaccessor,
hADsaccessor
)) ) {
DeleteADsAccessor(pADsaccessor);
RRETURN (hr);
}
pADsaccessor->cRef = 1;
// Fill out the new accessor structure with the binding info.
//pADsaccessor->obRowData = obRowData;
pADsaccessor->cbRowSize = cbRowSize;
// Return the accessor index in the extensible buffer as the accessor
// handle.
*phAccessor = (HACCESSOR) hADsaccessor;
// Now can safely leave.
RRETURN( NOERROR );
error:
RRETURN( hr );
}
//-------------------------------------------------------------------------
// CImpIAccessor::DetermineTypes
//
// @mfunc Determines internal accessor type classification for use by GetData,
// SetData and parameter handling code. Each of these has a separate type indicator
// variable and a separate set of defined types. This allows a very efficient
// handling of different accessors by methods that utilize them.
// Types are determined on the basis of AccessorFlags. Incorrect combinations of
// flags result in assignment of INERROR types.
//
// @rdesc NONE
//-------------------------------------------------------------------------
STDMETHODIMP_(void)
CImpIAccessor::DetermineTypes(
DBACCESSORFLAGS dwAccessorFlags,
ULONG *pdwGetDataType,
ULONG *pdwSetDataType
)
{
if( dwAccessorFlags & DBACCESSOR_PASSBYREF )
{
*pdwGetDataType = (ULONG) DB_E_BYREFACCESSORNOTSUPPORTED;
*pdwSetDataType = (ULONG) DB_E_BYREFACCESSORNOTSUPPORTED;
return;
}
else if( dwAccessorFlags & DBACCESSOR_PARAMETERDATA )
{
*pdwGetDataType = GD_ACCESSORTYPE_INERROR;
*pdwSetDataType = GD_ACCESSORTYPE_INERROR;
return;
}
// Determine types used in row data manipulations.
if( dwAccessorFlags & DBACCESSOR_ROWDATA ) {
// Determine accessor type from the point of
// view of GetData.
if( dwAccessorFlags & DBACCESSOR_OPTIMIZED )
*pdwGetDataType = GD_ACCESSORTYPE_READ_OPTIMIZED;
else
*pdwGetDataType = GD_ACCESSORTYPE_READ;
// Determine accessor type from the point of
// view of SetData. PASSBYREF is disallowed.
*pdwSetDataType = SD_ACCESSORTYPE_READWRITE;
}
else {
*pdwGetDataType = GD_ACCESSORTYPE_INERROR;
*pdwSetDataType = GD_ACCESSORTYPE_INERROR;
}
return;
}
//-------------------------------------------------------------------------
// CImpIAccessor::GetBindings
//
// @mfunc Returns bindings of an accessor.
//
// @rdesc Returns one of the following values:
// S_OK | getting bindings succeeded,
// E_INVALIDARG | getting bindings failed because
// pdwAccessorFlags or pcBindings or
// prgBindings was a NULL pointer,
// E_OUTOFMEMORY | getting bindings failed because memory
// allocation for the bindings array failed,
// OTHER | other result codes stored on the accessor
// object and singifying invalid accessor handle.
//-------------------------------------------------------------------------
STDMETHODIMP CImpIAccessor::GetBindings
(
HACCESSOR hAccessor, // IN | accessor handle
DBACCESSORFLAGS *pdwAccessorFlags, // OUT | stores accessor flags
DBCOUNTITEM *pcBindings, // OUT | stores # of bindings
DBBINDING **prgBindings // OUT | stores array of bindings
)
{
PADSACCESSOR pADsaccessor;
PADSACCESSOR pADsaccessorBadHandle;
DBCOUNTITEM ibind;
DBOBJECT *pObject;
if( pcBindings )
*pcBindings = 0;
if( prgBindings )
*prgBindings = NULL;
if( pdwAccessorFlags )
*pdwAccessorFlags = DBACCESSOR_INVALID;
// Are arguments valid?
if( pdwAccessorFlags == NULL || pcBindings == NULL || prgBindings == NULL )
RRETURN( E_INVALIDARG );
// Obtain pointer to the accessor to be used.
_pextbuffer->GetItemOfExtBuffer(hAccessor, &pADsaccessor);
// Get a pointer to the special "BadHandle" accessor.
_pextbuffer->GetItemOfExtBuffer(hAccessorBadHandle, &pADsaccessorBadHandle);
// Recognize bad accessor handles and return appropriate HRESULT.
if( pADsaccessor == pADsaccessorBadHandle )
return DB_E_BADACCESSORHANDLE;
// If necessary allocate the array of binding structures.
if( pADsaccessor->cBindings ) {
*prgBindings = (DBBINDING *) _pIMalloc->Alloc((ULONG)(
pADsaccessor->cBindings*sizeof(DBBINDING) ));
if( *prgBindings == NULL )
RRETURN ( E_OUTOFMEMORY );
// Copy bindings.
memcpy(
*prgBindings,
pADsaccessor->rgBindings,
(size_t)(pADsaccessor->cBindings*sizeof(DBBINDING))
);
// Loop through bindings and allocate and copy DBOBJECT structs.
for (ibind=0; ibind <pADsaccessor->cBindings; ibind++) {
// If the accessor had failed bindings for SetData, we have
// overloaded an unused structure member with status info, and
// this needs to be cleaned now.
(*prgBindings)[ibind].pTypeInfo = NULL;
if( (*prgBindings)[ibind].pObject ) {
pObject = (DBOBJECT *) _pIMalloc->Alloc( sizeof(DBOBJECT) );
if( pObject ) {
memcpy(
pObject,
(*prgBindings)[ibind].pObject,
sizeof(DBOBJECT)
);
(*prgBindings)[ibind].pObject = pObject;
}
else {
while (ibind--)
if( (*prgBindings)[ibind].pObject )
_pIMalloc->Free((*prgBindings)[ibind].pObject);
_pIMalloc->Free( *prgBindings );
*prgBindings = NULL;
RRETURN( E_OUTOFMEMORY );
}
}
}
}
// Return the count of bindings,
*pcBindings = pADsaccessor->cBindings;
// and accessor flags.
*pdwAccessorFlags = (pADsaccessor->dwFlags & ~DBACCESSOR_REFERENCES_BLOB);
return S_OK;
}
//-------------------------------------------------------------------------
// CImpIAccessor::ReleaseAccessor
//
// @mfunc Releases accessor. If accessor handle is valid the corresponding accessor
// is either freed (if no one else is using it) or its ref count is decremented.
//
// @rdesc Did release of the accessor Succeed
// @flag S_OK | accessor released successfully,
// @flag DB_E_BADACCESSORHANDLE | accessor could not be released because its
// | handle was invalid.
//-------------------------------------------------------------------------
STDMETHODIMP CImpIAccessor::ReleaseAccessor
(
HACCESSOR hAccessor, //@parm IN | handle of the accessor to release
DBREFCOUNT *pcRefCounts //@parm OUT | ref count of the released accessor
)
{
PADSACCESSOR pADsaccessor, pADsaccessorBadHandle;
if( pcRefCounts )
*pcRefCounts = 0;
CAutoBlock cab( &(_criticalsectionAccessor) );
// Get a pointer to the accessor.
_pextbuffer->GetItemOfExtBuffer(hAccessor, &pADsaccessor);
// Get a pointer to the special "BadHandle" accessor.
_pextbuffer->GetItemOfExtBuffer(hAccessorBadHandle, &pADsaccessorBadHandle);
// If the handle is valid
if( pADsaccessor != pADsaccessorBadHandle ) {
ADsAssert(pADsaccessor->cRef > 0);
// Delete if no one else is using it, otherwise
if( pADsaccessor->cRef == 1 ) {
// Delete the accessor structure itself, as well as allocations
// hanging off this structure.
DeleteADsAccessor(pADsaccessor);
// Make sure this handle is marked as "Bad" for future.
_pextbuffer->DeleteFromExtBuffer(hAccessor);
}
// decrement the refcount.
else {
pADsaccessor->cRef--;
if( pcRefCounts )
*pcRefCounts = pADsaccessor->cRef;
}
return NOERROR;
}
// otherwise complain about a bad handle.
else
RRETURN( DB_E_BADACCESSORHANDLE );
}
//-------------------------------------------------------------------------
// CImpIAccessor::DeleteADsAccessor
//
// @mfunc Deletes structures hanging off the ADSACCESSOR and then deletes the
// accessor structure itself.
//
// @rdesc NONE
//-------------------------------------------------------------------------
STDMETHODIMP_(void) CImpIAccessor::DeleteADsAccessor
(
PADSACCESSOR pADsaccessor //@parm IN | Kagera accessor ptr
)
{
DBCOUNTITEM ibind;
// Delete the list of affected columns.
if( pADsaccessor->rgcol )
delete [] pADsaccessor->rgcol;
// If the accessor references BLOBS then DBOBJECT structures describing
// objects dealing with BLOBS need to be deallocated.
for (ibind =0; ibind <pADsaccessor->cBindings; ibind++)
if( pADsaccessor->rgBindings[ibind].pObject )
delete pADsaccessor->rgBindings[ibind].pObject;
delete [] pADsaccessor;
}
STDMETHODIMP
CImpIAccessor::QueryInterface(REFIID iid, LPVOID FAR* ppv)
{
// Is the pointer bad?
if( ppv == NULL )
RRETURN( E_INVALIDARG );
// Place NULL in *ppv in case of failure
*ppv = NULL;
if( IsEqualIID(iid, IID_IUnknown) ) {
*ppv = (IAccessor FAR *) this;
}
else if( IsEqualIID(iid, IID_IAccessor) ) {
*ppv = (IAccessor FAR *) this;
}
else {
RRETURN( E_NOINTERFACE );
}
AddRef();
return S_OK;
}
//---------------------------------------------------------------------------
// CreateBadAccessor
//
// Inserts a bad accessor into the array of accessors (indexed by accessor
// handles). This is required so that inheritance of accessors from commands
// works correctly. If there are any 'holes' in the command's array of
// accessors, then a rowset created from the command should also inherit these
// 'holes' i,e, the array of accessor handles should not be compacted to
// eliminate these holes. This is done using this function.
//
//---------------------------------------------------------------------------
HRESULT
CImpIAccessor::CreateBadAccessor(void)
{
PADSACCESSOR pADsaccessorBadHandle;
ULONG_PTR hADsaccessor;
HRESULT hr;
// Get a pointer to the special "BadHandle" accessor.
_pextbuffer->GetItemOfExtBuffer(hAccessorBadHandle, &pADsaccessorBadHandle);
// ignore the returned accessor handle
hr = _pextbuffer->InsertIntoExtBuffer(
&pADsaccessorBadHandle, hADsaccessor);
RRETURN( hr );
}
//---------------------------------------------------------------------------