In an earlier post, I explained how to host Preview Handlers in Windows Forms. You may recall that Preview Handlers are implementations of the COM interface IPreviewHandler and are initialised either by a file or a stream (depending on whether they implement IInitializeWithStream or IInitializeWithFile).

I observed recently that the MAPI Mail Preview Handler (the handler registered for Outlook .MSG files) did not implement either of the aforementioned interfaces. After some further research, I have discovered that there is a third initialisation technique – which some preview handler implementations support to the exclusion of all others.

The COM interface IInitializeWithItem is used to initialise a preview handler (or thumbnail provider, etc) with an implementation of IShellItem. The latter interface is used by the Windows Shell API and represents a file, folder or virtual item in Windows Explorer. So why insist on a shell item instead of initialising the preview handler with a file or stream? Well, you have to remember that the primary host for preview handlers is, of course, Windows Explorer – which works primarily with shell items, rather than raw files or streams. They also remove the need for preview handlers to know the location of the item on disk (if indeed it is even on disk), allowing a wider variety of content to be loaded into them.

Declaration

The declaration for the interface is as follows:

internal const string GUID_ISHELLITEM = "43826d1e-e718-42ee-bc55-a1e261c37bfe";

(Don’t worry, i’ll explain that later…)

[
    ComImport,
    InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
    Guid("7F73BE3F-FB79-493C-A6C7-7EE14E245841")
]
interface IInitializeWithItem {
    void Initialize(IShellItem psi, uint grfMode);
}

[
    ComImport,
    InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
    Guid(PreviewHandlerHost.GUID_ISHELLITEM)
]
interface IShellItem {
    void BindToHandler(
        IntPtr pbc,
        [MarshalAs(UnmanagedType.LPStruct)]Guid bhid,
        [MarshalAs(UnmanagedType.LPStruct)]Guid riid,
        out IntPtr ppv
    );
    void GetParent(out IShellItem ppsi);
    void GetDisplayName(uint sigdnName, out IntPtr ppszName);
    void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs);
    void Compare(IShellItem psi, uint hint, out int piOrder);
};

Rather unsurprisingly, there is no managed API for obtaining instances of IShellItem, and they can’t be instantiated via Activator.CreateInstance(). Instead, the Shell API function SHCreateItemFromParsingName() is needed:

[DllImport("shell32.dll", CharSet = CharSet.Unicode, PreserveSig = false)]
    static extern void SHCreateItemFromParsingName(
    [In][MarshalAs(UnmanagedType.LPWStr)] string pszPath,
    [In] IntPtr pbc, [In][MarshalAs(UnmanagedType.LPStruct)] Guid riid,
    [Out][MarshalAs(UnmanagedType.Interface, IidParameterIndex = 2)] out IShellItem ppv
);

(There are other functions that return IShellItem instances, but this one is the most appropriate for obtaining them for file system objects.)

Usage

Recalling the implementation of PreviewHandlerHost (my Windows Forms control), we can add a third condition to the section which initialises the preview handler:

if (mCurrentPreviewHandler is IInitializeWithFile) {
    ((IInitializeWithFile)mCurrentPreviewHandler).Initialize(filename, 0);
}
else if (mCurrentPreviewHandler is IInitializeWithStream) {
    mCurrentPreviewHandlerStream = File.Open(filename, FileMode.Open);
    StreamWrapper stream = new StreamWrapper(mCurrentPreviewHandlerStream);
    ((IInitializeWithStream)mCurrentPreviewHandler).Initialize(stream, 0);
}
else if (mCurrentPreviewHandler is IInitializeWithItem) {
    IShellItem shellItem;
    SHCreateItemFromParsingName(
        filename,
        IntPtr.Zero,
        new Guid(GUID_ISHELLITEM),
        out shellItem
    );
    ((IInitializeWithItem)mCurrentPreviewHandler).Initialize(shellItem, 0);
}

The reason for defining a constant for the GUID of the IShellItem interface is that it must be passed to the SHCreateItemFromParsingName() function. (There are two versions of the interface, one for compatibility with Windows XP SP1 and the other for Vista and onwards, the parameter determines which one to create.)

Final words

So, what does this enable us to do? Well, to date i’ve only come across one preview handler that insists on being initialised in this manner; the MAPI Mail Preview Handler {435fdba0-964c-43a7-8aff-cc94e21b2249}. However, in the business software I write, being able to preview Outlook messages is a significant requirement unto itself. Since Windows 7 ships with several more preview handlers than Vista did, it follows that the likelihood of encountering more which are initialised from shell items will increase.

Download

If you find my code useful, please consider making a donation.

PreviewHandlers3.zip – Visual Studio 2012 solution (includes test app) – .NET Framework 4.0 (updated 2013/10/14)

Regarding MSG Files

Several people have reported problems with the preview handler for Outlook mail messages (.msg files). To ensure that e-mail previews are displayed correctly, you will need to:

  • Ensure that the solution platform matches the architecture of the preview handler; i.e. x64 on 64-bit OS with 64-bit Office, or x86 on 32-bit OS with 32-bit Office. If you get this wrong, you will get an E_FAIL or CO_E_SERVER_EXEC_FAILURE error.
  • Use the included app.manifest file when building the project. This manifest instructs the runtime to load version 6 of the Windows Common Controls library (instead of the default version 5).
  • Run the project without attaching the debugger. In my build environment, this was the only way I could get it to work.

82 thoughts on “Preview Handlers Revisited

  1. +1
    vote

    Hi Bradley, Thanks for this.

    I also have a problem previewing PDF files, I’m using a 64 bit machine. I seem to get an E_FAIL error. I have tried to compile the application in both 64 and 32 bits. I have Adobe reader 32bit installed and I get the correct previewhandler guid from the registry (Same you gave in a previous comment).

    I also tried changing the Marshal.WriteInt64 to 32 as Fabske said. But that didn’t change anything.

    The weird thing is: I have another project which is an Outlook plug-in where it does work (Office 32bit). I compared both projects but I can’t find a difference.

    Do you have any idea what the problem is here?

    Reply

Leave a reply

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> 

required