Click here to Skip to main content
15,897,334 members
Articles / Desktop Programming / ATL

Structured Storage Class for ATL & MFC

Rate me:
Please Sign up or sign in to vote.
3.67/5 (12 votes)
20 Jul 2000 127.2K   2.2K   55  
A wrapper class for most common IStorage methods and API calls.
// StructuredStorage.h: interface for the StructuredStorage class.
//
//////////////////////////////////////////////////////////////////////

#if !defined(AFX_STRUCTUREDSTORAGE_H__12A34775_5BB8_11D4_BC32_008048EB8A80__INCLUDED_)
#define AFX_STRUCTUREDSTORAGE_H__12A34775_5BB8_11D4_BC32_008048EB8A80__INCLUDED_

// ------------------------------------------------------------------
/*!
	\file 
	The StructuredStorage class declaration file. It declares the
	class that makes handling of casual structured storage actions
	easier.

	\author 
		Ales Krajnc
		
	\version 
		1.0 Initial version.
*/
// ------------------------------------------------------------------

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#include <ATLBase.h>
#include <vector>

//-------------------------------------------------------------------
//! This class provides a thin wrapper for common structured storage functionality.
/*!
	 Structured storage is a useful tool in the software development.
	 It allows us to open a single binary file and section it into 
	 several storages (which may be nested). Each storage may have one
	 or more streams, which are used to write and retrieve the program 
	 specific information. 
	 
	 All this functionality is built around COM objects that MS provides
	 with the Windows OS. We access it via IStorage and IStream interfaces,
	 and a few API function calls. This, however, is a tedious job and I
	 decided to write a thin wrapper around these interfaces and API calls.

	 <B>Background information</B>

    At this point I must say that the structure of the class was 
	 inspired by the article and code of <b>Andrew Peace</b>. His solution
	 suited me perfectly, but unfortunately, I find it useless due to
	 following reasons:

    - Andrew's solution uses CString and CList and is therefore bound
	   with MFC library. We need the structured storage functionality
		in a COM control and therefore the linkage to MFC is not desired.

    - A better look at his code shows that he uses several redundant 
	   variables, which complicate the code, unnecessarily.

    - Sometimes (though rarely) we need to know why an interface 
	   call or a function call failed.

    Although my solution eliminates the shortcomings above, it adds
	 some new:

    - I am using a STL vector container. Some people try to avoid 
	   using STL containers in their COM objects.

    - If my solution is used inside standard MFC project, a support
	   for CComBSTR class is required (defined in \<ATLBase.h\>).

    - Advance features of structured storage management are not
	   provided.

    
	 <B>Implementation information</B>

    The class is build around a vector of IStorage objects.
	 (IStorage object == storage, vector of storages == container)
	 The first element of vector represents the root storage and 
	 subsequent elements represent substorages. We can say that
	 the vector of storages represents a path from the root storage
	 to the current storage (the last one). Example:

	 \verbatim
	 [0] = root storage (level 0, not neceserally the root of the file).
	 [1] = storage inside the root (level 1).
    [2] = storage inside the level 1 storage (level2)
	 [n] = current storage at level n.
	 \endverbatim

	 If the root is also the root of the file, say 
	 "C:\test\StructFile.bin",
	 level 1 is "Level 1", level 2 is "Level 2", ... then
	 the path to the current storage is:

	 "C:\test\StructFile.bin|Level1|Level2|...|LevelN"
    
	 Vertical char | is used to emphasize the borderline between the 
	 storages. Note, that the part of the path "Level1|Level2|...|LevelN" 
	 is hidden inside the "StructFile.bin" file.

	 The \b front and the \b back of the vector are the most important
	 storages. The former presents the \e root storage, while the
	 later presents the \e current storage.

	 <B>Related documentation</B>

	 Please refer to MSDN library and search for <I>IStorage, IStream,
	 StgCreateDocfile, StgOpenStorage, ...</I> for more details.
*/

class StructuredStorage  
{
private:
	//! Internal (private) type used to shorten the stl::vector\<...\> declaration.
	/*! The IStorage objects are stored in the stl::vector container.
	    The objects are wrapped by the CComPtr\<\> smart pointer objects, 
		 that do the reference counting for us. 
		 
		 It is safe to put COM objects wrapped by CComPtr into stl::vector, 
		 since vector does not use relation operators. Otherwise, the CAdapt\<\> 
		 class would be needed.
	 */
	typedef std::vector<CComPtr<IStorage> > StorageContainer;

