Avoid or remove lock from StgOpenStorage()

1

Greets,

I found a similar question at Opening OLE Compound Documents read-only with StgOpenStorage but that solution didn't work for me.

I try to open an outlook .msg file with StgOpenStorage(). My problem is, that StgOpenStorage always locks my file.

So, how can I avoid/remove the lock StgOpenStorage() adds to my file?

(see last method of code OpenStorage())

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;

namespace IStorageLock
{
    [ComImport]
    [Guid("0000000d-0000-0000-C000-000000000046")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IEnumSTATSTG
    {
        // The user needs to allocate an STATSTG array whose size is celt.
        [PreserveSig]
        uint Next(uint celt,
                  [MarshalAs(UnmanagedType.LPArray), Out]
                  System.Runtime.InteropServices.ComTypes.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)]
    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, Guid 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,
                         System.Runtime.InteropServices.ComTypes.FILETIME pctime,
                             System.Runtime.InteropServices.ComTypes.FILETIME patime,
                             System.Runtime.InteropServices.ComTypes.FILETIME pmtime);

        void SetClass(Guid clsid);

        void SetStateBits(uint grfStateBits, uint grfMask);

        void Stat(out System.Runtime.InteropServices.ComTypes.STATSTG pstatstg,
                  uint grfStatFlag);
    }

    class Lock
    {
        [Flags]
        public enum STGM : int {
            DIRECT           = 0x00000000,
            TRANSACTED       = 0x00010000,
            SIMPLE           = 0x08000000,
            READ             = 0x00000000,
            WRITE            = 0x00000001,
            READWRITE        = 0x00000002,
            SHARE_DENY_NONE  = 0x00000040,
            SHARE_DENY_READ  = 0x00000030,
            SHARE_DENY_WRITE = 0x00000020,
            SHARE_EXCLUSIVE  = 0x00000010,
            PRIORITY         = 0x00040000,
            DELETEONRELEASE  = 0x04000000,
            NOSCRATCH        = 0x00100000,
            CREATE           = 0x00001000,
            CONVERT          = 0x00020000,
            FAILIFTHERE      = 0x00000000,
            NOSNAPSHOT       = 0x00200000,
            DIRECT_SWMR      = 0x00400000,
        }

        [DllImport("ole32.dll")]
        private static extern int StgIsStorageFile([MarshalAs(UnmanagedType.LPWStr)]
                                                   string pwcsName);

        [DllImport("ole32.dll")]
        static extern int StgOpenStorage([MarshalAs(UnmanagedType.LPWStr)]
                                         string       pwcsName,
                                         IStorage     pstgPriority,
                                         STGM         grfMode,
                                         IntPtr       snbExclude,
                                         uint         reserved,
                                         out IStorage ppstgOpen);

        public IStorage OpenStorage(string fileName)
        {
            if (StgIsStorageFile(fileName) != 0) {
                return null;
            }

            IStorage storage = null;

            //
            // StgOpenStorage() locks file 'fileName'
            //
            // Set flags like:
            // [https://stackoverflow.com/questions/1086814/opening-ole-compound-documents-read-only-with-stgopenstorage]
            //
            int stgOpenStorage = StgOpenStorage(fileName, null,
                                                STGM.READ            |
                                                STGM.SHARE_DENY_NONE |
                                                STGM.TRANSACTED,
                                                IntPtr.Zero, 0,
                                                out storage);

            //
            // Try to rename file (for testing purposes only)
            //
            try {
                File.Move(fileName, fileName + @".renamed");
            } catch (Exception ex) {        // exception: file alreay in use by another process
                throw;
            }

            if (stgOpenStorage != 0) {
                return null;
            } else {
                return storage;
            }
        }
    }
}

Hope you can help me.

Regards,

inno

c#
winapi
com
asked on Stack Overflow Nov 2, 2009 by Inno • edited May 23, 2017 by Community

2 Answers

1

Have you tried reading the file into memory and using StgOpenStorageOnILockBytes instead?

answered on Stack Overflow Nov 3, 2009 by Mattias S
0

I have encountered a similar problem and the following code solved for me. I have changed variable name to your example

storage.Commit(0);   // storage is a pointer to IStorage in OP's question
Marshal.ReleaseComObject(storage);
storage = null;

GC.Collect();
GC.Collect();  // call twice for good measure
GC.WaitForPendingFinalizers();      

This is based on Mirosoft sample code at Extract embedded files from Office documents .

So in COM you need to IUnknown.Release and reduce the reference count to zero so the object destroys itself deterministically but the whole .NET/COM Interop is not always as simple. For IStorage it is probably better to call IStorage.Commit before calling Marshal.ReleaseComObject which does IUnknown.Release for you. In the next line setting the variable to null explicitly helps mark the object for garbage collection which we then go on to call. We call GC.Collect twice because there are multiple generations of objects waiting for collection and because this is Microsoft sample code. Lastly, another deterministic tidy up occurs on GC.WaitForPendingFinalizers.

answered on Stack Overflow Apr 9, 2017 by S Meaden

User contributions licensed under CC BY-SA 3.0