|
|
|
date: Sat, 12 Apr 2008 18:42:13 -0500,
group: microsoft.public.platformsdk.shell
back
Thumbnail Extractor Shell Extension - "E_PENDING"
Hi folks,
(Thanks to Matt Ellis for an earlier response to some of the questions
here.)
Please pardon a COM / ATL newbie's venture into the (very) deep end of the
pool, but I'm trying to implement a pre-Vista thumbnail extractor shell
extension with VS 2008 VC++ ATL that implements the "IEIFLAG_ASYNC" protocol
for multithreaded access the way the MSDN docs seem to say that it's
supposed to be implemented -- or at least the way I read them.
According to the MSDN docs on IExtractImage::GetLocation, a) if you return
E_PENDING, you are indicating that IExtractImage::Extract can be called by a
(Shell?) worker thread, and b) if you return E_PENDING, your server must
also implement IRunnableTask. There's one other wrinkle here, which is that
the current docs indicate that Vista will actually ignore the return code
and call Extract on a worker thread regardless.
I took this to mean that a) if you return E_PENDING, then your server should
be capable of being marked with the "Both" or "Free" threading model for
best performance, b) that at least some IRunnableTask methods must be
implemented, and c) if running under Vista, both a) and b) better be true
regardless. (As it turns out, The IRunnableTask docs indicate that,
specifically for the IExtractImage case, only the "IsRunning" method needs
to be implemented, as the Extract call itself constitutes the "Run" call.
The other IRunnableTask methods are optionally implementable, but the
current docs indicate that if they are implemented, they *will* be used.)
There seem to be only a couple of comprehensive examples of how to do this
in VC++ on the web (and none in MSDN, other than a highly simplfied example
from Dino Esposito in a reproduced magazine article from many years ago).
One is on CodeProject
(http://www.codeproject.com/KB/shell/thumbextract.aspx), and the other on a
site referenced out of the comments on the CodeProject article.
In each case, the authors have marked their servers as "Apartment" threaded,
and used ATL's "CComSingleThreadModel" to create their objects. However, in
each case, in the IExtractImage::GetLocation call, the authors test the
"IEIFLAG_ASYNC" bit of the pdwFlags paramter, and return "E_PENDING" if the
bit is set. (Which it usually seems to be.) In neither case does the
author implement IRunnableTask. This appears to me to be a double-bogey by
the docs, but neither author seems to have a serious problem with it.
(Though some users have commented on occaisional problems.)
Now, my first bit of confusion here may be whether or not there's a
difference between the Shell's use of a worker thread to call Extract, and
the server's corresponding threading model, and whether it's an error for
these guys to be returning "E_PENDING" given their implementations. The
MSDN docs are very silent on what threading model the Shell - XP or Vista -
actually uses to call the server. (As an aside, it's not clear that the
pre-Vista thumbnail extraction protocol is actually even a "supported"
protocol. There doesn't seem to be any formal MSDN documentation of the
thumbnail extraction protocol that actually links the IPersistFile::Load
call to send the file path to the extraction server with the IExtractImage
calls to return the thumbnail. As near as I can tell, the protocol is
"heresay" based on a few paragraphs from a couple of Esposito's old magazine
articles reproduced in MSDN.)
Based on the examples above, along with some suggestions from the various
comments posted to the articles, I've implemented inproc server dll versions
of the extractor for "Apartment" / "CComSingleThreadModel", "Both" /
CComMultiThreadModel, and "Free" / CComMultiThreadModel. With the "Both"
and "Free" versions, I added some elementary critical section
synchronization (or at least, I think I have -- again, taken from an article
on CodeProject - http://www.codeproject.com/KB/COM/critsectionwrap.aspx). I
also added a very simplistic (maybe too simplistic)
"IRunnableTask::IsRunning" implementation.
The CodeProject critical section code that tests whether or not the critical
section needs to be created and used applies a test to determine what kind
of threading model the object was created in (it tests the return code from
a "probe" CoInitialize() call). Interestingly, even when the server is
marked "Both", the code trace indicates that the server is still created in
an STA. However, when the server is marked "Free", the code trace indicates
that the server actually is created in the MTA.
Possbily because the "Both" version appears to be just created in an STA
anyway, the extractor appears to run OK. However, when I attempt to run the
"Free" version, I get this Explorer exception in the output window of the
debugger, and the extraction protocol appears to abort silently:
"First-chance exception at 0x7c812a5b in explorer.exe: 0x8001010E: The
application called an interface that was marshalled for a different thread."
It appears that the exception occurs only *after* the IPersistFile::Load and
IExtractImage::GetLocation calls have run succesfully, and E_PENDING has
been returned from GetLocation. The Extract call never seems to get called,
nor does IRunnableTask::IsRunning seem to get pinged before this happens.
Again, I'm a total novice here, but I'm guessing this is the result of the
Shell seeing the E_PENDING return, and actually handing off an interface
pointer from the Shell MTA to an STA.
What I'm also suspecting is that if the Shell for some reason also decided
to actually load my "Both" version into the MTA instead of an STA, that it
would actually choke in the same way.
FWIW, I had already scanned some web stuff about the exception, and did go
back and comment out the stdafx.h "#define _ATL_APARTMENT_THREADED" line and
replace it with "#define _ATL_FREE_THREADED". (Aside -- In the ATL Simple
Object Wizard, I selected "Free" threading for this version -- is this an
old bug in the ATL wizard?) I've also seen a lot of stuff about the "GIT"
and Free-Threaded-Marshaller that I don't quite grok yet, but it's also not
clear to me if this something I have a lot of control over on the server
side anyway, particularly with the lack of insight into what the Shell
really wants to do here.
To keep this message a little shorter, I'll post the major code pieces in a
reply.
To end this, I guess my questions are,
1) Should I just stop worrying about Vista using a worker thread for Extract
no matter what I tell it, set "Apartment" / "CComSingleThreadModel",ignore
the IEIFLAG_ASYNC bit, *not* return E_PENDING and accept any performance
issues that arise from the marshalling (does it look from the "evidence"
like it's going to end up marshalling under the covers regardless)?
2) Is there anything that anyone could recommend to fix the "Free" threaded
(and presumably "Both" threaded) version? (Is it even remotely possible
that there is a bug in the Shell's interface pointer handling in this case?)
3) Is the critical section implementation I applied to support
multithreading in the server sensible?
4) Is the simplistic IRunnableTask::IsRunning implementation I used in the
ballpark?
5) There seem to be very few recent ATL/COM books, but the older books all
worry about marshalling performance. Is that still a big deal on modern
boxes where I'm not actually implementing an enterprise app of some kind?
Again, thanks for your patience.
date: Sat, 12 Apr 2008 18:42:13 -0500
author: linearred am
Re: Thumbnail Extractor Shell Extension - "E_PENDING"
Code for the "Free" threaded version. .h and .cpp based on Philipos
Sakellaropoulos' article from:
http://www.codeproject.com/KB/shell/thumbextract.aspx
NOTES:
* Inproc server component is marked "Free".
* stdafx.h "#define _ATL_APARTMENT_THREADED" line replaced with "#define
_ATL_FREE_THREADED"
-------------------
// myThumbGetter.h : Declaration of the CmyThumbGetter
#pragma once
#include "resource.h" // main symbols
#include "myThumbProto7_i.h"
//mine
#include <shlobj.h>
#include "ComSmartAutoCriticalSection.h"
//endmine
// CmyThumbGetter
class ATL_NO_VTABLE CmyThumbGetter :
public CComObjectRootEx<CComMultiThreadModel>,
public CComCoClass<CmyThumbGetter, &CLSID_myThumbGetter>,
public IPersistFile,
public IExtractImage2,
public IRunnableTask
{
public:
CmyThumbGetter()
{
m_lExtractionState = IRTIR_TASK_NOT_RUNNING;
}
DECLARE_REGISTRY_RESOURCEID(IDR_MYTHUMBGETTER)
DECLARE_NOT_AGGREGATABLE(CmyThumbGetter)
BEGIN_COM_MAP(CmyThumbGetter)
COM_INTERFACE_ENTRY(IPersistFile)
COM_INTERFACE_ENTRY(IExtractImage)
COM_INTERFACE_ENTRY(IExtractImage2)
COM_INTERFACE_ENTRY(IRunnableTask)
END_COM_MAP()
DECLARE_PROTECT_FINAL_CONSTRUCT()
HRESULT FinalConstruct()
{
return S_OK;
}
void FinalRelease()
{
}
public:
//IPersistFile
STDMETHODIMP GetClassID(LPCLSID) { return E_NOTIMPL; }
STDMETHODIMP IsDirty() { return E_NOTIMPL; }
STDMETHODIMP Load(LPCOLESTR, DWORD);
STDMETHODIMP Save(LPCOLESTR, BOOL) { return E_NOTIMPL; }
STDMETHODIMP SaveCompleted(LPCOLESTR) { return E_NOTIMPL; }
STDMETHODIMP GetCurFile(LPOLESTR*) { return E_NOTIMPL; }
//IExtractImage
STDMETHODIMP GetLocation(LPWSTR pszPathBuffer,
DWORD cchMax,
DWORD *pdwPriority,
const SIZE *prgSize,
DWORD dwRecClrDepth,
DWORD *pdwFlags);
STDMETHODIMP Extract(HBITMAP*);
// IExtractImage2
STDMETHODIMP GetDateStamp(FILETIME *pDateStamp);
//IRunnableTask
STDMETHODIMP Run( void) { return E_NOTIMPL; }
STDMETHODIMP Kill( BOOL bWait) { return E_NOTIMPL; }
STDMETHODIMP Suspend( void) { return E_NOTIMPL; }
STDMETHODIMP Resume( void) { return E_NOTIMPL; }
ULONG STDMETHODCALLTYPE IsRunning(void);
protected:
SIZE m_bmSize;
int m_nColorDepth;
TCHAR m_szFile[500];
private:
CComSmartAutoCriticalSection m_csSafeAccess;
LONG m_lExtractionState;
};
OBJECT_ENTRY_AUTO(__uuidof(myThumbGetter), CmyThumbGetter)
-------------
// myThumbGetter.cpp : Implementation of CmyThumbGetter
#include "stdafx.h"
#include "myThumbGetter.h"
//mine
#include <shlobj.h>
#include <gdiplus.h>
using namespace Gdiplus;
// CmyThumbGetter
STDMETHODIMP CmyThumbGetter::Load(LPCOLESTR wszFile, DWORD dwMode)
{
m_csSafeAccess.Lock();
USES_CONVERSION;
_tcscpy_s(m_szFile, OLE2T((WCHAR*)wszFile));
m_csSafeAccess.Unlock();
return S_OK;
};
STDMETHODIMP CmyThumbGetter::GetLocation(LPWSTR pszPathBuffer,
DWORD cchMax, DWORD *pdwPriority,
const SIZE *prgSize, DWORD dwRecClrDepth,
DWORD *pdwFlags) {
m_csSafeAccess.Lock();
m_bmSize = *prgSize;
m_nColorDepth = dwRecClrDepth;
*pdwFlags |= IEIFLAG_CACHE;
if (*pdwFlags & IEIFLAG_ASYNC) {
m_csSafeAccess.Unlock();
return E_PENDING;
} else {
m_csSafeAccess.Unlock();
return NOERROR;
}
//m_csSafeAccess.Unlock(); Performed.
}
STDMETHODIMP CmyThumbGetter::GetDateStamp(FILETIME *pDateStamp)
{
m_csSafeAccess.Lock();
FILETIME ftCreationTime,ftLastAccessTime,ftLastWriteTime;
// open the file and get last write time
HANDLE hFile = CreateFile(m_szFile,GENERIC_READ,FILE_SHARE_READ,NULL,
OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if(!hFile) return E_FAIL;
GetFileTime(hFile,&ftCreationTime,&ftLastAccessTime,&ftLastWriteTime);
CloseHandle(hFile);
*pDateStamp = ftLastWriteTime;
m_csSafeAccess.Unlock();
return NOERROR;
}
STDMETHODIMP CmyThumbGetter::Extract(HBITMAP* phBmpThumbnail)
{
m_csSafeAccess.Lock();
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
{
Bitmap bmOriginal(m_szFile, FALSE);
int nOriginalWidth = bmOriginal.GetWidth();
int nOriginalHeight = bmOriginal.GetHeight();
int nPixelFormat(0);
switch (m_nColorDepth) {
case 24:
nPixelFormat = PixelFormat24bppRGB;
break;
case 32:
nPixelFormat = PixelFormat32bppARGB;
break;
default:
nPixelFormat = PixelFormat24bppRGB;
}
Bitmap bmScaledBitmap(m_bmSize.cx, m_bmSize.cy, nPixelFormat);
Graphics gGraphics(&bmScaledBitmap);
gGraphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);
Color bgColor(255, 255, 255);
gGraphics.Clear(bgColor);
int nScaledHt = (nOriginalHeight * 96) / nOriginalWidth;
gGraphics.DrawImage(&bmOriginal, 0, 0, 96, nScaledHt);
bmScaledBitmap.GetHBITMAP(bgColor, phBmpThumbnail);
}
Gdiplus::GdiplusShutdown(gdiplusToken);
m_lExtractionState = IRTIR_TASK_FINISHED;
m_csSafeAccess.Unlock();
return NOERROR;
}
ULONG STDMETHODCALLTYPE CmyThumbGetter::IsRunning(void) {
LONG lExtractionState;
m_csSafeAccess.Lock();
lExtractionState = m_lExtractionState;
m_csSafeAccess.Unlock();
return lExtractionState;
}
------------
(Below taken from Jeremiah Talkar's article on CodeProject:
http://www.codeproject.com/KB/COM/critsectionwrap.aspx)
-------------
//ComSmartAutoCriticalSection.h
#pragma once
class CComSmartAutoCriticalSection
{
public:
CComSmartAutoCriticalSection()
{
if (SUCCEEDED(::CoInitialize(NULL)))
{
::CoUninitialize();
m_bInMTA = FALSE;
}
else
{
m_bInMTA = TRUE;
}
if (m_bInMTA)
::InitializeCriticalSection(&m_csSafeAccess);
}
~CComSmartAutoCriticalSection()
{
if (m_bInMTA)
::DeleteCriticalSection(&m_csSafeAccess);
}
void Lock()
{
if (m_bInMTA)
::EnterCriticalSection(&m_csSafeAccess);
}
void Unlock()
{
if (m_bInMTA)
::LeaveCriticalSection(&m_csSafeAccess);
}
private:
BOOL m_bInMTA;
CRITICAL_SECTION m_csSafeAccess;
};
date: Sat, 12 Apr 2008 18:46:11 -0500
author: linearred am
|
|