3
\$\begingroup\$

.NET tantalizingly exposes StorageInfo and StreamInfo as public classes, but hides the seminal StorageRoot as an internal class, leaving the user no way of creating or opening a Structured Storage from a file or stream.

Microsoft's Reference Source details the source behind StorageRoot which inherits from StorageInfo, so rather than copy the code, I've chosen to expose the static methods of StorageRoot via reflection, so that I can get a reference to the root storage of a File or Stream. I've also exposed a Close method to close an open root StorageInfo.

using System;
using System.IO;
using System.IO.Packaging;
using System.Reflection;

namespace StructuredStoreageTest
{
    public static class StorageRoot
    {
        public static StorageInfo CreateOnStream(Stream baseStream)
        {
            return (StorageInfo)InvokeStorageRootMethod(null, "CreateOnStream", baseStream);
        }

        public static StorageInfo CreateOnStream(Stream baseStream, FileMode mode)
        {
            return (StorageInfo)InvokeStorageRootMethod(null, "CreateOnStream", baseStream, mode);
        }

        public static StorageInfo Open(string path)
        {
            return (StorageInfo)InvokeStorageRootMethod(null, "Open", path);
        }

        public static StorageInfo Open(string path, FileMode mode)
        {
            return (StorageInfo)InvokeStorageRootMethod(null, "Open", path, mode);
        }

        public static StorageInfo Open(string path, FileMode mode, FileAccess access)
        {
            return (StorageInfo)InvokeStorageRootMethod(null, "Open", path, mode, access);
        }

        public static StorageInfo Open(string path, FileMode mode, FileAccess access, FileShare share)
        {
            return (StorageInfo)InvokeStorageRootMethod(null, "Open", path, mode, access, share);
        }

        public static StorageInfo Open(string path, FileMode mode, FileAccess access, FileShare share, int sectorSize)
        {
            return (StorageInfo)InvokeStorageRootMethod(null, "Open", path, mode, access, share, sectorSize);
        }

        public static void Close(StorageInfo storageRoot)
        {
            InvokeStorageRootMethod(storageRoot, "Close");
        }

        private static object InvokeStorageRootMethod(StorageInfo storageRoot, string methodName, params object[] methodArgs)
        {
            Type storageRootType = typeof(StorageInfo).Assembly.GetType("System.IO.Packaging.StorageRoot", true, false);
            return storageRootType.InvokeMember(methodName,
                BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | 
                BindingFlags.NonPublic | BindingFlags.InvokeMethod,
                null, storageRoot, methodArgs);
        }
    }
}

I can then use StorageRoot as follows:

MemoryStream myStream = new MemoryStream(streamBytes);
StorageInfo myRoot = StorageRoot.CreateOnStream(myStream);

StreamInfo dataStreamInfo = myRoot.GetStreamInfo("data");
Stream dataStream = dataStreamInfo.GetStream();
//read/process the stream
dataStream.Dispose();

StorageRoot.Close(myRoot);

Is relying upon Reflection the best alternative here? I could reproduce the StorageRoot code from Reference Source, and make StorageRoot an instance class, I suppose.

\$\endgroup\$
  • \$\begingroup\$ What does this solution make possible which isn't doable with the availabale public APIs? I think I'm missing the point of it. \$\endgroup\$ – t3chb0t Sep 10 '17 at 17:50
  • \$\begingroup\$ @t3chb0t it leverages/exposes an existing but internal implementation. Using the public API would entail inheriting StorageInfo and making an entirely new implementation. This solution enables creating/opening the internal structured storage of - entirely hypothetical idea here - say, an MS-Access form or report, and access things that aren't otherwise accessible, not even through interop assemblies and the host application object model. \$\endgroup\$ – Mathieu Guindon Sep 11 '17 at 13:54
  • 1
    \$\begingroup\$ @t3chb0t I could have used the API functions, and I could have written my own structured storage parser (the spec is documented), but wrapping an existing implementation seemed easier and guaranteed to be consistent with MS' handling of structured storage. I wasn't aiming to reinvent the wheel, but rather to make the wheel accessible. \$\endgroup\$ – ThunderFrame Sep 11 '17 at 20:14
3
\$\begingroup\$

A stream-like class that doesn't implement IDisposable and needs to be closed manually, and worked with static methods, breaks POLA/POLS.

I would expect to be able to do this:

using(var ms = new MemoryStream(bytes))
using(var root = new StorageRoot(ms))
{
    // do stuff
}

I like the reflection idea to access these otherwise hidden gems, however in a code base that uses objects in a standard/idiomatic way, I see this one sticking out like a sore thumb.

I'd definitely do whatever it takes to make it work with a proper instance.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.