	//! Copy constructor - private, empty & useless!
	/*! If copy constructor is not given, the compiler may create one
		 and this is definitely not what we want in this class. Therefore
		we implement private dummy copy constructor.
	 */
	StructuredStorage(const StructuredStorage&);

public:
	//! The default constructor. It creates an empty container.
	/*! The default constructor initializes the class variables. The object
		 we get is of no use until we open a valid storage.
		 \sa CreateFile, OpenFile, Attach.
	 */
	StructuredStorage();

	//! The destructor calls the Clear() method to do the cleaning job.
	/*! Destructor must release storages in the container
		 and empty the container. It leaves this job to the Clear()
		 function member.
	 */
	virtual ~StructuredStorage();

	//! Releases all storages, empties the container and filename.
	/*! Occasionally, we may want to get rid of current storages and
		 prepare the object to accept new one. This method simply
		 releases all storages in the container by emptying it.

		 After the call, \e this object is not useful until it is empty.

		 \sa CreateFile, OpenFile, Attach.
	 */
	void Clear();

	//! Returns the root IStorage object to the caller and empties the container.
	/*! In some special (and rare) cases, you may want to get rid of
		 storages in \e this object and return the root 
		 IStorage object to the caller. 
	 
		 This method does just that. It releases all storages in the
		 container but the root one. Afterwards it returns the
		 interface pointer to the root storage to the caller and empties
		 the container. The caller is responsible for the returned object
		 and must call Release() when it does not need the returned 
		 IStorage object anymore.

	    After the call the container is empty and \e this object is not useful
		 until a new storage is added.

		 \return The IStorage interface pointer of the object. The caller owns
		 the returned object. If the container is empty, it returns NULL.

		 \sa Attach
	 */
	IStorage* Detach();

	//! Attaches a new root IStorage object into this object. 
	/*! In some special (and rare) cases, you may want to create a new
		 series of storages starting from the given \a pIStg
		 object. There is no need for pIStg to be the root object in the
		 structured file. Any valid IStorage object will be accepted, however
		 inside \e this object it will be treated as root storage (initial
		 storage).

		 \param pIStg A pointer to the new root IStorage object.

		 \warning We do not AddRef the storage pointer \a pIStg that 
		 you pass in the call. 
		 When \e this object goes out of scope, the destructor will call Clear 
		 method, which calls Release for all storages in the container, including 
		 the root one. This means that the root storage will be released even if 
		 it was not AddRef-ed when it was attached.
		 If you do not want such behavior and you want to get the root storage 
		 back latter, you must call the Detach method, which will empty the
		 container without releasing the root storage.

		 The Attach/Detach calls are useful when storages are passed between
		 COM objects. Say, that we have a COM object A that owns COM object B and
		 A instructs B to save itself into given storage ...
		 \verbatim
		 // Object A code
		 ...
		 SructuredStorage ss;
		 ss.CreateFile("SomeFile.bin");
		 ss.CreateStorage("B");

		 // m_pB == object B owned by A
		 m_pB->Save(ss.GetCurrentStorage());
		 ... 
		 // Do something more with the ss.
		 \endverbatim

	    \verbatim
	    // Object B Save code
		 HRESULT B::Save(IStorage* pStg)
		 {
		    StructuredStorage ss;
			 ss.Attach(pStg);
			 // do something with the storage, e.g. create stream
			 CComPtr<IStream> spIStream;
			 ss.CreateStream(&spIStream, "SomeStream");
			 // ...

			 // MUST! detach the root from the ss to prevent releasing
			 // the root storage. Otherwise an error appears in A object.
			 ss.Detach();
			 return S_OK;
		 }
	 
		 \endverbatim

		 \sa Detach.
	 */
	void Attach(IStorage* pIStg); 

	//! Returns \b true if we have at least one valid storage in the container.
	bool AnyStorage() const
	{ return !m_conStorage.empty(); }

