6

IThumbnailProvider Re-Visited

Posted by Jeremy on January 11, 2010 in Software, Tech |

Note for the source downloads, see the bottom of the post.

 

About 3 months ago I posted about 64-bit versions of Thumbnailers written for Vista and Windows 7 using the new “IThumbnailProvider” class.  Writing new functionality in managed code ( C# is what I used ), just made it very easy to get code up and running.  Well, with Windows 7, there was a problem.  The file was being locked by the sytem, and we couldn’t delete it, programmatically nor manually.  In fact, when attempting to do it programmatically, Windows returned that the file had indeed been deleted, yet looking at the folder the file persisted.  Almost mocking my attempts to delete it.  Then, after some magical period of time ( usually between 10 seconds and 5 minutes ) the file would finally give up, and Windows would wisk the file away to Never Never Land.

This was all very confusing, and after a while I called up Microsoft.  I got my case ID and was told that someone would call me or email me within a few days.  Later that day I got a call from someone, but after showing them with a remote meeting the problem that was happening they determined they were the wrong group to handle it.  This happened yet again, and I was passed along through 4 or 5 more people before I got to someone that I believe worked up in Washington, had a name that was easily pronounceable, and had no noticeable accent to someone that lived on the West Coast of the United States.

I was able to show him the problem, and then sent him the code that reproduced the problem.  This back and forth with Microsoft started on 11/19/2009.  On 1/5/2010 I finally got a resolution.  Don’t do it like that. It seems that with the .NET Framework, that the stream is not released immediately.  It is released when the thread dies, which who knows when this will happen.  On Vista, the thread was terminated immediately, which is why we didn’t see the problem, but on 7 things changed enough for the problem to show up.  For those interested, here is the email:

I am writing to give you an update on the status of your issue regarding why having a managed thumbnail provider for files generated by your application causes Windows Explorer on Windows 7 to open and hold onto a handle to the file prior to closing the file at some point later.

When calling a thumbnail provider, Windows Explorer creates an IStream that wraps the file by calling SHCreateStreamOnFileEx. The returned IStream object opens a handle to the file and only closes the handle when the IStream object is released. The IStream object is then used when calling the thumbnail provider’s IInitializeWithStream::Initialize method. What we found in debugging Explorer when using the managed thumbnail provider is that the reference count on the IStream object was being incremented when calling the Initialize method but never decremented when the call returned.

In discussing this issue with the .NET Framework development team, it appears that the dllhost.exe process that is hosting the managed thumbnail provider it terminating (because Explorer has released its references to the thumbnail provider) before the garbage collector has an opportunity to clean up the runtime callable wrapper (RCW) for the IStream object. Essentially, the problem is a result of different lifetime models in COM (deterministic, reference counted) and managed code (non-deterministic, garbage collected). The IStream object is essentially orphaned when the dllhost.exe process terminates.

Later, the Explorer thread that calls the thumbnail provider releases the remaining references on the IStream object by calling CoUninitialize prior to exiting the thread.

As this problem only occurs on Windows 7, I looked at the implementation of the code that handles calling the thumbnail provider on Windows 7 versus Windows Vista. The main difference is that in Windows Vista, the thread that handles calling the thumbnail provider exits almost immediately after calling the thumbnail provider. While the IStream is orphaned on Windows Vista when the dllhost.exe process terminates, the problem appears to not exist because the orphaned IStream is cleaned up when the thread exits. On Windows 7, the thread that calls the thumbnail provider waits for a period of time to see if there is any other work to be performed. The thread terminates after a period of time if there is no other work to be performed.

That said, our recommendation is that you implement your thumbnail provider using unmanaged code. Please let me know if you have any additional questions.

So, that left me having to re-implement the IThumbnailProvider again.  As I haven’t had a whole lot of experience with COM, it was a bit of a challenge.  I didn’t find a lot of example of the IThumbnailProvider online, so I thought I’d add my code here ( removing anything that is proprietary to the company I work for of course ) to help anyone else trying to get a Thumbnailer working under COM.

You will find attached in this post the empty project to use as the basis for the Thumbnail Provider.  It will compile with 7 errors.  Open Main.CPP and go to line 74 and remove the /*.  Look for “CHANGE_THIS_LINE” and change “YOUR_EXTENSION_HERE” to “ABC” or whatever your file extension is, and remove the */ at the end of the line.

Go to Common.h.  Go to Tools->Create GUID.  Select option 2, click “New GUID” and press the “Copy” button.  Paste at the bottom of the file, and you should get something like

// {C838F6C0-BC06-45da-911F-9330EE20D4E8}
DEFINE_GUID(<<name>>,
0xc838f6c0, 0xbc06, 0x45da, 0x91, 0x1f, 0x93, 0x30, 0xee, 0x20, 0xd4, 0xe8);

replace <<name>> with CLSID_SampleThumbnailProvider.

copy the first like that was created from the Create GUID tool and do this

#define szCLSID_SampleThumbnailProvider L"_PASTE_GUID_HERE"

You know how have unique Thumbnailer built off IThumbnailProvider.  Now you just have to write the code to output the thumbnail.

Make sure you run this code to register your DLLs as necessary:

system32/regsvr32 ThumbnailProviderx64.dll
syswow64/regsvr32 ThumbnailProviderx86.dll

unregistering is like this:

system32/regsvr32 /u ThumbnailProviderx64.dll
syswow64/regsvr32 /u ThumbnailProviderx86.dll

Hope this helps people.

Update 5/6/2010:  A gentleman by the name of Dan Clarke found my blog and had some problems with my sample code.  After some emails back and forth we were able to determine that he wasn’t seeing the changes to his thumbnail due to the way that Windows will cache generated thumbnails, even invalid ones, and Windows 7 is even more relentless in it’s caching.  The regeneration of Thumbnails is not triggered until the file is changed ( or a new file is created ).  Simply copying & pasting the existing file will trigger the generation of the thumbnail for the new file, while the old file will remain unchanged.

 

Update 7/6/2011: I’m glad that a year later people are still finding this useful.  Marko had asked Ralfn to link his source, but his post was about 4 months back and may not check back here.  The .NET solution is also included, but as one of their engineers told me, this approach is NOT supported, so I don’t suggest you use it.

Downloads:

I have modified the C++ sample to include Paul’s code for the GetThumbnail implementation.  This will hopefully help some with having a fully working sample.

File: IThumbnailProvider – C++

File: ThumbnailProvider - C# ( This approach is NOT supported by Microsoft )

Tags: , , , , ,

6 Comments

  • Ralfn says:

    I successfully implemented a ThumbnailProvider in C# by setting all references to IStream to null and calling GC.Collect after the image has been gathered from the stream. Works like a charm.

  • Paul says:

    Thanks Jeremy! After trying numerous samples to try and get this to work, yours was the first one that actually did – and was very easy to use to boot!

    One thing to note for people trying to use this, if it works the result is a completely blank icon (instead of the default blank page icon). I think it was you and Dan having a conversation on another page I found, and a slight variation on your suggested “Hello World” implementation of GetThumbnail is this:


    *phbmp = NULL; *pdwAlpha = WTSAT_UNKNOWN;

    ULONG_PTR token;
    GdiplusStartupInput input;
    if (Ok == GdiplusStartup(&token, &input, NULL))
    {
    //gcImage.LogBuffer();
    Bitmap * pBitmap = new Bitmap(188, 141);
    if( pBitmap )
    {
    Graphics xGraphics( pBitmap );
    Font xFont( L"Arial", 12, FontStyleRegular, UnitPoint );
    SolidBrush xBrush( Color( 0, 0, 0) );
    xGraphics.DrawString(L"Hello!", 6, &xFont, PointF(0.0f,0.0f), &xBrush );
    Color color(255, 0, 0);
    pBitmap->GetHBITMAP(color, phbmp);
    }
    }

    GdiplusShutdown(token);

    if( *phbmp != NULL )
    return NOERROR;

    return E_NOTIMPL;

    Not forgetting to add #include “Gdiplus.h” and link to “gdiplus.lib”.

    Interestingly, Dan works at the same company as myself, and sits a few desks away – yet we discovered this page separately and for different purposes.

  • MFC_Convert says:

    Thanks a lot Jeremy. The post was straightforward and easy to adapt to. Great Writeup. 10/10.

    -Cheers
    MFC convert

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Get Adobe Flash playerPlugin by wpburn.com wordpress themes

Copyright © 2009-2012 Code Monkey Codes All rights reserved.
This site is using the Desk Mess Mirrored theme, v2.0, from BuyNowShop.com.