-
Notifications
You must be signed in to change notification settings - Fork 77
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Structure Storage reference implementation
A thin wrapper around Windows Structured Storage using COM interop from CsWin32.
- Loading branch information
1 parent
b488e15
commit 2e7cfeb
Showing
9 changed files
with
854 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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_* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}, | ||
}; | ||
} | ||
} |
Oops, something went wrong.