	//! Creates a structured storage file. 
	/*! This should be one of the first calls after \e this object 
		 is constructed. It takes the \a osFileName and optional \a mode
		 argument and calls the \b StgCreateDocfile Windows API function.

		 \param osFileName The structured storage file name.
		 \param mode       The mode flags used for storage creation.

		 If it succeeds, the API creates the new structured file by 
		 overwriting the existing file if needed. If you do not want to 
		 overwrite an existing file use the OpenFile call instead or specify
		 the \e STGM_FAILIFTHERE flag in the \e mode argument.
		 After the file was created, the IStorage object is also created
		 and inserted into the IStorage container. It is the first element
		 of the container and is representing the root IStorage object.

	    The IStorage object is closed when it is released. 

		 The m_LastResult data member holds the return value of the API 
		 call. You may consider to retrieve this result if the method fails.

		 \retval true The call was successful; the file and the root storage
		 was created. 
		 \retval false The call failed. Call the GetLastResult() to get
		 the reason for the failure.

		 \sa OpenFile, MSDN library - search for \e StgCreateDocfile 
		 documentation.
	 */
	bool CreateFile(LPCOLESTR osFileName, 
		DWORD mode = STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_CREATE);

	//! Opens an existing structured file.
	/*! When we want to work on an existing structured file, we open the
	    \a osFileName file using this method. The call succeeds only
		 if the file truly exists and if the \a mode flags have a meaningful
		 combination. 

		 \param osFileName The structured storage file name.
		 \param mode       The mode flags used for storage creation.

		 In order to open the file, we call the StgOpenStorage API function
		 call. The API also creates an IStorage object, which in turn, we
		 put into IStorage container. It is the first (root) element of the
		 container.

	    If you are unsure whether the file exists you may try to
		 open it using this method first and if the call fails, you try
		 to create it. The following code shows en example:

		 \verbatim
		 StructuredStorage ss;
		 LPCOLESTR osFN = L"SomeFileName.ext";
		 if(ss.OpenFile(osFN)==false) {
		    if(ss.CreateFile(osFN)==false) {
				 // Something else is wrong.
				 HRESULT hr = GetLastResult();
				 // Parse hr variable to get the reason for the failure.
				 return ...
			 }
		 }
		 // A was file opened/created and we have one storage in container.
		 \endverbatim

	    The IStorage object is closed when it is released. 

		 The m_LastResult data member holds the return value of the API 
		 call. You may consider to retrieve this result if the method fails.

		 \retval true The call was successful; the file was opened and the 
		 root storage  was created. 
		 \retval false The call failed. Call the GetLastResult() to get
		 the reason for the failure.

		 \sa CreateFile, MSDN library - search for \e StgOpenStorage 
		 documentation.
	*/
	bool OpenFile(LPCOLESTR osFileName, 
		DWORD mode = STGM_READWRITE | STGM_SHARE_EXCLUSIVE);

	//! Creates a new storage inside the current storage.
	/*! When we want to create a new storage object inside the 
		 \e current storage, this method should be used. It will try to
		 create (and overwrite if necessary) a new storage with the given
		 name (\a osName). If the old storage is overwritten, its existing
		 content is lost. If this is not what you want, you should consider 
		 using the OpenStorage() instead.

	    There must be at least one storage in the container or the method
		 fails immediately.

		 \param osName  The name of the new storage.
		 \param bEnter  True if the new storage becomes the current one.
		 \param mode    Storage creation flags.

		 The \a bEnter argument tells the new storage to become
		 the current storage, too. It is \b true by default.
		 If this is the case, the new storage is pushed at the end of 
		 the container and thus it becomes the \e current storage.

		 However, if \a bEnter is \b false, the entry in current storage
		 is made, but newly created storage does not becomes the \e current 
		 one. The new storage is released immediately and the same current
		 storage is kept. This option is useful, if we want to create \e 
		 placeholders for storages in the current storage. These storages
		 can be opened with the OpenStorage call, latter.

		 \retval true If everything went OK.
		 \retval false Something has failed and the storage was not created.
		 Call the GetLastResult() to get the details on the failure.

		 \sa OpenStorage, CloseStorage, MSDN library IStorage::CreateStorage.
	*/
	bool CreateStorage(LPCOLESTR osName, bool bEnter=true, 
		DWORD mode = STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_CREATE);

