42

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: , , , , ,

42 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

  • Tibold says:

    Hi,

    Great article!
    Using your example I managed to create an opensource SVG Viewer extension. :)
    As a reference: http://code.google.com/p/svg-explorer-extension/

    Thanks!

  • Sekhar says:

    can we achieve this using the thumbnail handler which comes along when we try to add an ATL Simple Object. I am able to register the dll but i do not know why my DllGetClassObject() is never called.

    • Jeremy says:

      Sekhar, from my understanding that implementation is now deprecated under windows vista and 7, I could be wrong, but I believe that’s the case.

      • Sekhar says:

        Thanks for the reply.

        Another doubt from me.. Is there anyway in which we can get full path of file selected in explorer. I am just able to retrieve filename from IStream object in Initialize() menthod?
        I was trying it from IPersistStream but not much of use.

        Thanks

  • Rs says:

    Got the full path of the file using a different method.
    Checked with SPY++ and i went down the windows hierarchy and got the folder path. Check the code below and you will get the full folder path

    HWND hwnd = ::FindWindowEx(l_pExplorerhwnd, NULL, L”WorkerW”, NULL);
    if(hwnd)
    {
    hwnd = ::FindWindowEx(hwnd, NULL, L”ReBarWindow32″, NULL);
    if(hwnd)
    {
    hwnd = ::FindWindowEx(hwnd, NULL, L”Address Band Root”, NULL);
    if(hwnd)
    {
    hwnd = ::FindWindowEx(hwnd, NULL, L”msctls_progress32″, NULL);
    if(hwnd)
    {
    hwnd = ::FindWindowEx(hwnd, NULL, L”Breadcrumb Parent”, NULL);
    if(hwnd)
    {
    hwnd = ::FindWindowEx(hwnd, NULL, L”ToolbarWindow32″, NULL);
    if(hwnd)
    ::InternalGetWindowText (hwnd, l_szTempName, MAX_PATH);
    else
    return E_FAIL;
    }
    else
    return E_FAIL;
    }
    else
    return E_FAIL;
    }
    else
    return E_FAIL;
    }
    else
    return E_FAIL;
    }
    else
    return E_FAIL;
    }
    else
    return E_FAIL;

  • Rs says:

    Hi jeremy,

    Does your sample code work on XP and Vista too. Or do we need to do something else for Vista and XP platforms ??

    • Jeremy says:

      I know it works fine on Vista ( I used this code at work and we still have to support Vista ). I’m pretty sure it works on XP as well (we’re still supporting XP….for now), but I can check tomorrow and get back to you ( I don’t have an XP machine to test on at home ).

      • Rs says:

        Thanks a lot. Please do check at your end too.

        I have tried registering it XP. It fails. I am getting a message as “The specified Procedure could not be found”.
        I have checked it using dependency walker. There is one specific dll{mpr.dll} which is shown in red color i.e it has errors. I suppose this dll is loaded by Shlwapi.dll.
        Error Message:

        Error: At least one module has an unresolved import due to a missing export function in an implicitly dependent module.

        missing function is an export function MPR.dll i.e “WNetRestoreConnectionA”

        I am using VS2010

        Any idea regarding this.

  • Jeremy says:

    Thinking about it, I seem to remember that XP exposes a different (more limited) interface for thumbnails. I’m not sure I have a sample that supports XP, but I’ll look around.

  • Computermensch says:

    Hi.

    May be something to checl out when using managed code:

    I recently asked online about the lockup stuff here http://social.technet.microsoft.com/Forums/sa/W8ITProPreRel/thread/ed3a112e-df02-41b7-aace-09707113ae47 – concerning the locked files.

    Allthough Microsoft replied they already recorded this issue for Windows 8 somebody replied or reminded that thumbs.db are only created for shares today (since Vista). So all not beautiful – but I don’t like those “old” thumbs.db files all over using the file server … you can create a group policy or use the registry to disable the thumbs.db

    However the thumbscache.db in \appdata\local\Microsoft\Windows\Explorer will still be used.

    In effect the Lock problem using managed code could may be disappear – if it is assumed thumbs.db is legacy and should be disabled.

    The point is the lock is “experienced” only when thumbs.db’s are in general use – and that only happens with (remote) shares. Except for that thumbs.db is never in use. It’s legacy.

    And eventhough one decides to implement ones own thumbsnail provider, the thumbs.db can still be in use and Lock stuff. It’s not ones own thumbs provider problem – but a general problem. Put a video or jpeg there – and it will Lock up anymore – eventhough your own thumbsnail provider was implemented to take care special care of Microsoft implementation or design problem.

    Since I have disabled thumbs.db on shares using the registry http://support.microsoft.com/kb/2025703 (“Turn off the caching of thumbnails in hidden thumbs.db files”) – I have not experienced Lock ups.

    OK – the caching when using file shares have gone. But I am not really after getting the thumbs cached.

    I am after getting a thumb rendered – having thumbnails functionality (I can live without the caching if it Means Explorer is blocked)

    BTW The concern of the support article I referenced above matches the reply from Microsoft almost exactly:

    http://support.microsoft.com/kb/2025703

    The folder rename operation fails because thumbcache.dll still has an open handle to the local thumbs.db file and does not currently implement a mechanism to release the handle to the file in a more dynamic and timely fashion.

    Steps to reproduce: Map a drive to a NETWORK SHARE that contains several sub-folders that contains images files or PDFs

    :) So I would may be still reconsider and keep using managed code. This problem just does not go away making my own implementation running unmanaged code.

    As I wrote above. If the user dumps a jpeg or a video back into that folder on the remote share, its locked anyway.

    For the local file system this problem does not exist.

    *Multiple comments from the same user merged by Jeremy

  • Jeremy says:

    Computermensch,

    The issue the managed thumbnail handler runs into doesn’t have anything ( specifically ) to do with Thumbs.db. The issue is that the Managed Thumbnail handler has a stray stream that Explorer doesn’t release immediately, which can leave the file that needs the thumbnail open & locked, unable to be deleted. I’m sure there are issues with Thumbs.db, but that is NOT what this post of mine was about. This happened on local non-shared folders, and this was on Vista & 7 where the Thumbs.db doesn’t exist ( for non-shared folders ).

  • Anup says:

    Hello Jeremy,
    If an extension already provides some thumbnail for a file extension (because a program has been associated with it), is there anyway to override that program’ thumbnail provider to use the new provider?

    • Jeremy says:

      To override an existing Thumbnail provider you just have to overwrite the registry entry that tells it what class it uses. The class is defined by a GUID, if you download the sample here, you’ll see where it is referenced in code in Common.h. Assuming you register the DLL properly, the new provider should be used and not the old one. Now, if the old one had an installer and “fixes” itself, it could be harder to override it.

  • Anup says:

    I also had one more query. How can I debug this thumbnail provider code?

  • Andrey says:

    Hi Jeremy! That’s amazing stuff really helped it and work. But at that time have a couple questions: I need to display the image in thumbnail (which exists in my .xxx file). This file has his own structure and I needed to pull this one which is Image and convert to Bitmap. Have you any idea or in which direction i need thinking? Thanks, best regards ;)

  • Andrey says:

    Yep, thanks boys! In this case i’ve had similar idea)
    Do you have an any suggestion why IInitializeWithFile interface method never gets called?

    • Jeremy says:

      The Remarks section from MSDN for IInitializeWithFile say the following
      “Whenever possible, it is recommended that initialization be done through a stream using IInitializeWithStream. Benefits of this include increased security and stability.”

      I’m going to guess that if the WIthStream interface exists it is always going to use that one instead.

  • Andrey says:

    Thanks Jeremy for help.
    After ~25 attempts I finally find the way. All meaning was at registration stage & Thumbnail which implemented by IInitializationWithFile interface need describe and run like DisableProcessIsolation with 0×001 value. This is a key!

    • Jurgen says:

      Could you please illustrate this with some example code, if possible. I only found a way to set this in the registry.

      Thank you.

      • Jeremy says:

        Jurgen,

        Sorry it took so long to get back to you. There is sample source attached to the post (should be towards the bottom, and let me know if it doesn’t work). As per the registration for the thumbnail and the file extension, that association is only set in the registry.

  • Andrey says:

    Thanks to all of you boys, the job is done, and this stuff a very helpful. I wish you all good luck, cheers ;)

  • Brent says:

    Is this code for client-side use, or can it also be used server-side so that a website could serve up thumbnails?

    • Jeremy says:

      Brent,

      This is only desktop solution, not a server side solution to be delivered to client connections. If you’re looking to do this over the web, I’m sure there are plenty of solutions to do so. What technology are you using to try to deliver thumbnails to a client page? I’ve done this more than my fair share of times.

      • Brent says:

        Hi Jeremy,

        Thanks for the quick reply!

        Given a document stored in a db server and returned to me from a service as a stream, i’d like to send a thumbnail down to the client browser from our web app (iis7, sql server, c#, .net 4, asp.net) dynamically (so webserver-hosted office doc to image).

        I see many libraries out that convert images to thumbnails, but not any .net libraries that convert client uploads (Microsoft office files, pdfs, text files, images) to thumbnails dynamically.

        I’ve gotten a pdf to convert to a thumbnail using aspose.pdf.dll, but that’s just one file type, and it’s slow because it has to open the entire file before it can convert the 1st page to an image. I was hoping to leverage the IThumbnailProvider to accomplish this task since some office documents store a thumbnail inside of them.

  • Amir says:

    Hi Jeremy

    I wanna know what happens if we want to write a thumbnail preview for zipped package I want to write my own thumbnail preview for a .xyz package that is contained 3 different folders inside it and in one of them there is a picture (a png file). The question is now how should I convert the stream (since here stream is refers to whole zip package not my picture, correct me if I am wrong) to a BMP to return to the system.

    • Jeremy says:

      You’d have to unpack the stream to extract the file you want…or depending on the zip methodology, you may be able to just find the file in the stream and extract the data from it individually. At my place of employment, the file was a conglomeration of several different elements, with a BMP shoved somewhere towards the end (but not the very end, there was other data around it too), so I had to parse the stream to know where to look for what. Sounds like you’ll have to do something similar.

      Hope that helps.

      • Amir says:

        Tanx for your prompt answer,
        you answer makes a lot of sense and I think yes that’s exactly what I should do. yet I have one naive question when we are talking about stream what exactly we are talking about? I mean in GetThumbnail(UINT cx, HBITMAP *phbmp, WTS_ALPHATYPE *pdwAlpha)I don’t see any input to stream? I only understand that at the end I should set phbmp but how can I have access to the stream in order to parse it?

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>

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

Get Adobe Flash player