// 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_)