	//! Opens an existing storage inside the current storage.
	/*! Opens an existing storage inside the current storage. If the
		 storage with name \a osName does not exists, the call fails.
		 If the call is successful, the new storage object is added into
		 container and it becomes the \e current storage.

		 There must be at least one storage in the container or the function
		 fails immediately.

		 \param osName  The name of the storage to open.
		 \param mode    Storage open flags.

	    If you are unsure whether the storage already exists you may try to
		 open it using this method first and if the call fails, you try
		 to create it. The following code shows an example:

		 \verbatim
		 StructuredStorage ss;
		 // ...
		 // we assume that ss has a valid current storage
		 CComBSTR bsStorage(L"SomeStorage");
		 if(ss.OpenStorage(bsStorage)==false) {
		    if(ss.CreateStorage(bsStorage)==false) {
				 // Something else is wrong.
				 HRESULT hr = GetLastResult();
				 // Parse hr variable to get the reason for the failure.
				 return ...
			 }
		 }
		 // A valid storage was created ...
		 \endverbatim

		 \retval true If everything went OK.
		 \retval false Something has failed and the storage was not created.
		 Call the GetLastResult() to get the details on the failure.

		 \sa CreateStorage, CloseStorage, MSDN library IStorage::OpenStorage.
	 */
	bool OpenStorage(LPCOLESTR osName, 
		DWORD mode = STGM_READWRITE | STGM_SHARE_EXCLUSIVE);

	//! Closes the storage at the highest (current) level and moves one level down.
	/*! There is not explicit method for a storage that would
	    close it down. It is closed when it is released. This method
		 does just that. It removes the last storage from the container. The
		 removed storage is automatically released.

		 The storage at the lower level becomes the current storage.

	    The root storage can not be closed using this method. If you
		 want to close the root storage, you must call the Clear() method.

		 \warning If the storage that has been removed was previously 
		 given to the caller by one of the GetRootStorage, 
		 GetCurrentStorage or GetStorageAtLevel calls and if the caller 
		 had called AddRef() on the storage, the storage might not be removed 
		 from the memory.

		 \retval true If the current storage was removed and a storage at
		 lower level became the current storage.
		 \retval false If you attempt to close the root storage or if the 
		 container is empty.

	    \sa CreateStorage, OpenStorage.
	 */
	bool CloseStorage();

	//! Destroys a storage or a stream inside the current storage.
	/*! A storage or a stream with given name \a osName inside the 
	    current storage is removed. The call is valid only if the
		 storage container is not empty.
	 
		 The call to this method may fail if the stream or storage
		 that we want to delete (destroy) is in use. See the 
		 IStorage::DestroyElement in MSDN library for more details.

		 \retval true In the case of success.
		 \retval false The method failed to delete the storage or
		 stream. You might call the GetLastResult to get the reason.

		 \sa CreateStorage, CreateStream.
	 */
	bool Destroy(LPCOLESTR osName);

	//! Creates a stream inside the current storage.
	/*! The whole idea of this class is to get a stream into which we 
	    are able to put information and retrieve it from. This method
		 creates a new stream in current storage. If the stream with 
		 given \a osName exists, the stream is overwritten and the data in
		 old stream is lost. If this is not the desired action, 
		 consider using OpenStream() method.

		 There must be at least one storage in the container or the method
		 fails immediately.

		 \param ppIStr Address of the pointer to the IStream object
							that will be created.
		 \param osName Name of the storage/stream to remove from the
							current storage.
		 \param mode   Stream creation flags.

		 \retval true The method was successful and the newly
		 created stream object was assigned into the \a ppIStr variable. 
		 The caller gets the ownership over this object and must 
		 Release() it when it does  not need it anymore.

		 \retval false Something has failed and the steam was not created.
		 NULL was assigned to the \a ppStr argument. You may consider to call
		 the GetLastResult() to get the reason for the failure.

		 \sa OpenStream, Destroy, MSDN library IStorage::CreateStream.
	 */ 
	bool CreateStream(IStream** ppIStr, LPCOLESTR osName, 
		DWORD mode = STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_CREATE);

