转载请注明出处:http://blog.youkuaiyun.com/jh_zzz
最近项目中正好用到用 C# 写结构化存储,贴出来与大家共享一下:
结构化存储机制是COM的数据存储的基础,其核心思想是在一个文件内部建立一个类似于文件系统的完整的存储结构,并以存储对象或流对象构成了此类文件系统中树状结构的各个节点,这个包含了类似于文件系统的存储结构的文件也被称为复合文件。
Windows 提供了两个函数用来打开或创建结构化文件存储对象:
public
sealed
class
NativeMethods

...
{
private NativeMethods()

...{
}

[DllImport("ole32.dll", PreserveSig = false)]
[return: MarshalAs(UnmanagedType.Interface)]
internal static extern IStorage StgCreateDocfile([MarshalAs(UnmanagedType.LPWStr)] string pwcsName, uint grfMode, uint reserved);

[DllImport("ole32.dll", PreserveSig = false)]
[return: MarshalAs(UnmanagedType.Interface)]
internal static extern IStorage StgOpenStorage([MarshalAs(UnmanagedType.LPWStr)] string pwcsName, IntPtr pstgPriority, uint grfMode, IntPtr snbExclude, uint reserved);
}
这两个函数成功执行后都会返回一个 IStorage 接口指针,调用相应的接口的函数便可以对复合文件进行操作,以下是对 IStorage,ISteam 接口以及相关常量的声明:
public
enum
StorageMode

...
{
Read = 0x0,
Write = 0x1,
ReadWrite = 0x2,
ShareDenyNone = 0x40,
ShareDenyRead = 0x30,
ShareDenyWrite = 0x20,
ShareExclusive = 0x10,
Priority = 0x40000,
Create = 0x1000,
}

[ComImport, Guid(
"
0000000d-0000-0000-C000-000000000046
"
), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public
interface
IEnumSTATSTG

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

void Skip(uint celt);

void Reset();

[return: MarshalAs(UnmanagedType.Interface)]
IEnumSTATSTG Clone();
}

