Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Structure Storage reference implementation #166

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions OpenMcdf.sln
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenMcdf.Test", "sources\Te
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenMcdf.Benchmark", "sources\Test\OpenMcdf.Benchmark\OpenMcdf.Benchmark.csproj", "{B3645D34-1E22-4BCC-8956-A8A56FA9F114}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StructuredStorage", "StructuredStorage\StructuredStorage.csproj", "{543A2F13-F9B1-4B9F-B982-EECC6F44F0E0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -103,6 +105,10 @@ Global
{B3645D34-1E22-4BCC-8956-A8A56FA9F114}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B3645D34-1E22-4BCC-8956-A8A56FA9F114}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B3645D34-1E22-4BCC-8956-A8A56FA9F114}.Release|Any CPU.Build.0 = Release|Any CPU
{543A2F13-F9B1-4B9F-B982-EECC6F44F0E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{543A2F13-F9B1-4B9F-B982-EECC6F44F0E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{543A2F13-F9B1-4B9F-B982-EECC6F44F0E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{543A2F13-F9B1-4B9F-B982-EECC6F44F0E0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
42 changes: 42 additions & 0 deletions StructuredStorage/LockBytes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.System.Com.StructuredStorage;

namespace StructuredStorage;

/// <summary>
/// Encapsulates <c>ILockBytes</c> over an HGlobal allocation.
/// </summary>
internal sealed class LockBytes : IDisposable
{
readonly ILockBytes lockBytes;
private bool disposedValue;

public LockBytes(int count)
{
IntPtr hGlobal = Marshal.AllocHGlobal(count);
HRESULT hr = PInvoke.CreateILockBytesOnHGlobal((HGLOBAL)hGlobal, true, out lockBytes);
hr.ThrowOnFailure();
}

public void Dispose()
{
if (disposedValue)
return;

int count = Marshal.ReleaseComObject(lockBytes);
Debug.Assert(count == 0);

disposedValue = true;
GC.SuppressFinalize(this);
}

~LockBytes()
{
Dispose();
}

internal ILockBytes ILockBytes => lockBytes;
}
6 changes: 6 additions & 0 deletions StructuredStorage/NativeMethods.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"$schema": "https://aka.ms/CsWin32.schema.json",
"wideCharOnly": true,
"emitSingleFile": true,
"public": false
}
37 changes: 37 additions & 0 deletions StructuredStorage/NativeMethods.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Structured storage

// Functions
CreateILockBytesOnHGlobal
PropVariantToVariant
ReadClassStg
ReadClassStm
SHCreateItemFromParsingName
SHCreateStreamOnFile
StgCreateDocfileOnILockBytes
StgCreateStorageEx
StgIsStorageFile
StgOpenStorage
StgOpenStorageEx
VariantClear
VariantToPropVariant
WriteClassStg
WriteClassStm

// Interfaces
IEnumSTATPROPSETSTG
IEnumSTATPROPSTG
IEnumSTATSTG
ILockBytes
IPropertySetStorage
IPropertyStorage
IRootStorage
IStorage
IStream

// Enumerations
PROPSETFLAG_*
STGTY

// Constants
STG_E_*
PROPSETFLAG_*
50 changes: 50 additions & 0 deletions StructuredStorage/PropertySetStorage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using Windows.Win32;
using Windows.Win32.System.Com.StructuredStorage;

namespace StructuredStorage;

/// <summary>
/// Wraps <c>IPropertySetStorage</c>.
/// </summary>
public sealed class PropertySetStorage
{
/// <summary>
/// PROPSETFLAG constants.
/// </summary>
[Flags]
#pragma warning disable CA1008
public enum Flags
{
Default = (int)PInvoke.PROPSETFLAG_DEFAULT,
NonSimple = (int)PInvoke.PROPSETFLAG_NONSIMPLE,
ANSI = (int)PInvoke.PROPSETFLAG_ANSI,
Unbuffered = (int)PInvoke.PROPSETFLAG_UNBUFFERED,
CaseSensitive = (int)PInvoke.PROPSETFLAG_CASE_SENSITIVE,
}
#pragma warning restore CA1008

private readonly IPropertySetStorage propSet; // Cast of IStorage does not need disposal

internal PropertySetStorage(IStorage storage)
{
propSet = (IPropertySetStorage)storage;
}

public PropertyStorage Create(Guid formatID, StorageModes mode) => Create(formatID, Flags.Default, mode, Guid.Empty);

public PropertyStorage Create(Guid formatID, Flags flags = Flags.Default, StorageModes mode = StorageModes.ShareExclusive | StorageModes.AccessReadWrite) => Create(formatID, flags, mode, Guid.Empty);

public unsafe PropertyStorage Create(Guid formatID, Flags flags, StorageModes mode, Guid classID)
{
propSet.Create(&formatID, &classID, (uint)flags, (uint)mode, out IPropertyStorage stg);
return new(stg);
}

public unsafe PropertyStorage Open(Guid formatID, StorageModes mode = StorageModes.ShareExclusive | StorageModes.AccessReadWrite)
{
propSet.Open(&formatID, (uint)mode, out IPropertyStorage propStorage);
return new(propStorage);
}

public unsafe void Remove(Guid formatID) => propSet.Delete(&formatID);
}
167 changes: 167 additions & 0 deletions StructuredStorage/PropertyStorage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
using System.Collections;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.System.Com.StructuredStorage;

namespace StructuredStorage;

/// <summary>
/// Enumerates <c>STATPROPSTG</c> elements from a <c>PropertyStorage</c>.
/// </summary>
internal sealed class StatPropStgEnumerator : IEnumerator<STATPROPSTG>
{
readonly IEnumSTATPROPSTG enumerator;
STATPROPSTG propStat;

public STATPROPSTG Current => propStat;

object IEnumerator.Current => propStat;

public unsafe StatPropStgEnumerator(IPropertyStorage propertyStorage)
{
propertyStorage.Enum(out enumerator);
}

public unsafe void Dispose()
{
FreeName();

Marshal.ReleaseComObject(enumerator);
}

private unsafe void FreeName()
{
Marshal.FreeCoTaskMem((nint)propStat.lpwstrName.Value);
propStat.lpwstrName = null;
}

public unsafe bool MoveNext()
{
FreeName();

fixed (STATPROPSTG* statPtr = &propStat)
{
uint fetched;
enumerator.Next(1, statPtr, &fetched);
return fetched > 0;
}
}

public void Reset()
{
FreeName();

enumerator.Reset();
}
}

/// <summary>
/// Creates an enumerator for <c>STATPROPSTG</c> elements from a <c>PropertyStorage</c>.
/// </summary>
internal sealed class StatPropStgCollection : IEnumerable<STATPROPSTG>
{
readonly IPropertyStorage propertyStorage;

public StatPropStgCollection(IPropertyStorage propertyStorage)
{
this.propertyStorage = propertyStorage;
}

public IEnumerator<STATPROPSTG> GetEnumerator() => new StatPropStgEnumerator(propertyStorage);

IEnumerator IEnumerable.GetEnumerator() => new StatPropStgEnumerator(propertyStorage);
}

/// <summary>
/// Wraps <c>IPropertyStorage</c>.
/// </summary>
public sealed class PropertyStorage : IDisposable
{
private readonly IPropertyStorage propertyStorage;
private bool disposed;

internal unsafe PropertyStorage(IPropertyStorage propertyStorage)
{
this.propertyStorage = propertyStorage;
StatPropStgCollection = new(propertyStorage);

STATPROPSETSTG prop;
this.propertyStorage.Stat(&prop);
}

#region IDisposable Members

public void Dispose()
{
if (disposed)
return;

int count = Marshal.ReleaseComObject(propertyStorage);
Debug.Assert(count == 0);

disposed = true;
}

#endregion

internal StatPropStgCollection StatPropStgCollection { get; }

public void Flush(CommitFlags flags = CommitFlags.Default) => propertyStorage.Commit((uint)flags);

public unsafe void Remove(int propertyID)
{
PROPSPEC propspec = new()
{
ulKind = PROPSPEC_KIND.PRSPEC_PROPID,
Anonymous = new PROPSPEC._Anonymous_e__Union()
{
propid = (uint)propertyID,
},
};
propertyStorage.DeleteMultiple(1, &propspec);
}

public void Revert() => propertyStorage.Revert();

public unsafe object? this[int propertyID]
{
get
{
PROPSPEC spec = PropVariantExtensions.CreatePropSpec(PROPSPEC_KIND.PRSPEC_PROPID, propertyID);

var variants = new PROPVARIANT[1];
propertyStorage.ReadMultiple(1, &spec, variants);
HRESULT hr = PInvoke.PropVariantToVariant(variants[0], out object variant);
hr.ThrowOnFailure();
return variant;
}

set
{
PROPSPEC spec = PropVariantExtensions.CreatePropSpec(PROPSPEC_KIND.PRSPEC_PROPID, propertyID);

HRESULT hr = PInvoke.VariantToPropVariant(value, out PROPVARIANT pv);
hr.ThrowOnFailure();

PROPVARIANT[] pvs = [pv];
propertyStorage.WriteMultiple(1, &spec, pvs, 2);
}
}
}

static class PropVariantExtensions
{
public static PROPSPEC CreatePropSpec(PROPSPEC_KIND kind, int propertyID)
{
return new PROPSPEC
{
ulKind = kind,
Anonymous = new PROPSPEC._Anonymous_e__Union
{
propid = (uint)propertyID,
},
};
}
}
Loading