Posts
61
Comments
90
Trackbacks
0
Writing Windows Shell Extension with .NET 4 - Part 3

Sample download

In Part 1 and Part 2 of the series, we gave an introduction of writing Windows Shell extension in .NET Framework 4, and demonstrated a "skeleton" Context Menu Handler, and a "skeleton" Infotip Handler.


- Context Menu Handler


- Infotip Handler

You are looking at the third part of the series. It introduces writing Windows Shell Thumbnail Handler with .NET Framework 4.

CSShellExtThumbnailHandler:   Shell thumbnail handler (C#) 
VBShellExtThumbnailHandler:   Shell thumbnail handler (VB.NET)
CppShellExtThumbnailHandler: Shell thumbnail handler (C++)


A thumbnail image handler provides an image to represent the item. It lets you customize the thumbnail of files with a specific file extension. Windows Vista and newer operating systems make greater use of file-specific thumbnail images than earlier versions of Windows. Thumbnails of 32-bit resolution and as large as 256x256 pixels are often used. File format owners should be prepared to display their thumbnails at that size.

Demo

Here is a quick demo of the thumbnail image handler code sample. After you successfully build the sample project in Visual Studio 2010, you will get a DLL: CSShellExtThumbnailHandler.dll. Run 'Visual Studio
Command Prompt (2010)' (or 'Visual Studio x64 Win64 Command Prompt (2010)' if you are on a x64 operating system) in the Microsoft Visual Studio 2010 \ Visual Studio Tools menu as administrator. Navigate to the folder that contains the build result CSShellExtThumbnailHandler.dll and enter the command:

    Regasm.exe CSShellExtThumbnailHandler.dll /codebase

The thumbnail handler is registered successfully if the command prints:

    "Types registered successfully"

Step2. Find the chocolatechipcookies.recipe file in the sample folder. You will see a picture of chocoate chip cookies as its thumbnail.

The .recipe file type is simply an XML file registered as a unique file name extension. It includes an element called "Picture", embedding an image file. The thumbnail handler extracts the embedded image and asks the Shell to display it as a thumbnail.

Step3. In the same Visual Studio command prompt, run the command

    Regasm.exe CSShellExtThumbnailHandler.dll /unregister

to unregister the Shell thumbnail handler.

Implementation Details

A. Creating and configuring the project

In Visual Studio 2010, create a Visual C# / Windows / Class Library project named "CSShellExtThumbnailHandler". Open the project properties, and in the Signing page, sign the assembly with a strong name key file.

-----------------------------------------------------------------------------

B. Implementing a basic Component Object Model (COM) DLL

Shell extension handlers are all in-process COM objects implemented as DLLs. Making a basic .NET COM component is very straightforward. You just need to define a 'public' class with ComVisible(true), use the Guid attribute to specify its CLSID, and explicitly implements certain COM interfaces. For example,

    [ClassInterface(ClassInterfaceType.None)]
    [Guid("2A736503-DDE4-4876-801D-60063E9E2215"), ComVisible(true)]
    public class SimpleObject : ISimpleObject
    {
        ... // Implements the interface
    }

You even do not need to implement IUnknown and class factory by yourself because .NET Framework handles them for you.

-----------------------------------------------------------------------------

C. Implementing the thumbnail handler and registering it for a certain file class

-----------
Implementing the thumbnail handler:

The FileThumbnailProvider.cs file defines an thumbnail handler. The thumbnail handler implements the IInitializeWithStream and IThumbnailProvider interfaces.

    [ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("e357fccd-a995-4576-b01f-234630154e96")]
    internal interface IThumbnailProvider
    {
        void GetThumbnail(uint cx, out IntPtr hbmp, out WTS_ALPHATYPE dwAlpha);
    }

    [ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("b824b49d-22ac-4161-ac8a-9916e8fa3f7f")]
    internal interface IInitializeWithStream
    {
        void Initialize(IStream stream, STGM grfMode);
    }

    [ClassInterface(ClassInterfaceType.None)]
    [Guid("2A736503-DDE4-4876-801D-60063E9E2215"), ComVisible(true)]
    public class FileThumbnailProvider : IInitializeWithStream, IThumbnailProvider
    {
        #region IInitializeWithStream Members

        public void Initialize(IStream stream, STGM grfMode)
        {
            ...
        }

        #endregion  


        #region IThumbnailProvider Members

        public void GetThumbnail(uint cx, out IntPtr hbmp, out WTS_ALPHATYPE dwAlpha)
        {
            ...
        }

        #endregion
    }

When you write your own handler, you must create a new CLSID by using the "Create GUID" tool in the Tools menu for the shell extension class, and specify the value in the Guid attribute.

    ...
    [Guid("2A736503-DDE4-4876-801D-60063E9E2215"), ComVisible(true)]
    public class FileThumbnailProvider : ...


  1. Implementing IThumbnailProvider

  The IThumbnailProvider interface has been introduced in Windows Vista to make providing a thumbnail easier and more straightforward than in the past, when IExtractImage would have been used instead. Note, that existing code that uses IExtractImage is still valid under Windows Vista. However, IExtractImage is not supported in the Details pane.

  IThumbnailProvider has only one method—GetThumbnail—that is called with the largest desired size of the image, in pixels. Although the parameter is called cx, this is used as the maximum size of both the x and y dimensions. If the retrieved thumbnail is not square, then the longer axis is limited by cx and the aspect ratio of the original image respected.

  On exit, GetThumbnail provides a handle to the retrieved image. It also provides a value that indicates the color format of the image and whether it has valid alpha information.

    public void GetThumbnail(uint cx, out IntPtr hbmp, out WTS_ALPHATYPE dwAlpha)
    {
        // Read the base64-encoded string value representing the thumbnail image
        // from the source stream.
        XDocument doc = XDocument.Load(this._stream);
        var picture = doc.Element("Recipe").Element("Attachments").Element("Picture");
        // The XML may provide images of different sizes, and the code can query
        // image matching the desired size (cx). For simplicity, this sample
        // omits the cx paramter and provides only one image for all situations.

        string imageString = picture.Attribute("Source").Value;

        // Convert the base64-encoded string to a stream.
        byte[] buffer = Convert.FromBase64String(imageString);
        Stream imageStream = new MemoryStream(buffer);

        // Construct a bitmap from the stream and get its handle.
        Bitmap bmp = new Bitmap(imageStream);
        hbmp = bmp.GetHbitmap();

        // Set the alpha type of the thumbnail image.
        dwAlpha = WTS_ALPHATYPE.WTSAT_ARGB;
    }

  The .recipe file type is simply an XML file registered as a unique file name extension. It includes an element called Picture that embeds images to be used as the thumbnail for this particular .recipe file. The XML may provide images of different sizes, and the code can query image matching the desired size specified by the cx parameter of GetThumbnail. For simplicity, this sample omits the cx paramter and provides only one image for all situations.

  2. Implementing IInitializeWithStream/IInitializeWithItem/IInitializeWithFile

  IThumbnailProvider must always be implemented in concert with one of these interfaces:
 
    IInitializeWithStream - provides the file stream
    IInitializeWithItem - provides the IShellItem
    IInitializeWithFile - provides the file path

  Whenever possible, it is recommended that initialization be done through a stream using IInitializeWithStream. Benefits of this include increased security and stability.

    private ReadOnlyIStreamStream _stream = null;

    public void Initialize(IStream stream, STGM grfMode)
    {
        // A handler instance should be initialized only once in its lifetime.
        if (this.stream == null)
        {
            // Initialize the stream if it has not been initialized yet.
            this._stream = new ReadOnlyIStreamStream(stream);
        }
        else
        {
            Marshal.ThrowExceptionForHR(WinError.HRESULT_FROM_WIN32(
                WinError.ERROR_ALREADY_INITIALIZED));
        }
    }

-----------
Registering the handler for a certain file class:

Thumbnail handlers can be associated with a file class. The handlers are registered by setting the default value of the following registry key to be the CLSID the handler class.

    HKEY_CLASSES_ROOT\<File Type>\shellex\{e357fccd-a995-4576-b01f-234630154e96}

The registration of the thumbnail handler is implemented in the Register method of FileThumbnailProvider. The ComRegisterFunction attribute attached to the method enables the execution of user-written code other than the basic registration of the COM class. Register calls the ShellExtReg.RegisterShellExtThumbnailHandler method in ShellExtLib.cs to associate the handler with a certain file type. If the file type starts with '.', it tries to read the default value of the HKCR\<File Type> key which may contain the Program ID to which the file type is linked. If the default value is not empty, use the Program ID as the file type to proceed the registration.

For example, this code sample associates the handler with '.recipe' files. The following keys and values are added in the registration process of the sample handler.

    HKCR
    {
        NoRemove .recipe
        {
            NoRemove shellex
            {
                {e357fccd-a995-4576-b01f-234630154e96} =
                    s '{2A736503-DDE4-4876-801D-60063E9E2215}'
            }
        }
    }

The unregistration is implemented in the Unregister method of FileThumbnailProvider. Similar to the Register method, the ComUnregisterFunction attribute attached to the method enables the execution of user-written code during the unregistration process. It removes the registry key: HKCR\<File Type>\shellex\{e357fccd-a995-4576-b01f-234630154e96}.

Diagnostic:

Debugging thumbnail handlers is difficult for several reasons.

1) The Windows Explorer hosts thumbnail providers in an isolated process to get robustness and improve security. Because of this it is difficult to debug your handler as you cannot set breakpoints on your code in the explorer.exe process as it is not loaded there. The isolated process is DllHost.exe and this is used for other purposes so finding the right instance of this process is difficult.

