COM Structured Storage in .NET
Written by Harry Fairhead   
Wednesday, 03 February 2010
Article Index
COM Structured Storage in .NET
Opening structured storage
Using IStorage
Reading a stream
Reading JPEG data


Open the file

In most cases the first thing that you need to do when working with a structured storage file is to open it.

Another helper function does this job – StgOpenStorage. Its definition can be found at PINVOKE.NET:

static extern int StgOpenStorage(
string pwcsName,
IStorage pstgPriority,
STGM grfMode,
IntPtr snbExclude,
uint reserved,
out IStorage ppstgOpen);

At this point we hit our first "definition cascade".

The function is simple enough but it makes use of the new types STGM and IStorage.

STGM is just a set of constants that control the SToraGe Mode, i.e. the access mode. You could look these constants up in the header file and write them explicitly into the program but PINVOKE.NET has a very nice definition for STGM which starts:

public enum STGM : int
DIRECT = 0x00000000,
TRANSACTED = 0x00010000,

The [Flags] attribute means that the members can be treated as bit fields and combined together using logical operations to set multiple conditions.

The STGM enumeration would have been easy to create manually but not so the IStorage interface. Thankfully there is a translation in both C# and VB at Pinvoke.Net which starts:

interface IStorage
void CreateStream(
/* [string][in] */ string pwcsName,
/* [in] */ uint grfMode,
/* [in] */ uint reserved1,
/* [in] */ uint reserved2,
/* [out] */ out IStream ppstm);

You can copy and paste this and the STGM enumeration into your program.

It is clear that the IStorage definition at Pinvoke.Net isn't much used so you need to check it as you use each function. Reading through the definition IStorage also seems to introduce new types that should need explicit definitions.

The first is FILETIME used in SetElementTimes:

void SetElementTimes(
/* [string][unique][in] */
 string pwcsName,
/* [unique][in] */ FILETIME pctime,
/* [unique][in] */ FILETIME patime,
/* [unique][in] */ FILETIME pmtime);

We don't need to add a definition for this because it's already defined within ComType:

void SetElementTimes(
/* [string][unique][in] */
string pwcsName,
/* [unique][in] */
ComTypes.FILETIME pctime,
/* [unique][in] */
ComTypes.FILETIME patime,
/* [unique][in] */
ComTypes.FILETIME pmtime);

Similarly the Stat function introduces the STATSTG struc:

void Stat(
/* [out] */ out STATSTG pstatstg,
/* [in] */ uint grfStatFlag);

As this too is defined in ComTypes, we simply change this to:

void Stat(
/* [out] */ out
ComTypes.STATSTG pstatstg,
/* [in] */ uint grfStatFlag);

Fully qualified names are necessary because FILETIME and STATSTG are ambiguous. The new interface IStream, which is introduced in a number of methods, is also defined in ComType but as it is unambiguous we can deal with it by adding:

using System.Runtime.

Finally we have the IEnumSTATSTG interface introduced in the all important EnumElements function:

void EnumElements(
/* [in] */ uint reserved1,
/* [size_is][unique][in] */
IntPtr reserved2,
/* [in] */ uint reserved3,
/* [out] */ out IEnumSTATSTG ppenum);

This isn't available in ComTypes or anywhere else so we have to make use of Pinvoke.Net's definition which starts:

public interface IEnumSTATSTG

If you copy and paste this into the program you will discover that it too defines types which aren't yet defined.

Never ending!

At this point in the development of this sort of project you begin to think that you will never have everything defined as each time you introduce something you need more definitions.

Of course it does end and in this case all that is needed is to change the Next method to read:

uint Next(
uint celt,
[MarshalAs(UnmanagedType.LPArray), Out]
ComTypes.STATSTG[] rgelt,
out uint pceltFetched

This makes use of the STATSTG structure defined in ComTypes. At this point everything is defined and we can move on to start making use of the new interfaces and types. The definitions may need some fine tuning but the effort Pinvoke.Net has saved just in typing is evident.

At long last we can now open a structured storage file:

IStorage Is;

result = StgOpenStorage(file, null,
IntPtr.Zero, 0, out Is);

It is assumed that "file" contains the name of a Thumbs.db file complete with path. Notice the the file is opened in ReadWrite and Share_Exclusive mode. This is usually what you need to do but the whole subject of modes of opening structured storage is complicated and if you get an error message when you try and open a file this is usually the source of the problem - if you are running Vista or Windows 7 it is unlikely that the StgOpenStorage call will work without making some modifications to the permissions the program has - see the next section.





Last Updated ( Thursday, 04 February 2010 )