[ComImport, Guid(
"
0000000b-0000-0000-C000-000000000046
"
), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public
interface
IStorage

...
{
void CreateStream(string pwcsName, uint grfMode, uint reserved1, uint reserved2, out IStream ppstm);

void OpenStream(string pwcsName, IntPtr reserved1, uint grfMode, uint reserved2, out IStream ppstm);

void CreateStorage(string pwcsName, uint grfMode, uint reserved1, uint reserved2, out IStorage ppstg);

void OpenStorage(string pwcsName, IStorage pstgPriority, uint grfMode, IntPtr snbExclude, uint reserved, out IStorage ppstg);

void CopyTo(uint ciidExclude, IntPtr rgiidExclude, IntPtr snbExclude, IStorage pstgDest);

void MoveElementTo(string pwcsName, IStorage pstgDest, string pwcsNewName, uint grfFlags);

void Commit(uint grfCommitFlags);

void Revert();

void EnumElements(uint reserved1, IntPtr reserved2, uint reserved3, out IEnumSTATSTG ppenum);

void DestroyElement(string pwcsName);

void RenameElement(string pwcsOldName, string pwcsNewName);

void SetElementTimes(string pwcsName, FILETIME pctime, FILETIME patime, FILETIME pmtime);

void SetClass(Guid clsid);

void SetStateBits(uint grfStateBits, uint grfMask);

void Stat(out STATSTG pstatstg, uint grfStatFlag);
}

[ComImport, Guid(
"
0000000c-0000-0000-C000-000000000046
"
), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public
interface
IStream

...
{
void Read([Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] pv, int cb, IntPtr pcbRead);

void Write([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] pv, int cb, IntPtr pcbWritten);

void Seek(long dlibMove, int dwOrigin, IntPtr plibNewPosition);

void SetSize(long libNewSize);
void CopyTo(IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten);

void Commit(int grfCommitFlags);

void Revert();

void LockRegion(long libOffset, long cb, int dwLockType);

void UnlockRegion(long libOffset, long cb, int dwLockType);

void Stat(out STATSTG pstatstg, int grfStatFlag);

void Clone(out IStream ppstm);
}
为了使用方便,我们又另外包装了 Storage 类:
public
sealed
class
Storage : IDisposable

...
{
private bool disposed;
private IStorage storage;

public Storage(IStorage storage)

...{
this.storage = storage;
}

~Storage()

...{
//this.Dispose();
}

public static Storage CreateDocFile(string storageFile, StorageMode mode)

...{
IStorage storage = NativeMethods.StgCreateDocfile(storageFile, (uint)mode, 0);

return new Storage(storage);
}

public static Storage Open(string storageFile, StorageMode mode)

...{
IStorage storage = NativeMethods.StgOpenStorage(storageFile, IntPtr.Zero, (uint)mode, IntPtr.Zero, 0);

return new Storage(storage);
}

public void CopyTo(Storage destinationStorage)

...{
this.storage.CopyTo(0, IntPtr.Zero, IntPtr.Zero, destinationStorage.storage);
}

public Storage OpenStorage(string name, bool autoCreate)

...{
IStorage subStorage;

try

...{
this.storage.OpenStorage(name, null, (uint)(StorageMode.ReadWrite | StorageMode.ShareExclusive), IntPtr.Zero, 0, out subStorage);
}
catch (COMException)

...{
subStorage = null;
}

if (subStorage == null)

...{
if (autoCreate)
return CreateStorage(name);

return null;
}

return new Storage(subStorage);
}

public Storage RecurOpenStorage(string name, bool autoCreate)

...{
string pwcsName;

int pos = name.IndexOf('/');
if (pos > 0)

...{
pwcsName = name.Substring(0, pos);
name = name.Substring(pos + 1);
}
else

...{
pwcsName = name;
name = "";
}

Storage subStorage = OpenStorage(pwcsName, autoCreate);
if (subStorage != null && name.Length > 0)

...{
return subStorage.RecurOpenStorage(name, autoCreate);
}

return subStorage;
}

public void Dispose()

...{
if (!this.disposed)

...{
Marshal.ReleaseComObject(this.storage);
this.storage = null;

this.disposed = true;
}

GC.SuppressFinalize(this);
}

public Storage CreateStorage(string name)

...{
IStorage subStorage = null;

try

...{
//this.storage.OpenStorage(name, null,
// (uint)(StorageMode.ReadWrite | StorageMode.ShareExclusive),
// IntPtr.Zero, 0, out subStorage);
this.storage.CreateStorage(name,
(uint)(StorageMode.Create | StorageMode.ReadWrite | StorageMode.ShareExclusive),
0, 0, out subStorage);
this.storage.Commit(0);

return new Storage(subStorage);
}
catch (COMException)

...{
if (subStorage != null)
Marshal.ReleaseComObject(subStorage);
}

return null;
}

public Stream CreateStream(string name)

...{
IStream subStream = null;

try

...{
//this.storage.OpenStream(name, IntPtr.Zero,
// (uint)(StorageMode.ReadWrite | StorageMode.ShareExclusive),
// 0, out subStream);

//if (subStream != null)
// this.storage.DestroyElement(name);

//Now create the element
this.storage.CreateStream(name,
(uint)(StorageMode.Create | StorageMode.ReadWrite | StorageMode.ShareExclusive),
0, 0, out subStream);
this.storage.Commit(0);

return new Stream(subStream);
}
catch (COMException)

...{
if (subStream != null)
Marshal.ReleaseComObject(subStream);

return null;
}
}

public Stream OpenStream(string name)

...{
IStream subStream;

try

...{
this.storage.OpenStream(name, IntPtr.Zero,
(uint)(StorageMode.ReadWrite | StorageMode.ShareExclusive),
0, out subStream);

return new Stream(subStream);
}
catch (COMException)

...{
return null;
}
}

public void Commit(uint grfCommitFlags)

...{
this.storage.Commit(grfCommitFlags);
}
}
C# 在进行输入输出流操作的时候,都是使用 Stream 对象的,所以我们另外包装两个类做 IStream 接口到 Stream 对象的相互转换,这样在 C# 中用起来就很爽啦:
[ClassInterface(ClassInterfaceType.AutoDispatch)]
public
class
AxMemoryStream : MemoryStream, IStream

...
{
void IStream.Clone(out IStream clone)

...{
clone = this.MemberwiseClone() as IStream;
}

void IStream.Commit(int flags)

...{
throw new NotImplementedException("AxMemoryStream is not transactional");
}

void IStream.CopyTo(IStream destination, long count, IntPtr pcbRead, IntPtr pcbWritten)

...{

/**///////////
// Copy the lot using 4k chunks

/**///////////
byte[] _buffer = new byte[4096];
int _cbRead = 0;
int _cbWritten = 0;
while (count > 0)

...{
int _chunk = (int)Math.Min(count, _buffer.Length);
int _chunkRead = this.Read(_buffer, _cbRead, _chunk);
destination.Write(_buffer, _chunk, IntPtr.Zero);

_cbRead += _chunkRead;
_cbWritten += _chunkRead;
}


/**///////////
// Update the counts, if they were provided

/**///////////
if (pcbRead != IntPtr.Zero)

...{
Marshal.WriteInt64(pcbRead, _cbRead);
}

if (pcbWritten != IntPtr.Zero)

...{
Marshal.WriteInt64(pcbWritten, _cbWritten);
}
}

void IStream.LockRegion(long offset, long count, int lockType)

...{
throw new NotImplementedException("AxMemoryStream does not support locking");
}

void IStream.Read(byte[] buffer, int count, IntPtr pcbRead)

...{
int _cbRead = this.Read(buffer, 0, count);

if (pcbRead != IntPtr.Zero)

...{
Marshal.WriteInt32(pcbRead, _cbRead);
}
}

void IStream.Revert()

...{
throw new NotImplementedException("AxMemoryStream is not transactional");
}

void IStream.Seek(long offset, int origin, IntPtr pcbPos)

...{
long _position = this.Seek(offset, (SeekOrigin)origin);

if (pcbPos != IntPtr.Zero)

...{
Marshal.WriteInt64(pcbPos, _position);
}
}

void IStream.SetSize(long newSize)

...{
this.SetLength(newSize);
}

void IStream.Stat(out STATSTG stat, int flags)

...{
stat = new STATSTG();
stat.cbSize = Marshal.SizeOf(stat);
stat.grfLocksSupported = 0;
}

void IStream.UnlockRegion(long offset, long count, int lockType)

...{
throw new NotImplementedException("AxMemoryStream does not support locking");
}

void IStream.Write(byte[] buffer, int count, IntPtr pcbWritten)

...{
this.Write(buffer, 0, count);

if (pcbWritten != IntPtr.Zero)

...{
Marshal.WriteInt32(pcbWritten, count);
}
}
}

public
sealed
class
Stream : System.IO.Stream

...
{
private bool disposed;
private IStream stream;

public Stream(IStream stream)

...{
this.stream = stream;
}

~Stream()

...{
//this.Dispose();
}

public void Dispose()

...{
if (!this.disposed)

...{
Marshal.ReleaseComObject(this.stream);
this.stream = null;

this.disposed = true;
}

GC.SuppressFinalize(this);
}

public IStream UnderlyingStream

...{
get

...{
return this.stream;
}
}

public override bool CanRead

...{

get ...{ return true; }
}

public override bool CanSeek

...{

get ...{ return true; }
}

public override bool CanWrite

...{

get ...{ return true; }
}

public override void Flush()

...{
this.stream.Commit(0);
}

public override long Length

...{
get

...{
if (this.stream == null)
throw new ObjectDisposedException("Invalid stream object.");

STATSTG statstg;


this.stream.Stat(out statstg, 1 /**//* STATSFLAG_NONAME*/ );

return statstg.cbSize;
}
}

public override long Position

...{

get ...{ return Seek(0, SeekOrigin.Current); }

set ...{ Seek(value, SeekOrigin.Begin); }
}

public override int Read(byte[] buffer, int offset, int count)

...{
if (stream == null)
throw new ObjectDisposedException("Invalid stream object.");

if (offset != 0)

...{
throw new NotSupportedException("Only 0 offset is supported");
}

int bytesRead;

unsafe

...{
IntPtr address = new IntPtr(&bytesRead);

stream.Read(buffer, count, address);
}

return bytesRead;
}

public override long Seek(long offset, SeekOrigin origin)

...{
if (stream == null)
throw new ObjectDisposedException("Invalid stream object.");

long position = 0;

unsafe

...{
IntPtr address = new IntPtr(&position);
stream.Seek(offset, (int)origin, address);
}

return position;
}

public override void SetLength(long value)

...{
if (stream == null)
throw new ObjectDisposedException("Invalid stream object.");

stream.SetSize(value);
}

public override void Write(byte[] buffer, int offset, int count)

...{
if (stream == null)
throw new ObjectDisposedException("Invalid stream object.");

if (offset != 0)

...{
throw new NotSupportedException("Only 0 offset is supported");
}

stream.Write(buffer, count, IntPtr.Zero);
stream.Commit(0);
}

//Convenience method for writing Strings to the stream
public void Write(string s)

...{
System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
byte[] pv = encoding.GetBytes(s);

Write(pv, 0, pv.GetLength(0));
}

public override void Close()

...{
if (this.stream != null)

...{
stream.Commit(0);

GC.SuppressFinalize(this);
}
}
}
值得一提的是 IStorage COM 复合文件的实现只支持 STGM_DIRECT 模式,所以文件的存取模式只能有以下三种组合:
STGM_READ | STGM_SHARE_DENY_WRITE
STGM_READWRITE | STGM_SHARE_EXCLUSIVE
STGM_READ | STGM_PRIORITY
所以在进行存取操作时都是独占式的访问,由于.Net 本身的垃圾回收机制,在使用 Storage 对象打开一个存储对象后,一定要记住操作完成后调用 Dispose 释放对象,否则下次再打开其他存储对象将会碰到访问被拒绝。