2) Once a thumbnail is computed for a particular file it is cached and your handler won't be called again for that item unless you invalidate the cache by updating the modification date of the file. Note that this cache works even if the files are renamed or moved.

Given all of these issues the easiest way to debug your code in a test application then once you have proven it works there test it in the context of the explorer.

Another thing to do is to disable the process isolation feature of explorer. You can do this by putting the following named value on the CLSID of your handler

    HKCR\CLSID\{CLSID of Your Handler}
        DisableProcessIsolation=REG_DWORD:1

Be sure to not ship your handler with this on as customers require the security and robustness benefits of the isolated process feature.

Download

http://1code.codeplex.com/releases

And find the CS/VB/CppShellExtThumbnailHandler samples in the Visual Studio 2010 folder.

If you have any feedback about the samples, please email onecode@microsoft.com.

posted on Monday, November 8, 2010 8:48 PM Print
Comments
Gravatar
# re: Writing Windows Shell Extension with .NET 4 - Part 3
Miguelit0
11/30/2010 12:47 AM
First of all, thanks for your article.

Unfortunately, I can't get it to work at all. I've tried the cpp and the cs versions and in both cases simply nothing happens or I get the default infotip. Tried reg/unreg, restarting explorer, disabling process isolation, changing file to invalidate the cache...nothing.

I'm using Windows 7 64bit and I have tried another example some time ago. After unsuccessfully wasting hours I gave up, back then and now.

With Windows XP, shell extensions used to work well. But using Win7 there must be some trick. Googling yielded a bunch of people with the same problems but no solutions at all...

greetings
Miguelit0

Post Comment

Title *
Name *
Email
Comment *  
 
Advertise on this site through Lake Quincy Media
Tag Cloud