Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2039 lines
51 KiB

//+-------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1992 - 1993.
//
// File: propstg.cxx
//
// Contents: Class that directly implements IPropertyStorage
//
// Classes: CCoTaskAllocator
// CPropertyStorage
//
// Notes: For methods that state 'if successful returns S_OK,
// otherwise error code', the possible error codes include:
//
// STG_E_INVALIDHANDLE
// STG_E_INSUFFICIENTMEMORY
// STG_E_MEDIUMFULL
// STG_E_REVERTED
// STG_E_INVALIDPARAMETER
// STG_E_INVALIDFLAG
//
//--------------------------------------------------------------------------
#include "pch.cxx"
DECLARE_INFOLEVEL(prop, DEB_ERROR)
//+-------------------------------------------------------------------
//
// Member: CCoTaskAllocator::Allocate, Free.
//
// Synopsis: Allocation routines called by RtlQueryProperties.
//
// Notes:
//
//--------------------------------------------------------------------
CCoTaskAllocator g_CoTaskAllocator;
void *
CCoTaskAllocator::Allocate(ULONG cbSize)
{
return(CoTaskMemAlloc(cbSize));
}
void
CCoTaskAllocator::Free(void *pv)
{
CoTaskMemFree(pv);
}
//+-------------------------------------------------------------------
//
// Member: CPropertyStorage::CPropertyStorage
//
// Synopsis: Constructor used to create a property storage on disk.
//
// Arguments: [pstg] -- storage object to be parent
// [rfmtid] -- format id for new property set.
// [pclsid] -- pointer to class id
// [grfFlags] -- flags
// [grfMode] -- mode
// [phr] -- HRESULT assumed to be STG_E_INSUFFICIENTMEMORY
// on entry. Will be HRESULT reflecting result on exit.
//
// Notes: Get a CPropertySetStream initialized with the correct
// type of map (i.e. docfile or native.)
// If non-simple mode, create a storage with name derived
// from rfmtid and then a contents sub-stream.
// If simple mode, create a stream of a name derived from
// rfmtid.
//
// Does not clean up on failure: this is done by the
// destructor.
//
//--------------------------------------------------------------------
CPropertyStorage::CPropertyStorage(
IPrivateStorage *pprivstg,
REFFMTID rfmtid,
const CLSID *pclsid,
DWORD grfFlags,
DWORD grfMode,
HRESULT *phr)
{
HRESULT & hr = *phr;
CPropSetName psn(rfmtid); // acts as Probe(&rfmtid, sizeof(rfmtid));
BOOL fCreated = FALSE;
IStorage *pstg = pprivstg->GetStorage();
Initialize();
if (grfFlags & PROPSETFLAG_NONSIMPLE)
{
hr = STG_E_UNIMPLEMENTEDFUNCTION;
PROPASSERT(FALSE && "Unsupported function in reference called!\n" );
return;
}
if (grfFlags & ~PROPSETFLAG_ANSI)
{
hr = STG_E_INVALIDFLAG;
return;
}
// check for any mode flags disallowed in Create.
if (grfMode & (STGM_PRIORITY | STGM_CONVERT |
STGM_SIMPLE | STGM_DELETEONRELEASE))
{
hr = STG_E_INVALIDFLAG;
return;
}
_grfFlags = grfFlags;
_grfAccess = 3 & grfMode;
_grfShare = 0xF0 & grfMode;
// Is this the special-case second-section property set?
_fUserDefinedProperties = ( rfmtid == FMTID_UserDefinedProperties ) ? TRUE : FALSE;
if (_grfAccess != STGM_READWRITE)
{
hr = STG_E_INVALIDFLAG;
return;
}
if (_grfFlags & PROPSETFLAG_ANSI)
{
_usCodePage = GetACP();
}
int i=0;
while (i<=1)
{
// Create the property set stream in pstg.
// The second section of the DocumentSummaryInformation Property Set
// is a special-case.
if( IsEqualGUID( rfmtid, FMTID_UserDefinedProperties ))
{
hr = _CreateDocumentSummary2Stream( pstg, psn, grfMode, &fCreated );
}
else
{
hr = pstg->CreateStream(psn.GetPropSetName(), grfMode, 0, 0, &_pstmPropSet);
if( hr == S_OK )
fCreated = TRUE;
}
if (hr == S_OK)
{
break;
}
else
{
PropDbg((DEB_PROP_TRACE_CREATE, "CPropertyStorage(%08X)::CPropertyStorage"
" - CreateStream(%ls) attempt %d, hr=%08X\n", this, psn.GetPropSetName(), i+1, hr));
if (hr != STG_E_FILEALREADYEXISTS)
{
break;
}
else
if (i == 0 && (grfMode & STGM_CREATE) == STGM_CREATE)
{
pstg->DestroyElement(psn.GetPropSetName());
}
} // if (hr == S_OK) ... else
i++;
}
if (hr == S_OK)
{
hr = InitializePropertyStream(CREATEPROP_CREATE,
&rfmtid,
pclsid);
}
if (hr != S_OK && fCreated)
{
//
// if we fail after creating the property set in storage, cleanup.
//
pstg->DestroyElement(psn.GetPropSetName());
}
}
//+-------------------------------------------------------------------
//
// Member: CPropertyStorage::_CreateDocumentSummary2Stream
//
// Synopsis: Open the "DocumentSummaryInformation" stream, creating
// it if necessary.
//
// Arguments: [pstg] -- container storage
// [psn] -- the property set name
// [grfMode] -- mode of the property set
// [fCreated] -- TRUE if Stream is created, FALSE if opened.
//
// Notes: This special case is necessary because this property set
// is the only one in which we support more than one section.
// For this property set, if the caller Creates the second
// Section, we must not *Create* the Stream, because that would
// lost the first Section. So, we must open it.
//
// This routine is only called when creating the second
// Section. The first Section is created normally (note
// that if the client creates the first section, the second
// section is lost).
//
// Also note that it may not be possible to open the Stream,
// since it may already be opened. This is significant
// because it may not be obvious to the caller. I.e.,
// to a client of IPropertyStorage, the 2 sections are
// distinct property sets, and you would think that you could
// open them for simultaneous write.
//
//--------------------------------------------------------------------
HRESULT
CPropertyStorage::_CreateDocumentSummary2Stream( IStorage * pstg,
CPropSetName & psn,
DWORD grfMode,
BOOL * pfCreated )
{
HRESULT hr;
DWORD grfOpenMode = grfMode & ~(STGM_CREATE | STGM_CONVERT);
*pfCreated = FALSE;
hr = pstg->OpenStream( psn.GetPropSetName(), NULL, grfOpenMode, 0L, &_pstmPropSet );
// If the file wasn't there, try a create.
if( hr == STG_E_FILENOTFOUND )
{
hr = pstg->CreateStream(psn.GetPropSetName(), grfMode, 0, 0, &_pstmPropSet);
if( SUCCEEDED( hr ))
{
*pfCreated = TRUE;
}
}
return( hr );
} // CPropertyStorage::_CreateDocumentSummary2Stream()
//+-------------------------------------------------------------------
//
// Member: CPropertyStorage::CPropertyStorage
//
// Synopsis: Constructor used to open a property storage on disk.
//
// Arguments: [pstg] -- container storage
// [rfmtid] -- FMTID of property set to open
// [grfMode] -- mode of the property set
// [fDelete] -- Delete this property set from its stream
// [phr] -- HRESULT returned here.
//
// Notes: Does not clean up on failure: this is done by the
// destructor.
//
//--------------------------------------------------------------------
CPropertyStorage::CPropertyStorage(
IPrivateStorage *pprivstg,
REFFMTID rfmtid,
DWORD grfMode,
BOOL fDelete,
HRESULT *phr)
{
HRESULT &hr = *phr;
CPropSetName psn(rfmtid);
IStorage *pstgParent;
IStorage *pstg = pprivstg->GetStorage();
USHORT createprop = 0L;
Initialize();
_grfAccess = 3 & grfMode;
_grfShare = 0xF0 & grfMode;
// Is this the special-case second-section property set?
_fUserDefinedProperties = ( rfmtid == FMTID_UserDefinedProperties ) ? TRUE : FALSE;
// check for any mode flags disallowed in Open.
if (grfMode & (STGM_CREATE | STGM_PRIORITY | STGM_CONVERT | STGM_TRANSACTED |
STGM_SIMPLE | STGM_DELETEONRELEASE))
{
hr = STG_E_INVALIDFLAG;
return;
}
hr = pstg->OpenStream(psn.GetPropSetName(), NULL, _grfAccess | _grfShare,
0, &_pstmPropSet);
if (hr == S_OK)
{
pstgParent = pstg;
}
// Determine the CREATEPROP flags.
if( fDelete )
{
createprop = CREATEPROP_DELETE;
}
else
{
createprop = (S_OK == IsWriteable() ?
CREATEPROP_WRITE : CREATEPROP_READ);
}
if (hr == S_OK)
{
// sets up _usCodePage
hr = InitializePropertyStream(
createprop,
&rfmtid,
NULL);
}
}
//+-------------------------------------------------------------------
//
// Member: CPropertyStorage::Initialize
//
// Synopsis: Initialize members to known values.
//
//--------------------------------------------------------------------
VOID CPropertyStorage::Initialize(VOID)
{
_ulSig = PROPERTYSTORAGE_SIG;
_cRefs = 1;
_pstgPropSet = NULL;
_pstmPropSet = NULL;
_dwOSVersion = PROPSETHDR_OSVERSION_UNKNOWN;
_np = NULL;
_ms = NULL;
_usCodePage = CP_WINUNICODE;
_grfFlags = 0;
_grfAccess = 0;
_grfShare = 0;
_fUserDefinedProperties = FALSE;
}
//+-------------------------------------------------------------------
//
// Member: CPropertyStorage::InitializePropertyStream.
//
// Synopsis: Initialize the storage-type specific members.
//
// Arguments: [Flags] -- Flags for RtlCreatePropertySet: CREATEPROP_*
// [pguid] -- FMTID, in for create only.
// [pclsid] -- Class id, in for create only.
//
// Returns: HRESULT
//
// Requires:
// _pstmPropSet -- The IStream of the main property set stream.
//
// Modifies: _ms (NTMAPPEDSTREAM)
//
// (assumed NULL on entry) will be NULL or valid on exit
//
// if _fNative, then _ms is CNtMappedStream*
// if !_fNative, then _ms is CMappedStream* of CExposedStream
//
// _np (NTPROP) aka CPropertySetStream
//
// (assumed NULL on entry) will be NULL or valid on exit
//
// Notes:
//
//--------------------------------------------------------------------
HRESULT
CPropertyStorage::InitializePropertyStream(
USHORT Flags,
const GUID *pguid,
GUID const *pclsid)
{
HRESULT hr;
CExposedStream *pexpstm = (CExposedStream*)_pstmPropSet;
PROPASSERT(pexpstm->Validate() != STG_E_INVALIDHANDLE );
_ms = (CMappedStream*)pexpstm;
hr = S_OK;
PropDbg((DEB_PROP_TRACE_CREATE, "CPropertyStorage(%08X)::InitializePropertyStream"
" - using CExposedDocfile as CMappedStream\n", this));
NTSTATUS Status;
Status = RtlCreatePropertySet(
_ms,
Flags,
pguid,
pclsid,
(NTMEMORYALLOCATOR) & g_CoTaskAllocator,
GetUserDefaultLCID(),
&_dwOSVersion,
&_usCodePage,
&_np);
if (!NT_SUCCESS(Status))
{
PropDbg((DEB_PROP_TRACE_CREATE, "CPropertyStorage(%08X)::InitializePropertyStream"
" - RtlCreatePropertySet Status=%08X\n", this, Status));
}
if (NT_SUCCESS(Status))
{
if (_usCodePage != CP_WINUNICODE)
_grfFlags |= PROPSETFLAG_ANSI; // for Stat
}
else
{
hr = DfpNtStatusToHResult(Status);
}
return(hr);
}
//+-------------------------------------------------------------------
//
// Member: CPropertyStorage::~CPropertyStorage
//
// Synopsis: Free up object resources.
//
// Notes: Cleans up even from partial construction.
//
//--------------------------------------------------------------------
CPropertyStorage::~CPropertyStorage()
{
_ulSig = PROPERTYSTORAGE_SIGDEL; // prevent someone else deleting it
if (_np != NULL)
{
// RtlFlushPropertySet(_np);
RtlClosePropertySet(_np);
}
if (_pstmPropSet != NULL)
_pstmPropSet->Release();
if (_pstgPropSet != NULL)
_pstgPropSet->Release();
}
//+-------------------------------------------------------------------
//
// Member: CPropertyStorage::QueryInterface, AddRef, Release
//
// Synopsis: IUnknown members
//
// Notes: IPropertyStorage supports IPropertyStorage and IUnknown
//
//--------------------------------------------------------------------
HRESULT CPropertyStorage::QueryInterface( REFIID riid, void **ppvObject)
{
HRESULT hr;
// ----------
// Validation
// ----------
// Validate 'this'
if (S_OK != (hr = ValidateRef()))
return(hr);
// Validate the inputs
VDATEREADPTRIN( &riid, IID );
VDATEPTROUT( ppvObject, void* );
// -----------------
// Perform the Query
// -----------------
*ppvObject = NULL;
if (IsEqualIID(riid,IID_IPropertyStorage) || IsEqualIID(riid,IID_IUnknown))
{
*ppvObject = (IPropertyStorage *)this;
CPropertyStorage::AddRef();
}
else
{
hr = E_NOINTERFACE;
}
return(hr);
}
ULONG CPropertyStorage::AddRef(void)
{
if (S_OK != ValidateRef())
return(0);
InterlockedIncrement(&_cRefs);
return(_cRefs);
}
ULONG CPropertyStorage::Release(void)
{
LONG lRet;
if (S_OK != ValidateRef())
return(0);
lRet = InterlockedDecrement(&_cRefs);
if (lRet == 0)
{
delete this; // this will do a flush if dirty
}
else
if (lRet <0)
{
lRet = 0;
}
return(lRet);
}
//+-------------------------------------------------------------------
//
// Member: CPropertyStorage::ReadMultiple
//
// Synopsis: Read properties from the property set.
//
// Arguments: [cpspec] -- Count of PROPSPECs in [rgpspec]
// [rgpspec] -- Array of PROPSPECs
// [rgpropvar] -- Array of PROPVARIANTs to be filled in
// with callee allocated data.
//
// Returns: S_FALSE if none found
// S_OK if >=1 found
// FAILED(hr) otherwise.
//
//--------------------------------------------------------------------
HRESULT CPropertyStorage::ReadMultiple(
ULONG cpspec,
const PROPSPEC rgpspec[],
PROPVARIANT rgpropvar[])
{
NTSTATUS Status;
HRESULT hr;
ULONG cpropFound;
// ----------
// Validation
// ----------
// Validate 'this'
if (S_OK != (hr = Validate()))
goto errRet;
if (S_OK != (hr = IsReverted()))
goto errRet;
if (S_OK != (hr = IsReadable()))
goto errRet;
// Validate inputs
if (0 == cpspec)
{
hr = S_FALSE;
goto errRet;
}
if (S_OK != (hr = ValidateRGPROPSPEC( cpspec, rgpspec )))
goto errRet;
if (S_OK != (hr = ValidateOutRGPROPVARIANT( cpspec, rgpropvar )))
goto errRet;
// -------------------
// Read the Properties
// -------------------
Status = RtlQueryProperties(
_np,
cpspec,
rgpspec,
NULL, // don't want PROPID's
rgpropvar,
&cpropFound);
if (NT_SUCCESS(Status))
{
if (cpropFound == 0)
{
hr = S_FALSE;
}
}
else
{
hr = DfpNtStatusToHResult(Status);
}
// ----
// Exit
// ----
errRet:
PropDbg((DEB_PROP_EXIT,
"CPropertyStorage(%08X)::ReadMultiple(cpspec=%d, rgpspec=%08X, "
"rgpropvar=%08X) returns %08X\n",
this, cpspec, rgpspec, rgpropvar, hr));
return(hr);
}
//+-------------------------------------------------------------------
//
// Member: CPropertyStorage::_WriteMultiple, private
//
// Synopsis: Write the properties to the property set. Allows
// a NULL rgpropvar pointer for deletion case.
//
// Arguments: [cpspec] -- count of PROPSPECs and PROPVARIANTs in
// [rgpspec] and [rgpropvar]
//
// [rgpspec] -- pointer to array of PROPSPECs
//
// [rgpropvar] -- pointer to array of PROPVARIANTs with
// the values to write.
//
// [propidNameFirst] -- id below which not to assign
// ids for named properties.
//
//
// Returns: S_OK, -- all requested data was written.
// Errors --
//
// Modifies:
//
// Derivation:
//
// Notes:
// This routine assumes the object has been validated
// and is writeable.
//
//--------------------------------------------------------------------
HRESULT CPropertyStorage::_WriteMultiple(
ULONG cpspec,
const PROPSPEC rgpspec[],
const PROPVARIANT rgpropvar[],
PROPID propidNameFirst)
{
HRESULT hr;
NTSTATUS Status;
CStackPropIdArray spia;
if (S_OK != (hr = spia.Init(cpspec)))
return(hr);
Status = RtlSetProperties(_np, // property set context
cpspec, // property count
propidNameFirst, // first propid for new named props
rgpspec, // array of property specifiers
spia.GetBuf(), // buffer for array of propids
rgpropvar);
if (!NT_SUCCESS(Status))
{
hr = DfpNtStatusToHResult(Status);
}
return(hr);
}
//+-------------------------------------------------------------------
//
// Member: CPropertyStorage::WriteMultiple
//
// Synopsis: Write properties.
//
// Arguments: [cpspec] -- count of PROPSPECs and PROPVARIANTs in
// [rgpspec] and [rgpropvar]
// [rgpspec] -- pointer to array of PROPSPECs
// [rgpropvar] -- pointer to array of PROPVARIANTs with
// the values to write.
// [propidNameFirst] -- id below which not to assign
// ids for named properties.
//
// Returns: S_OK, -- all requested data was written.
// Errors -- accordingly
//
// Notes: Checks that rgpropvar is not NULL, then calls
// _WriteMultiple.
//
//--------------------------------------------------------------------
HRESULT CPropertyStorage::WriteMultiple(
ULONG cpspec,
const PROPSPEC rgpspec[],
const PROPVARIANT rgpropvar[],
PROPID propidNameFirst)
{
HRESULT hr;
// ----------
// Validation
// ----------
// Validate 'this'
if (S_OK != (hr = Validate()))
goto errRet;
if (S_OK != (hr = IsReverted()))
goto errRet;
if (S_OK != (hr = IsWriteable()))
goto errRet;
// Validate the inputs
if (0 == cpspec)
{
hr = S_OK;
goto errRet;
}
if (S_OK != (hr = ValidateRGPROPSPEC( cpspec, rgpspec )))
goto errRet;
if (S_OK != (hr = ValidateInRGPROPVARIANT( cpspec, rgpropvar )))
goto errRet;
// --------------------
// Write the Properties
// --------------------
hr = _WriteMultiple(cpspec, rgpspec, rgpropvar, propidNameFirst);
if (hr == STG_E_INSUFFICIENTMEMORY)
{
hr = S_OK;
for (ULONG i=0; hr == S_OK && i < cpspec; i++)
{
hr = _WriteMultiple(1, rgpspec+i, rgpropvar+i, propidNameFirst);
}
}
// ----
// Exit
// ----
errRet:
PropDbg((DEB_PROP_EXIT,
"CPropertyStorage(%08X)::WriteMultiple(cpspec=%d, rgpspec=%08X, "
"rgpropvar=%08X, propidNameFirst=%d) returns %08X\n",
this, cpspec, rgpspec, rgpropvar, propidNameFirst, hr));
return(hr);
}
//+-------------------------------------------------------------------
//
// Member: CPropertyStorage::DeleteMultiple
//
// Synopsis: Delete properties.
//
// Arguments: [cpspec] -- count of PROPSPECs and PROPVARIANTs in
// [rgpspec] and [rgpropvar]
// [rgpspec] -- pointer to array of PROPSPECs
//
// Returns: S_OK, -- all requested data was deleted.
// Errors --
//
// Notes: Checks that rgpropvar is not NULL, then calls
// _WriteMultiple.
//
//--------------------------------------------------------------------
HRESULT CPropertyStorage::DeleteMultiple(
ULONG cpspec,
const PROPSPEC rgpspec[])
{
HRESULT hr;
// ----------
// Validation
// ----------
// Validate 'this'
if (S_OK != (hr = Validate()))
goto errRet;
if (S_OK != (hr = IsReverted()))
goto errRet;
if (S_OK != (hr = IsWriteable()))
goto errRet;
// Validate the inputs
if (0 == cpspec)
{
hr = S_OK;
goto errRet;
}
if (S_OK != (hr = ValidateRGPROPSPEC( cpspec, rgpspec )))
goto errRet;
// ---------------------
// Delete the Properties
// ---------------------
hr = _WriteMultiple(cpspec, rgpspec, NULL, 2);
if (hr == STG_E_INSUFFICIENTMEMORY)
{
hr = S_OK;
for (ULONG i=0; hr == S_OK && i < cpspec; i++)
{
hr = _WriteMultiple(1, rgpspec+i, NULL, 2);
}
}
// ----
// Exit
// ----
errRet:
PropDbg((DEB_PROP_EXIT,
"CPropertyStorage(%08X)::DeleteMultiple(cpspec=%d, rgpspec=%08X) "
"returns %08X\n",
this, cpspec, rgpspec, hr));
return hr;
}
//+-------------------------------------------------------------------
//
// Member: CPropertyStorage::ReadPropertyNames
//
// Synopsis: Attempt to read names for all identified properties.
//
// Arguments: [cpropid] -- Count of PROPIDs in [rgpropid]
// [rgpropid] -- Pointer to array of [cpropid] PROPIDs
// [rglpstrName] -- Pointer to array of [cpropid] LPOLESTRs
//
// Returns: S_OK -- success, one or more names returned
// S_FALSE -- success, no names returned
// STG_E_INVALIDHEADER -- no propid->name mapping property
// other errors -- STG_E_INSUFFICIENTMEMORY etc
//
//--------------------------------------------------------------------
HRESULT CPropertyStorage::ReadPropertyNames(
ULONG cpropid,
const PROPID rgpropid[],
LPOLESTR rglpwstrName[])
{
HRESULT hr;
NTSTATUS Status;
// --------
// Validate
// --------
// Validate 'this'
if (S_OK != (hr = Validate()))
goto errRet;
if (S_OK != (hr = IsReverted()))
goto errRet;
if (S_OK != (hr = IsReadable()))
goto errRet;
// Validate the inputs
if (0 == cpropid)
{
hr = S_FALSE;
goto errRet;
}
if (S_OK != (hr = ValidateRGPROPID( cpropid, rgpropid )))
goto errRet;
if (S_OK != (hr = ValidateOutRGLPOLESTR( cpropid, rglpwstrName )))
goto errRet;
// --------------
// Read the Names
// --------------
Status = RtlQueryPropertyNames(_np, cpropid, rgpropid, rglpwstrName);
if (Status == STATUS_NOT_FOUND)
hr = STG_E_INVALIDHEADER;
else
if (Status == STATUS_BUFFER_ALL_ZEROS)
hr = S_FALSE;
else
if (!NT_SUCCESS(Status))
hr = DfpNtStatusToHResult(Status);
// ----
// Exit
// ----
errRet:
PropDbg((DEB_PROP_EXIT, "CPropertyStorage(%08X)::ReadPropertyNames(cpropid=%d, rgpropid=%08X, "
"rglpwstrName=%08X) returns %08X\n",
this, cpropid, rgpropid, rglpwstrName, hr));
return hr;
}
//+-------------------------------------------------------------------
//
// Member: CPropertyStorage::_WritePropertyNames
//
// Synopsis: Internal function used by WritePropertyNames and
// DeletePropertyNames.
//
// Arguments: [cpropid] -- Count of PROPIDs in [rgpropid]
// [rgpropid] -- Pointer to array of [cpropid] PROPIDs
// [rglpstrName] -- Pointer to array of [cpropid] LPOLESTRs
//
// Returns: S_OK if successful, otherwise error code.
//
//--------------------------------------------------------------------
HRESULT CPropertyStorage::_WritePropertyNames(
ULONG cpropid,
const PROPID rgpropid[],
const LPOLESTR rglpwstrName[])
{
NTSTATUS Status;
Status = RtlSetPropertyNames(_np, cpropid, rgpropid,
(OLECHAR const* const*) rglpwstrName);
return NT_SUCCESS(Status) ? S_OK : DfpNtStatusToHResult(Status);
}
//+-------------------------------------------------------------------
//
// Member: CPropertyStorage::WritePropertyNames
//
// Synopsis: Attempt to write names for all identified properties.
//
// Arguments: [cpropid] -- Count of PROPIDs in [rgpropid]
// [rgpropid] -- Pointer to array of [cpropid] PROPIDs
// [rglpstrName] -- Pointer to array of [cpropid] LPOLESTRs
//
// Returns: S_OK -- success, otherwise error code.
//
//--------------------------------------------------------------------
HRESULT CPropertyStorage::WritePropertyNames(
ULONG cpropid,
const PROPID rgpropid[],
const LPOLESTR rglpwstrName[])
{
HRESULT hr;
// ----------
// Validation
// ----------
// Validate 'this'
if (S_OK != (hr = Validate()))
goto errRet;
if (S_OK != (hr = IsReverted()))
goto errRet;
if (S_OK != (hr = IsWriteable()))
goto errRet;
// Validate inputs
if (0 == cpropid)
{
hr = S_OK;
goto errRet;
}
if (S_OK != (hr = ValidateRGPROPID( cpropid, rgpropid )))
goto errRet;
if (S_OK != (hr = ValidateInRGLPOLESTR( cpropid, rglpwstrName )))
goto errRet;
// ---------------
// Write the Names
// ---------------
hr = _WritePropertyNames(cpropid, rgpropid, rglpwstrName);
if (hr == STG_E_INSUFFICIENTMEMORY)
{
hr = S_OK;
for (ULONG i=0; hr == S_OK && i < cpropid; i++)
{
hr = _WritePropertyNames(1, rgpropid+i, rglpwstrName+i);
}
}
// ----
// Exit
// ----
errRet:
PropDbg((DEB_PROP_EXIT, "CPropertyStorage(%08X)::WritePropertyNames(cpropid=%d, rgpropid=%08X, "
"rglpwstrName=%08X) returns %08X\n",
this, cpropid, rgpropid, rglpwstrName, hr));
return hr;
}
//+-------------------------------------------------------------------
//
// Member: CPropertyStorage::DeletePropertyNames
//
// Synopsis: Attempt to delete names for all identified properties.
//
// Arguments: [cpropid] -- Count of PROPIDs in [rgpropid]
// [rgpropid] -- Pointer to array of [cpropid] PROPIDs
//
// Returns: S_OK -- success, otherwise error.
//
//--------------------------------------------------------------------
HRESULT CPropertyStorage::DeletePropertyNames(
ULONG cpropid,
const PROPID rgpropid[])
{
HRESULT hr;
// ----------
// Validation
// ----------
// Validate 'this'
if (S_OK != (hr = Validate()))
goto errRet;
if (S_OK != (hr = IsReverted()))
goto errRet;
if (S_OK != (hr = IsWriteable()))
goto errRet;
// Validate the inputs
if( 0 == cpropid )
{
hr = S_OK;
goto errRet;
}
if (S_OK != (hr = ValidateRGPROPID( cpropid, rgpropid )))
goto errRet;
// ----------------
// Delete the Names
// ----------------
hr = _WritePropertyNames(cpropid, rgpropid, NULL);
if (hr == STG_E_INSUFFICIENTMEMORY)
{
hr = S_OK;
for (ULONG i=0; hr == S_OK && i < cpropid; i++)
{
hr = _WritePropertyNames(1, rgpropid+i, NULL);
}
}
// ----
// Exit
// ----
errRet:
PropDbg((DEB_PROP_EXIT, "CPropertyStorage(%08X)::DeletePropertyNames(cpropid=%d, rgpropid=%08X) "
"returns %08X\n",
this, cpropid, rgpropid, hr));
return hr;
}
//+-------------------------------------------------------------------
//
// Member: CPropertyStorage::Commit
//
// Synopsis: Flush and/or commit the property set
//
// Arguments: [grfCommittFlags] -- Commit flags.
//
// Returns: S_OK -- success, otherwise error.
//
// Notes: For both simple and non-simple, this flushes the
// memory image to disk subsystem. In addition,
// for non-simple transacted-mode property sets, this
// performs a commit on the property set.
//
//--------------------------------------------------------------------
HRESULT CPropertyStorage::Commit(DWORD grfCommitFlags)
{
HRESULT hr;
NTSTATUS Status;
// ----------
// Validation
// ----------
// Validate 'this'
if (S_OK != (hr = Validate()))
goto errRet;
if (S_OK != (hr = IsReverted()))
goto errRet;
if (S_OK != (hr = IsWriteable()))
goto errRet;
// Validate the inputs
if (S_OK != (hr = VerifyCommitFlags(grfCommitFlags)))
goto errRet;
// --------------------------
// Commit the PropertyStorage
// --------------------------
Status = RtlFlushPropertySet(_np);
if (!NT_SUCCESS(Status))
{
hr = DfpNtStatusToHResult(Status);
}
// ----
// Exit
// ----
errRet:
PropDbg((DEB_PROP_EXIT, "CPropertyStorage(%08X)::Commit(grfCommitFlags=%08X) "
"returns %08X\n",
this, grfCommitFlags, hr));
return(hr);
}
//+-------------------------------------------------------------------
//
// Member: CPropertyStorage::Revert
//
// Synopsis: For non-simple property sets, revert it.
//
// Returns: S_OK if successful. STG_E_UNIMPLEMENTEDFUNCTION for
// simple property sets.
//
// Notes: For non-simple property sets, call the underlying
// storage's Revert and re-open the 'contents' stream.
//
//--------------------------------------------------------------------
HRESULT CPropertyStorage::Revert()
{
HRESULT hr;
hr = Validate();
PropDbg((DEB_PROP_EXIT, "CPropertyStorage(%08X)::Revert() "
"returns %08X\n",
this, hr));
return(hr);
}
//+-------------------------------------------------------------------
//
// Member: CPropertyStorage::Enum
//
// Synopsis: Create an enumerator over the property set.
//
// Arguments: [ppenum] -- where to return the IEnumSTATPROPSTG *
//
// Returns: S_OK or error.
//
// Notes: The constructor of CEnumSTATPROPSTG creates a
// CStatArray which reads the entire property set and
// which can be shared when IEnumSTATPROPSTG::Clone is
// used.
//
//--------------------------------------------------------------------
HRESULT CPropertyStorage::Enum(IEnumSTATPROPSTG ** ppenum)
{
HRESULT hr;
// ----------
// Validation
// ----------
// Validate 'this'
if (S_OK != (hr = Validate()))
return(hr);
if (S_OK != (hr = IsReadable()))
return(hr);
if (S_OK != (hr = IsReverted()))
return(hr);
// Validate the inputs
VDATEPTROUT( ppenum, IEnumSTATPROPSTG* );
// ----------------------
// Create the Enumeration
// ----------------------
*ppenum = NULL;
hr = STG_E_INSUFFICIENTMEMORY;
*ppenum = new CEnumSTATPROPSTG(_np, &hr);
if (FAILED(hr))
{
delete (CEnumSTATPROPSTG*) *ppenum;
*ppenum = NULL;
}
// ----
// Exit
// ----
return(hr);
}
//+-------------------------------------------------------------------
//
// Member: CPropertyStorage::SetTimes
//
// Synopsis: Set the given times on the underlying storage
//
// Arguments: [pctime] -- creation time
// [patime[ -- access time
// [pmtime] -- modify time
//
// Returns: S_OK or error.
//
// Notes:
// (non-simple only) Only the times supported by the
// underlying docfile implementation are
// supported.
//
//--------------------------------------------------------------------
HRESULT CPropertyStorage::SetTimes(
FILETIME const * pctime,
FILETIME const * patime,
FILETIME const * pmtime)
{
HRESULT hr;
// ----------
// Validation
// ----------
// Validate 'this'
if (S_OK != (hr = Validate()))
goto errRet;
if (S_OK != (hr = IsReverted()))
goto errRet;
if (S_OK != (hr = IsWriteable()))
goto errRet;
// Validate the inputs
VDATEPTRIN_LABEL( pctime, FILETIME, errRet, hr );
VDATEPTRIN_LABEL( patime, FILETIME, errRet, hr );
VDATEPTRIN_LABEL( pmtime, FILETIME, errRet, hr );
// since we only support non-simple, this function does not
// do anything
// ----
// Exit
// ----
errRet:
PropDbg((DEB_PROP_EXIT,
"CPropertyStorage(%08X)::SetTimes("
"pctime=%08X, patime=%08X, pmtime=%08X) returns %08X\n",
this, pctime, patime, pmtime, hr));
return(hr);
}
//+-------------------------------------------------------------------
//
// Member: CPropertyStorage::SetClass
//
// Synopsis: Sets the class of the property set.
//
// Arguments: [clsid] -- class id to set.
//
// Returns: S_OK or error.
//
// Notes: Have clsid set into the property set stream.
//
//--------------------------------------------------------------------
HRESULT CPropertyStorage::SetClass(REFCLSID clsid)
{
HRESULT hr;
NTSTATUS Status;
DBGBUF(buf);
// ----------
// Validation
// ----------
// Validate 'this'
if (S_OK != (hr = Validate()))
goto errRet;
if (S_OK != (hr = IsReverted()))
goto errRet;
if (S_OK != (hr = IsWriteable()))
goto errRet;
// Validate the inputs
GEN_VDATEREADPTRIN_LABEL(&clsid, CLSID, E_INVALIDARG, errRet, hr);
// -------------
// Set the CLSID
// -------------
// Set it in the property set header
Status = RtlSetPropertySetClassId(_np, &clsid);
if (!NT_SUCCESS(Status))
hr = DfpNtStatusToHResult(Status);
// ----
// Exit
// ----
errRet:
if( E_INVALIDARG != hr )
{
PropDbg((DEB_PROP_EXIT, "CPropertyStorage(%08X)::SetClass(clsid=%s) "
"returns %08X\n",
this, DbgFmtId(clsid, buf), hr));
}
else
{
PropDbg((DEB_PROP_EXIT, "CPropertyStorage(%08X)::SetClass(clsid@%08X) "
"returns %08X\n",
this, &clsid, hr));
}
return(hr);
}
//+-------------------------------------------------------------------
//
// Member: CPropertyStorage::Stat
//
// Synopsis: Get STATPROPSETSTG about the property set.
//
// Arguments: [p] -- STATPROPSETSTG *
//
// Returns: S_OK if successful, error otherwise. On failure,
// *p is all zeros.
//
// Notes: See spec. Gets times from underlying storage or stream
// using IStorage or IStream ::Stat.
//
//--------------------------------------------------------------------
HRESULT CPropertyStorage::Stat(STATPROPSETSTG * p)
{
HRESULT hr;
NTSTATUS Status;
// ----------
// Validation
// ----------
// Validate 'this'
if (S_OK != (hr = Validate()))
goto errRet;
if (S_OK != (hr = IsReverted()))
goto errRet;
if (S_OK != (hr = IsReadable()))
goto errRet;
// Validate inputs
VDATEPTROUT_LABEL(p, STATPROPSETSTG, errRet, hr);
// ------------
// Get the Stat
// ------------
ZeroMemory(p, sizeof(*p));
// returns mtime, ansi flag, clsid, fmtid
Status = RtlQueryPropertySet(_np, p);
if (NT_SUCCESS(Status))
{
STATSTG statstg;
hr = S_OK;
hr = _pstmPropSet->Stat(&statstg, STATFLAG_NONAME);
if (hr == S_OK)
{
p->mtime = statstg.mtime;
p->ctime = statstg.ctime;
p->atime = statstg.atime;
p->grfFlags = _grfFlags;
p->dwOSVersion = _dwOSVersion;
}
}
else
{
hr = DfpNtStatusToHResult(Status);
}
if (FAILED(hr))
{
ZeroMemory(p, sizeof(*p));
}
// ----
// Exit
// ----
errRet:
PropDbg((DEB_PROP_EXIT, "CPropertyStorage(%08X)::Stat(STATPROPSETSTG *p = %08X) "
"returns %08X\n",
this, p, hr));
return(hr);
}
//+-------------------------------------------------------------------
//
// Member: CStatArray::CStatArray
//
// Synopsis: Read in the enumeration using RtlEnumerateProperties
//
// Arguments: [np] -- the NTPROP to use
// [phr] -- S_OK on success, error otherwise.
//
// Notes: Retry getting number of properties and reading all of
// them into a caller-allocated buffer until it fits.
//
//--------------------------------------------------------------------
CStatArray::CStatArray(NTPROP np, HRESULT *phr)
{
NTSTATUS Status;
ULONG ulKeyZero;
ULONG cpropAllocated;
_cRefs = 1;
_psps = NULL;
do
{
// when *pkey == 0, *pcprop == MAXULONG, aprs == NULL and asps == NULL on input,
// *pcprop will be the total count of properties in the enumeration set. OLE needs to
// allocate memory and enumerate out of the cached PROPID+propname list.
ulKeyZero = 0;
_cpropActual = MAX_ULONG;
delete [] _psps;
_psps = NULL;
Status = RtlEnumerateProperties(
np,
ENUMPROP_NONAMES,
&ulKeyZero,
&_cpropActual,
NULL, // aprs
NULL);
if (!NT_SUCCESS(Status))
break;
cpropAllocated = _cpropActual + 1;
_psps = new STATPROPSTG [ cpropAllocated ];
if (_psps == NULL)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
ulKeyZero = 0;
Status = RtlEnumerateProperties(
np,
0,
&ulKeyZero,
&cpropAllocated,
NULL, // aprs
_psps);
} while (NT_SUCCESS(Status) && cpropAllocated != _cpropActual);
*phr = NT_SUCCESS(Status) ? S_OK : DfpNtStatusToHResult(Status);
}
//+-------------------------------------------------------------------
//
// Member: CStatArray::~CStatArray
//
// Synopsis: Deallocated the object's data.
//
//--------------------------------------------------------------------
CStatArray::~CStatArray()
{
if (_psps != NULL)
{
CleanupSTATPROPSTG(_cpropActual, _psps);
}
delete [] _psps;
}
//+-------------------------------------------------------------------
//
// Member: CStatArray::NextAt
//
// Synopsis: Read from the internal STATPROPSTG array.
//
// Effects: The cursor is passed in, and this function acts
// as a IEnumXX::Next would behave if the current cursor
// was [ipropNext].
//
// Arguments: [ipropNext] -- index of cursor to use
// [pspsDest] -- if NULL, emulate read's effect on cursor.
// if non-NULL, return data with cursor effect.
// [pceltFetched] -- buffer for count fetched
//
// Returns: STATUS_SUCCESS if successful, otherwise
// STATUS_INSUFFICIENT_RESOURCES.
//
// Notes:
//
//--------------------------------------------------------------------
NTSTATUS
CStatArray::NextAt(ULONG ipropNext, STATPROPSTG *pspsDest, ULONG *pceltFetched)
{
ULONG ipropLastPlus1;
//
// Copy the requested number of elements from the cache
// (including strings, the allocation of which may fail.)
//
ipropLastPlus1 = ipropNext + *pceltFetched;
if (ipropLastPlus1 > _cpropActual)
{
ipropLastPlus1 = _cpropActual;
}
*pceltFetched = ipropLastPlus1 - ipropNext;
if (pspsDest != NULL)
return CopySTATPROPSTG(*pceltFetched, pspsDest, _psps + ipropNext);
else
return(STATUS_SUCCESS);
}
//+-------------------------------------------------------------------
//
// Member: CEnumSTATPROPSTG::CEnumSTATPROPSTG
//
// Synopsis: Constructor for object that has cursor over CStatArray
// and implements IEnumSTATPROPSTG, used by
// CPropertyStorage::Enum.
//
// Arguments: [np] -- the NTPROP to use
// [phr] -- where to put the HRESULT
//
//--------------------------------------------------------------------
CEnumSTATPROPSTG::CEnumSTATPROPSTG(NTPROP np, HRESULT *phr)
{
_ulSig = ENUMSTATPROPSTG_SIG;
_cRefs = 1;
_psa = new CStatArray(np, phr);
_ipropNext = 0;
}
//+-------------------------------------------------------------------
//
// Member: CEnumSTATPROPSTG::CEnumSTATPROPSTG
//
// Synopsis: Constructor which is used by IEnumSTATPROPSTG::Clone.
//
// Arguments: [other] -- the CEnumSTATPROPSTG to copy
// [phr] -- the error code.
//
// Notes: Since the CStatArray actually contains the object this
// just adds to the ref count.
//
//--------------------------------------------------------------------
CEnumSTATPROPSTG::CEnumSTATPROPSTG(const CEnumSTATPROPSTG & other, HRESULT *phr)
{
_ulSig = ENUMSTATPROPSTG_SIG;
_cRefs = 1;
_psa = other._psa;
_psa->AddRef();
_ipropNext = other._ipropNext;
*phr = S_OK;
}
//+-------------------------------------------------------------------
//
// Member: CEnumSTATPROPSTG::~CEnumSTATPROPSTG
//
// Synopsis: Deallocated storage.
//
// Arguments:
//
// Returns:
//
// Notes:
//
//--------------------------------------------------------------------
CEnumSTATPROPSTG::~CEnumSTATPROPSTG()
{
_ulSig = ENUMSTATPROPSTG_SIGDEL; // prevent another thread doing it - kinda
if (_psa != NULL)
_psa->Release();
}
//+-------------------------------------------------------------------
//
// Member: CEnumSTATPROPSTG::QueryInterface
//
// Synopsis: Respond to IEnumSTATPROPSTG and IUnknown.
//
// Returns: S_OK or E_NOINTERFACE
//
//--------------------------------------------------------------------
HRESULT CEnumSTATPROPSTG::QueryInterface( REFIID riid, void **ppvObject)
{
HRESULT hr;
*ppvObject = NULL;
if (S_OK != (hr = Validate()))
return(hr);
if (IsEqualIID(riid, IID_IEnumSTATPROPSTG))
{
*ppvObject = (IEnumSTATPROPSTG *)this;
AddRef();
}
else
if (IsEqualIID(riid, IID_IUnknown))
{
*ppvObject = (IUnknown *)this;
AddRef();
}
else
{
hr = E_NOINTERFACE;
}
return(hr);
}
//+-------------------------------------------------------------------
//
// Member: CEnumSTATPROPSTG::AddRef
//
// Synopsis: Add 1 to ref count.
//
//--------------------------------------------------------------------
ULONG CEnumSTATPROPSTG::AddRef(void)
{
if (S_OK != Validate())
return(0);
InterlockedIncrement(&_cRefs);
return(_cRefs);
}
//+-------------------------------------------------------------------
//
// Member: CEnumSTATPROPSTG::Release
//
// Synopsis: Subtract 1 from ref count and delete if 0.
//
//--------------------------------------------------------------------
ULONG CEnumSTATPROPSTG::Release(void)
{
LONG lRet;
if (S_OK != Validate())
return(0);
lRet = InterlockedDecrement(&_cRefs);
if (lRet == 0)
{
delete this;
}
else
if (lRet <0)
{
lRet = 0;
}
return(lRet);
}
//+-------------------------------------------------------------------
//
// Function: CopySTATPROPSTG
//
// Synopsis: Copy out the range of elements from [pspsSrc] to
// [pspsDest].
//
// Arguments: [celt] -- count of elements to copy
// [pspsDest] -- where to copy to, always filled with
// zeros before anything else (helps cleanup
// case.)
//
// [pspsSrc] -- where to copy from
//
// Returns: STATUS_SUCCESS if ok, otherwise
// STATUS_INSUFFICIENT_RESOURCES in which case there
// may be pointers that need deallocating. Use
// CleanupSTATPROPSTG to do that.
//
//--------------------------------------------------------------------
NTSTATUS
CopySTATPROPSTG(ULONG celt,
STATPROPSTG * pspsDest,
const STATPROPSTG * pspsSrc)
{
memset(pspsDest, 0, sizeof(*pspsDest) * celt);
while (celt)
{
*pspsDest = *pspsSrc;
if (pspsSrc->lpwstrName != NULL)
{
pspsDest->lpwstrName = (LPOLESTR)CoTaskMemAlloc(
sizeof(OLECHAR)*(1+ocslen(pspsSrc->lpwstrName)));
if (pspsDest->lpwstrName != NULL)
{
ocscpy(pspsDest->lpwstrName,
pspsSrc->lpwstrName);
}
else
{
return STATUS_INSUFFICIENT_RESOURCES;
}
}
celt--;
pspsDest++;
pspsSrc++;
}
return(STATUS_SUCCESS);
}
//+-------------------------------------------------------------------
//
// Member: CleanupSTATPROPSTG
//
// Synopsis: Free any elements in the passed array.
//
// Arguments: [celt] -- number of elements to examine.
// [psps] -- array of STATPROPSTG to examine.
//
// Notes: Zeros them out too.
//
//--------------------------------------------------------------------
VOID
CleanupSTATPROPSTG(ULONG celt, STATPROPSTG * psps)
{
while (celt)
{
CoTaskMemFree(psps->lpwstrName);
memset(psps, 0, sizeof(*psps));
celt--;
psps++;
}
}
//+-------------------------------------------------------------------
//
// Member: CEnumSTATPROPSTG::Next
//
// Synopsis: Get the next [celt] STATPROPSTGs from the enumerator.
//
// Arguments: [celt] -- count requested.
// [rgelt] -- where to return them
// [pceltFetched] -- buffer for returned-count.
// if pceltFetched==NULL && celt != 1 -> STG_E_INVALIDPARAMETER
// if pceltFetched!=NULL && celt == 0 -> S_OK
//
// Returns: S_OK if successful, otherwise error.
//
//--------------------------------------------------------------------
HRESULT CEnumSTATPROPSTG::Next(
ULONG celt,
STATPROPSTG * rgelt,
ULONG * pceltFetched)
{
HRESULT hr;
NTSTATUS Status;
ULONG celtFetched = celt;
// ----------
// Validation
// ----------
// Validate 'this'
if (S_OK != (hr = Validate()))
return(hr);
// Validate the inputs
if (NULL == pceltFetched)
{
if (celt != 1)
return(STG_E_INVALIDPARAMETER);
}
else
{
VDATEPTROUT( pceltFetched, ULONG );
*pceltFetched = 0;
}
if( 0 == celt )
return( S_OK );
if( !IsValidPtrOut(rgelt, celt * sizeof(rgelt[0])) )
return( E_INVALIDARG );
// -----------------------
// Perform the enumeration
// -----------------------
if (celt == 0)
return(hr);
Status = _psa->NextAt(_ipropNext, rgelt, &celtFetched);
if (NT_SUCCESS(Status))
{
_ipropNext += celtFetched;
if (pceltFetched != NULL)
*pceltFetched = celtFetched;
hr = celtFetched == celt ? S_OK : S_FALSE;
}
else
{
CleanupSTATPROPSTG(celt, rgelt);
hr = DfpNtStatusToHResult(Status);
}
return(hr);
}
//+-------------------------------------------------------------------
//
// Member: CEnumSTATPROPSTG::Skip
//
// Synopsis: Skip the next [celt] elements in the enumeration.
//
// Arguments: [celt] -- number of elts to skip
//
// Returns: S_OK if skipped [celt] elements
// S_FALSE if skipped < [celt] elements
//
// Notes:
//
//--------------------------------------------------------------------
HRESULT CEnumSTATPROPSTG::Skip(ULONG celt)
{
HRESULT hr;
ULONG celtFetched = celt;
if (S_OK != (hr = Validate()))
return(hr);
_psa->NextAt(_ipropNext, NULL, &celtFetched);
_ipropNext += celtFetched;
return celtFetched == celt ? S_OK : S_FALSE;
}
//+-------------------------------------------------------------------
//
// Member: CEnumSTATPROPSTG::Reset
//
// Synopsis: Set cursor to beginnging of enumeration.
//
// Returns: S_OK otherwise STG_E_INVALIDHANDLE.
//
//--------------------------------------------------------------------
HRESULT CEnumSTATPROPSTG::Reset()
{
HRESULT hr;
if (S_OK != (hr = Validate()))
return(hr);
_ipropNext = 0;
return(S_OK);
}
//+-------------------------------------------------------------------
//
// Member: CEnumSTATPROPSTG::Clone
//
// Synopsis: Creates an IEnumSTATPROPSTG with same cursor
// as this.
//
// Arguments: S_OK or error.
//
//--------------------------------------------------------------------
HRESULT CEnumSTATPROPSTG::Clone(IEnumSTATPROPSTG ** ppenum)
{
HRESULT hr;
// ----------
// Validation
// ----------
// Validate 'this'
if (S_OK != (hr = Validate()))
return(hr);
// Validate the input
VDATEPTROUT( ppenum, IEnumSTATPROPSTG* );
// --------------------
// Clone the enumerator
// --------------------
*ppenum = NULL;
hr = STG_E_INSUFFICIENTMEMORY;
*ppenum = new CEnumSTATPROPSTG(*this, &hr);
if (FAILED(hr))
{
delete (CEnumSTATPROPSTG*)*ppenum;
*ppenum = NULL;
}
return(hr);
}