Access violation when calling EnumElements of IStorage

332 views Asked by At

I am reading a structured storage file. And trying to get all child elements of the root structure. But I am getting access violation exception while doing so.

Here is the native methods,

[ComImport][Guid("0000000d-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IEnumSTATSTG
{
    [PreserveSig] uint Next(uint celt, [MarshalAs(UnmanagedType.LPArray), Out] System.Runtime.InteropServices.ComTypes.STATSTG[] rgelt, out uint pceltFetched);
}

[ComImport][Guid("0000000b-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IStorage
{
    [return: MarshalAs(UnmanagedType.Interface)]
    IStream OpenStream([In, MarshalAs(UnmanagedType.BStr)] string pwcsName, IntPtr reserved1, [In, MarshalAs(UnmanagedType.U4)] int grfMode, [In, MarshalAs(UnmanagedType.U4)] int reserved2);

    void EnumElements(
        /* [in] */ uint reserved1,
        /* [size_is][unique][in] */ IntPtr reserved2,
        /* [in] */ uint reserved3,
        /* [out] */ out IEnumSTATSTG ppenum);
}

    [DllImport("ole32.dll", CharSet = CharSet.Unicode)]
    internal static extern uint StgOpenStorageEx
    (
        [MarshalAs(UnmanagedType.LPWStr)] string name, uint accessMode,
        uint storageFileFormat, uint fileBuffering, IntPtr options,
             IntPtr reserved, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] ref IStorage stg
    );

And here is my calling code.

IStorage _storageObject;
// Opening file,
NativeMethods.StgOpenStorageEx(path, (uint)STM.Read | STM.ShareDenyWrite, (uint)storageFileFormat, (uint)fileBuffering,
                options, IntPtr.Zero, ref _iidIStorage, ref _storageObject);

// Here I am calling EnumElements, I get exception here.
IEnumSTATSTG pIEnumStatStg;
_storageObject.EnumElements(0, IntPtr.Zero, 0, out pIEnumStatStg);

Note that if I call another method if IStorage, like OpenStream, that works fine,

_storageObject.OpenStream(streamName, IntPtr.Zero, (int)accessMode, 0);

I tried different combinations of STM flags when I open file, but it is not working.

1

There are 1 answers

2
Hans Passant On BEST ANSWER

As you might suspect, your interface declarations are completely wrong. The names of the methods are irrelevant, like all names are in COM, it is the order of the methods that is crucial. They must co-incide with the interface's v-table. Which means that you cannot just omit methods willy-nilly.

This is a detail that's different from .NET interfaces, the CLR figures out how to bind interface methods to their implementation and it can do so because it has access to both the declaration and the implementation. But it does not have such access to COM method implementations, they are hidden quite out of view, usually in a DLL that was written in, say, C++ or Delphi.

Your OpenStream() test does not in fact work. You declared it as the 1st method but it is actually the 2nd method. You are actually calling CreateStream(). That it did not bomb was an accident, CreateStream happens to take 5 arguments as well and they are similar. That kind of luck ran out at EnumElements(), it is actually the 9th method of the interface. You are calling the 2nd method, OpenStream. With completely wrong arguments this time, kaboom.

You can take a shortcut on an interface declaration, but you'll have to use a placeholder for methods that you want to skip. Like:

[ComImport][Guid("0000000b-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IStorage
{
    void Dummy1();
    [return: MarshalAs(UnmanagedType.Interface)]
    IStream OpenStream([In, MarshalAs(UnmanagedType.BStr)] string pwcsName, IntPtr reserved1, [In, MarshalAs(UnmanagedType.U4)] int grfMode, [In, MarshalAs(UnmanagedType.U4)] int reserved2);
    void Dummy3();
    void Dummy4();
    void Dummy5();
    void Dummy6();
    void Dummy7();
    void Dummy8();
    void EnumElements(
        /* [in] */ uint reserved1,
        /* [size_is][unique][in] */ IntPtr reserved2,
        /* [in] */ uint reserved3,
        /* [out] */ out IEnumSTATSTG ppenum);
}

It is okay to omit trailing methods. You must fix your IEnumSTATSTG declaration the same way. Not the case here, but if the interface inherits from a base interface other than IUnknown or IDispatch then you must also declare the inherited methods.

Or just copy/paste the declarations from the Reference Source, if available, that's usually best. They are, here and here