	//! Opens an existing stream inside the current storage
	/*! The method tries to open an existing stream, known under 
	    \a osName. It fails if the current storage does not have such stream.

		 There must be at least one storage in the container or the method
		 fails immediately.

		 \param ppIStr Address of the pointer to the IStream object
							that will be created.
		 \param osName Name of the storage/stream to remove from the
							current storage.
		 \param mode   Stream open flags.

	    If you are unsure whether the stream already exists you may try to
		 open it using this method first and if the call fails, you try
		 to create it. The following code shows en example:

		 \verbatim
		 StructuredStorage ss;
		 // ...
		 // we assume that ss has a valid current storage
		 CComPtr<IStream> spIStr;
		 CComBSTR bsStream(L"SomeStream");
		 if(ss.OpenStream(&spIStr, bsStream)==false) {
		    if(ss.CreateStream(&spIStr, bsStream)==false) {
				 // Something else is wrong.
				 HRESULT hr = GetLastResult();
				 // Parse hr variable to get the reason for the failure.
				 return ...
			 }
		 }
		 // A valid stream was created. It is stored in spIStr!
		 \endverbatim

		 \retval true The method was successful and we assign the newly opened
		 stream object into the \a ppIStr variable. The caller gets
		 the ownership over this object and must Release() it when it does 
		 not need it anymore.

		 \retval false Something has failed and the stream was not opened.
		 We assign NULL to the \a ppStr argument. You may consider to call
		 the GetLastResult() to get the reason for the failure.

		 \sa CreateStream, Destroy, MSDN library IStorage::OpenStream.
	 */
	bool OpenStream(IStream** ppIStr, LPCOLESTR osName, 
		DWORD mode = STGM_READWRITE | STGM_SHARE_EXCLUSIVE);

	//! Returns the structured storage file name, if available.
	const CComBSTR& GetFileName() const
	{ return m_bsFileName; }

	//! Returns the path from the root storage up to the default one.
	/*! Each of the storages in the container holds information about
	    its name. If we put those names together, we get a kind of
		 path from the root storage up to current storage. The path is
		 stored in CComBSTR object (a BSTR actually).

		 If the root storage is also the root of the structured file, 
		 it holds the full path to the file.

		 Storages are separated with '|' character to distinguish  
		 storages from folders.

		 \return A CComBSTR object with the path BSTR string. If the
		 container is empty the string is empty, too.
	 */
	CComBSTR GetPath() const;

	//! Returns the \e HRESULT value of the last call.
	HRESULT GetLastResult() const
	{ return m_LastResult; }

	//! Returns the interface pointer to the current storage.
	/*! In most cases we want access to the last storage in the container
	    also known as the \e current storage. We allow outside access to
		 this storage. The caller however must use it immediately and should
		 not store it.

		 If the caller wants to own the returned storage, it must AddRef() it
		 explicitly, to prevent \e this class to invalidate it.

		 \return If the container is not empty, it returns pointer to the
		 last element in container. Otherwise it returns NULL.

		 \sa GetRootStorage, GetStorageAtLevel
	 */
	IStorage* GetCurrentStorage();

	//! Returns the interface pointer to any storage in the container.
	/*! The storage container holds valid storage objects from the root 
		 up to default. We say that the root storage is at level 0, the
		 next one at level 1, ... up to the last - default storage.

		 \param uLevel Level of the required storage.

		 This method allows the caller to get a storage in the container at
		 desired level (\a uLevel). The caller however must use it immediately 
		 and should not store it for latter use.

		 If the caller wants to own the returned storage, it must AddRef() it
		 explicitly, to prevent \e this class to invalidate it.

		 \return If the \a uLevel argument is in a valid range, it returns 
		 pointer to storage at the desired level. Otherwise it returns NULL.

		 \sa GetCurrentStorage, GetRootStorage
	 */
	IStorage* GetStorageAtLevel(unsigned int uLevel);

	//! Returns the interface pointer to the root storage.
	/*! The root storage might be also interesting. We allow outside world to
		 access this storage. The caller however must use it immediately 
		 and should not store it for latter use.

		 If the caller wants to own the returned storage, it must AddRef() it
		 explicitly, to prevent \e this class to invalidate it.

		 \return If the container is not empty, it returns pointer to the
		 first element in container. Otherwise it returns NULL.

		 \sa GetCurrentStorage, GetStorageAtLevel
	*/
	IStorage* GetRootStorage();

private:
	//! The name of the structured storage file. May be empty.
	CComBSTR				m_bsFileName;
	//! The container of IStorage objects.
	StorageContainer  m_conStorage;
	//! The result of last file/storage/stream operation.
	HRESULT           m_LastResult;
};
// ------------------------------------------------------------------

#endif // !defined(AFX_STRUCTUREDSTORAGE_H__12A34775_5BB8_11D4_BC32_008048EB8A80__INCLUDED_)

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

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


Written By
Web Developer
Slovenia Slovenia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions