mike replied on 04-Aug-07 10:24 AM
So, the thundering silence means nobody has any idea or nobody cares.
Fair enough.

In either event, how about this: Is it reasonable to suppose that, if
I see my QueryInterface method returning a pointer to the correct
interface (i.e., XPersistFile::QueryInterface() has called pThis-
XExtractIcon interface, as it should, with a result code of S_OK),
that the shell should then call GetIconLocation() and Extract() as one
would expect?

To sum up: Does anyone have any idea why, after calling
QueryInterface() and getting back what appears to be a valid pointer,
the shell would fail to follow up with a call into my ExtractIcon
interface?

Thanks.




Michael Phillips, Jr. replied on 04-Aug-07 11:18 AM
If you followed the guidelines listed here:
http://msdn2.microsoft.com/EN-US/library/aa969357.aspx

And your icon handler is registered properly,
then one would expect GetIconLocation and Extract
to be called for the file extension that you registered your icon handler
for.

Windows 7 - MFC Icon Handler's IExtractIcon::GetIconLocation() never gets call

Asked By noma on 01-Aug-07 06:32 PM
I'm using the MFC COM macros to implement an Icon Handler. The DLL gets
loaded, I eventually get a call to QueryInterface() (actully,
IPersistFile::QueryInterface()), and eventually a call to
IPersistFile::Load(). Next, I get another IPersistFile::QueryInterface call
which returns a pointer to the IExtractIcon interface, but none of the
IExtractIcon methods ever get called (except for AddRef() and Release());
specifically, GetIconLocation() is never called, and neither is ExtractIcon().

I've run this in the VS debugger, attaching to the explorer window and
putting breakpoints on all of my rouintes. Everything *seems* fine, except
that IExtractIcon never gets called.

I have to assume that I've done something wrong somewhere. My class
declaration looks like this:

class CCMIconHandler :	public CWinApp
{
private: typedef CWinApp inherited;

public:
DECLARE_DYNCREATE(CCMIconHandler);
DECLARE_OLECREATE(CCMIconHandler);

CCMIconHandler	(void);
virtual			~CCMIconHandler	(void);

virtual  BOOL	InitInstance	(void);

virtual  int	ExitInstance	(void);

protected:
DECLARE_INTERFACE_MAP()

/*
IPersistFile.
*/
BEGIN_INTERFACE_PART(PersistFile, IPersistFile)

STDMETHOD_(HRESULT, GetClassID)		(
CLSID*							pClassID);

STDMETHOD_(HRESULT, Load)			(
LPCOLESTR						pszFileName,
DWORD							dwMode);

STDMETHOD_(HRESULT, GetCurFile)		(
LPOLESTR*						pszFileName);

STDMETHOD_(HRESULT, IsDirty)		(void);

STDMETHOD_(HRESULT, Save)			(
LPCOLESTR						pszFileName,
BOOL							fRemember);

STDMETHOD_(HRESULT, SaveCompleted)	(
LPCOLESTR						pszFileName);

END_INTERFACE_PART(PersistFile)

/*
IExtractIcon
*/
BEGIN_INTERFACE_PART(ExtractIcon, IExtractIcon)

STDMETHOD_(HRESULT, Extract)		(
LPCTSTR							pszFile,
UINT							nIconIndex,
HICON*							phiconLarge,
HICON*							phiconSmall,
UINT							nIconSize);

STDMETHOD_(HRESULT, GetIconLocation)(
UINT							uFlags,
LPTSTR							smIconFile,
UINT							cchMax,
int*							piIndex,
UINT*							pwFlags);

END_INTERFACE_PART(ExtractIcon)

protected:
CString		fFileName;		// Name of file for which icon is requested.

};

The code (more or less) looks like this:

/*
Allow dynamic creation of an object of this class.
*/
IMPLEMENT_DYNCREATE(CCMIconHandler, CWinApp);

/*
Implement a class factory for this object.
*/
IMPLEMENT_OLECREATE(CCMIconHandler, "CM Icon Handler",
0x897d3ada, 0xc314, 0x4e2e,
0xa9, 0x25, 0x84, 0x3b, 0x57, 0x82, 0x22, 0xce);

/*
Declarations for COM interface...
*/
BEGIN_INTERFACE_MAP(CCMIconHandler, CWinApp)
INTERFACE_PART(CCMIconHandler, IID_IPersistFile, PersistFile)
INTERFACE_PART(CCMIconHandler, IID_IExtractIcon, ExtractIcon)
END_INTERFACE_MAP()
;

/*
The one and only CCMIconHandler object
*/
CCMIconHandler	theApp;

STDAPI
DllCanUnloadNow(void)
{
/*
Required for dynamic linking with MFC.
*/
AFX_MANAGE_STATE(::AfxGetStaticModuleState());

return (::AfxDllCanUnloadNow());

}	// DllCanUnloadNow


STDAPI
DllGetClassObject(
REFCLSID	rclsid,
REFIID		riid,
LPVOID*		ppv)
{
/*
Required for dynamic linking with MFC.
*/
AFX_MANAGE_STATE(::AfxGetStaticModuleState());

return (::AfxDllGetClassObject(rclsid, riid, ppv));

}	// DllGetClassObject


STDAPI
DllRegisterServer(void)
{
/*
Required for dynamic linking with MFC.
*/
AFX_MANAGE_STATE(::AfxGetStaticModuleState());

HRESULT		result(S_OK);	// Result code.

if (not COleObjectFactory::UpdateRegistryAll())
{
/*
Something went wrong; tell the caller we were unable to register
all of the object classes.
*/
result = SELFREG_E_CLASS;
}

return (result);

}	// DllRegisterServer


STDAPI
DllUnregisterServer(void)
{
/*
Required for dynamic linking with MFC.
*/
AFX_MANAGE_STATE(::AfxGetStaticModuleState());

HRESULT		result(S_OK);	// Result code.

if (not COleObjectFactory::UpdateRegistryAll(False))
{
/*
Something went wrong; tell the caller we were unable to unregister all
of the object classes.
*/
result = SELFREG_E_CLASS;
}

return (result);

}	// DllUnregisterServer


ULONG
FAR EXPORT
CCMIconHandler::XPersistFile::AddRef(void)
{
/*
Required for all interface methods...
*/
METHOD_PROLOGUE(CCMIconHandler, PersistFile);

return (pThis->ExternalAddRef());

}	// CCMIconHandler::XPersistFile::AddRef


HRESULT
FAR EXPORT
CCMIconHandler::XPersistFile::GetClassID(
CLSID*		pClassID)
{
/*
Required for all interface methods...
*/
METHOD_PROLOGUE(CCMIconHandler, PersistFile);

return (E_NOTIMPL);

}	// CCMIconHandler::XPersistFile::GetClassID


HRESULT
FAR EXPORT
CCMIconHandler::XPersistFile::GetCurFile(
LPOLESTR*	/* pszFileName */)
{
/*
Required for all interface methods...
*/
METHOD_PROLOGUE(CCMIconHandler, PersistFile);

return (E_NOTIMPL);

}	// CCMIconHandler::XPersistFile::GetCurFile


HRESULT
FAR EXPORT
CCMIconHandler::XPersistFile::IsDirty(void)
{
/*
Required for all interface methods...
*/
METHOD_PROLOGUE(CCMIconHandler, PersistFile);

return (E_NOTIMPL);

}	// CCMIconHandler::XPersistFile::IsDirty


HRESULT
FAR EXPORT
CCMIconHandler::XPersistFile::Load(
LPCOLESTR	pszFileName,
DWORD		/* dwMode */)
{
/*
Required for all interface methods...
*/
METHOD_PROLOGUE(CCMIconHandler, PersistFile);

HRESULT		result(E_FAIL);		// Result of load operation.

// Copies the path to the <fFileName> field.
[...]

return (result);

}	// CCMIconHandler::XPersistFile::Load


HRESULT
FAR EXPORT
CCMIconHandler::XPersistFile::QueryInterface(
REFIID		iid,
LPVOID far*	ppvObj)
{
/*
Required for all interface methods...
*/
METHOD_PROLOGUE(CCMIconHandler, PersistFile);

HRESULT			result(E_NOINTERFACE);		// Result of query operation.

if (nil != ppvObj)
{
result = pThis->ExternalQueryInterface(&iid, ppvObj);
}

return (result);

}	// CCMIconHandler::XPersistFile::QueryInterface


ULONG
FAR EXPORT
CCMIconHandler::XPersistFile::Release(void)
{
/*
Required for all interface methods...
*/
METHOD_PROLOGUE(CCMIconHandler, PersistFile);

return (pThis->ExternalRelease());

}	// CCMIconHandler::XPersistFile::Release


HRESULT
FAR EXPORT
CCMIconHandler::XPersistFile::Save(
LPCOLESTR	/* pszFileName */,
BOOL		/* fRemember */)
{
/*
Required for all interface methods...
*/
METHOD_PROLOGUE(CCMIconHandler, PersistFile);

return (E_NOTIMPL);

}	// CCMIconHandler::XPersistFile::Save


HRESULT
FAR EXPORT
CCMIconHandler::XPersistFile::SaveCompleted(
LPCOLESTR	/* pszFileName */)
{
/*
Required for all interface methods...
*/
METHOD_PROLOGUE(CCMIconHandler, PersistFile);

return (E_NOTIMPL);

}	// CCMIconHandler::XPersistFile::SaveCompleted


ULONG
FAR EXPORT
CCMIconHandler::XExtractIcon::AddRef(void)
{
/*
Required for all interface methods...
*/
METHOD_PROLOGUE(CCMIconHandler, ExtractIcon);

return (pThis->ExternalAddRef());

}	// CCMIconHandler::XExtractIcon::AddRef


HRESULT
FAR EXPORT
CCMIconHandler::XExtractIcon::Extract(
LPCTSTR		/* pszFile */,
UINT		/* nIconIndex */,
HICON*		phiconLarge,
HICON*		phiconSmall,
UINT		nIconSize)
{
/*
Required for all interface methods...
*/
METHOD_PROLOGUE(CCMIconHandler, ExtractIcon);

// Code for extracting icons....
[...]

return (result);

}	// CCMIconHandler::XExtractIcon::Extract


HRESULT
FAR EXPORT
CCMIconHandler::XExtractIcon::GetIconLocation(
UINT		uFlags,
LPTSTR		smIconFile,
UINT		cchMax,
int*		piIndex,
UINT*		pwFlags)
{
/*
Required for all interface methods...
*/
METHOD_PROLOGUE(CCMIconHandler, ExtractIcon);

// Code for locating icon.
[...]

return (result);

}	// CCMIconHandler::XExtractIcon::GetIconLocation


HRESULT
FAR EXPORT
CCMIconHandler::XExtractIcon::QueryInterface(
REFIID		iid,
LPVOID far*	ppvObj)
{
/*
Required for all interface methods...
*/
METHOD_PROLOGUE(CCMIconHandler, PersistFile);

HRESULT		result(E_NOINTERFACE);		// Result of query operation.

ASSERT(nil != ppvObj);

if (nil != ppvObj)
{
result = pThis->ExternalQueryInterface(&iid, ppvObj);
}

return (result);

}	// CCMIconHandler::XExtractIcon::QueryInterface


ULONG
FAR EXPORT
CCMIconHandler::XExtractIcon::Release(void)
{
/*
Required for all interface methods...
*/
METHOD_PROLOGUE(CCMIconHandler, ExtractIcon);

return (pThis->ExternalRelease());

}	// CCMIconHandler::XExtractIcon::Release


CCMIconHandler::CCMIconHandler(void)
{

}	// CCMIconHandler::CCMIconHandler


CCMIconHandler::~CCMIconHandler(void)
{

}	// CCMIconHandler::~CCMIconHandler


int
CCMIconHandler::ExitInstance(void)
{
return (inherited::ExitInstance());

}	// CCMIconHandler::ExitInstance


BOOL
CCMIconHandler::InitInstance(void)
{
inherited::InitInstance();

// Register all OLE server (factories) as running.  This enables the
//  OLE libraries to create objects from other applications.
COleObjectFactory::RegisterAll();

return (True);

}	// CCMIconHandler::InitInstance

It compiles fine and, as I said, it seems that *some* methods get called,
but the Shell never tries to extract an icon. Is there anything obvious that
anyone can see that I'm doing wrong? There doesn't seem to be much to it, so
I'm probably doing something stupid.
noma replied on 05-Aug-07 03:10 AM
Thanks, Michael. I've followed all the guidelines in that article (and
several others). The salient point here is that I'm pretty sure my
IPersistFile::Load() method would not have been called if the extension
weren't registered properly. I assume, therefore, that it must be, and that
something else is going wrong.

I can sit in the debugger and watch my XPersistFile::QueryInterface() get
called, and see it return a pointer to the correct vtable for my XExtractIcon
subclass (I can derference it and see the pointers to the functions), and yet
none of my XExtractIcon methods ever get called.

So I don't think it's a registration problem; I think there's something else
going on.
Michael Phillips, Jr. replied on 05-Aug-07 10:01 AM
Are you able to read the arguments passed to you?  Do they make any sense?

Most importantly, do you return S_OK?


Is there any reason why you cannot implement the IExtractIcon and
IPersistFile methods in the same class?
Jim Barry replied on 05-Aug-07 11:18 AM
That's the way COM is done in MFC - not pleasant!

--=20
Jim Barry, MVP (Windows SDK)
noma replied on 05-Aug-07 12:10 PM
Yes, the MFC declarations for everything are much cleaner, but the
implementation is a bit ... squirrely.

Anyway, another interesting data point: While stepping through the debugger
in my DllGetClassObject() entry point, I noticed that the object that's being
returned by my call to AfxDllGetClassObject() appears to be 4 bytes off. That
is, the fields, read in the debugger, of my CWinApp/CCmdTarget class object
appear to be offset 4 bytes from where they should be: the pointer to a
string that should be in one field appears to be in the next field down. If I
add 4 to the pointer, the fields as displayed in the debugger look correct
again, but if I let it run at that point I get a memory access error.

I haven't played with this enough to know whether this is a debugger
artifact or what, but does this perhaps ring any bells? It would certainly
explain why my interface methods arent' being called: the pointe to the
correct vtable is 4 bytes off from where it should be!
Jim Barry replied on 06-Aug-07 08:25 AM
Cleaner than what?


The way that MFC uses a nested class for each interface makes it =
extremely hard to follow in the debugger. Another reason to steer well =
clear of it.

--=20
Jim Barry, MVP (Windows SDK)
noma replied on 06-Aug-07 09:48 AM
Well, ATL, for one thing.

Anyway, debugger aside, I've got a test app now and I can verify that I can
load the DLL and call into the IExtractIcon interface if I call
the method off the pointer I get back:

::CoInitialize(NULL);

IUnknown*	pUnk(NULL);

result = ::CoCreateInstance(kIHCLSID, NULL, CLSCTX_INPROC_SERVER,
IID_IUnknown,
reinterpret_cast<LPVOID*>(&pUnk));

IPersistFile*	pIPF(NULL);

if (S_OK == pUnk->QueryInterface(IID_IPersistFile,
reinterpret_cast<LPVOID*>(&pIPF)))
{
ASSERT(S_OK == pIPF->Load(_T("<path to my data file>"), 0));
}

IExtractIcon*	pIEI(NULL);
TCHAR		path[MAX_PATH];

if (S_OK == pUnk->QueryInterface(IID_IExtractIcon,
reinterpret_cast<LPVOID*>(&pIEI)))
{
int		iIndex(0);
UINT	wFlags(0);

if (S_OK == pIEI->GetIconLocation(0, path, MAX_PATH, &iIndex, &wFlags))
{
if (S_OK == pIEI->Extract(path, iIndex, &fHBigIcon, &fHSmallIcon,
MAKELONG(32, 16)))
{
(void) fBigIcon.SetIcon(fHBigIcon);
(void) fSmallIcon.SetIcon(fHSmallIcon);
}
}
}

That all works fine. I can also do it using SHGetDesktopFolder() and the
various related calls:

::CoInitialize(NULL);

if (FAILED(SHGetDesktopFolder(&deskFolder)))
return TRUE;

if (FAILED(deskFolder->ParseDisplayName(NULL, NULL,
_T("g:\\development\\projects\\sample files\\files with
embedded previews\\biotite.cmdf"),
NULL, &pidlLocal, NULL)))
{
deskFolder->Release();
return TRUE;
}

pidlRelative = ILClone(ILFindLastID(pidlLocal));
ILRemoveLastID(pidlLocal);

if (FAILED(deskFolder->BindToObject(pidlLocal, NULL, IID_IShellFolder,
(void**)&appObject)))
{
deskFolder->Release();
ILFree(pidlLocal);
return TRUE;
}

ILFree(pidlLocal);

hr = appObject->GetUIObjectOf(NULL, 1, (LPCITEMIDLIST*)&pidlRelative,
IID_IExtractIcon, NULL,
(void**)&extractIcon);

But if I attach to an explorer window with the debugger, what happens is
that my IPersistFile::Load() method gets called, then I load the interface
for IExtractIcon, and, right after that, my IExtractIcon::Release() method
gets called (apparently from CoWaitForMultipleHandles()). So the shell gets
the IExtractIcon interface, and releases it without (apparently) calling
anything.
Michael Phillips, Jr. replied on 06-Aug-07 11:14 AM
There appears to be a problem with your icon handler's interface
implementation.

You should see the following in your debugger:
1) The shell should load your icon handler and call your IClassFactory and
get an IExtractIcon interface.
2) It then calls QueryInteface on your IExtractIcon interface pointer to get
the IPersistFile interface.
3) If QueryInterface returns an IPersistFile interface, it then calls Load
and looks for S_OK on return.
4) If S_OK, then the shell should call your icon handler's GetIconLocation
and Extract methods.
noma replied on 06-Aug-07 11:42 AM
Here's the sequence of events, dumped from my implementation. In each case I
print out the incoming parameters on entry, and then the outgoing ones (e.g.,
ppv, result) upon completion. To me, it all looks fine until it calls the
QueryInterface immediately after the Load() call.

*** CMyIconHandler::InitInstance() ***
DllGetClassObject()
rclsid = 897d3ada-c314-4e2e-a925-843b578222ce
riid = 00000001-0000-0000-c000-000000000046
ppv = 0x0182af2c
result = 0x00000000
CMyIconHandler::XPersistFile::AddRef()
result = 0x00000002
CMyIconHandler::XPersistFile::Release()
result = 0x00000001
CMyIconHandler::XPersistFile::QueryInterface()
iid = 00000003-0000-0000-c000-000000000046
ppvObj = 0x00000000
result = 0x80004002
CMyIconHandler::XPersistFile::QueryInterface()
iid = 0000001b-0000-0000-c000-000000000046
ppvObj = 0x00000000
result = 0x80004002
CMyIconHandler::XPersistFile::QueryInterface()
iid = 00000000-0000-0000-c000-000000000046
ppvObj = 0x00e6d1b4
result = 0x00000000
CMyIconHandler::XPersistFile::AddRef()
result = 0x00000003
CMyIconHandler::XPersistFile::QueryInterface()
iid = 00000018-0000-0000-c000-000000000046
ppvObj = 0x00000000
result = 0x80004002
CMyIconHandler::XPersistFile::QueryInterface()
iid = 00000019-0000-0000-c000-000000000046
ppvObj = 0x00000000
result = 0x80004002
CMyIconHandler::XPersistFile::QueryInterface()
iid = 4c1e39e1-e3e3-4296-aa86-ec938d896e92
ppvObj = 0x00000000
result = 0x80004002
CMyIconHandler::XPersistFile::Release()
result = 0x00000002
CMyIconHandler::XPersistFile::QueryInterface()
iid = 0000010b-0000-0000-c000-000000000046
ppvObj = 0x00e6d1b4
result = 0x00000000
CMyIconHandler::XPersistFile::QueryInterface()
iid = 0000010b-0000-0000-c000-000000000046
ppvObj = 0x00e6d1b4
result = 0x00000000
CMyIconHandler::XPersistFile::Release()
result = 0x00000003
CMyIconHandler::XPersistFile::QueryInterface()
iid = 1c733a30-2a1c-11ce-ade5-00aa0044773d
ppvObj = 0x00000000
result = 0x80004002
CMyIconHandler::XPersistFile::Load()
filename = F:\Documents and Settings\<my file here>
dwMode = 0
result = 0x00000000
CMyIconHandler::XPersistFile::QueryInterface()
iid = 000214fa-0000-0000-c000-000000000046
ppvObj = 0x00e6d1b8
result = 0x00000000
CMyIconHandler::XExtractIcon::Release()
result = 0x00000003
CMyIconHandler::XPersistFile::Release()
result = 0x00000002
CMyIconHandler::XPersistFile::Release()
result = 0x00000001
CMyIconHandler::XPersistFile::Release()
result = 0x00000000
Michael Phillips, Jr. replied on 06-Aug-07 12:12 PM
Your IClassFactory implementation is returning an IPersistFile interface.
It should return an IExtractIcon interface(e.g.,
IExtractIconA{000214eb-0000-0000-c000-000000000046}).
Jim Barry replied on 06-Aug-07 12:51 PM
Actually, that's IID_IStdIdentity. (IID_IPersistFile starts with =
0000010b.)


In fact, "iid" is just the IID being passed to QueryInterface.

--=20
Jim Barry, MVP (Windows SDK)
noma replied on 06-Aug-07 01:12 PM
As I understand it, the class factory can pass back the pointer to any of
the internal interfaces, as long as they all implement IUnknown properly.
DllGetClassObject() just returns the first one it finds, in this case
IPersistFile. IPersistFile::QueryInterface() is being asked for a number of
interfaces which I don't implement (something involving IMarshall or some
such, and a number of others which aren't relevant to the problem of
extracting an icon). Eventually, as the listing shows, it calls
IPersistFile::Load(), as it's supposed to. It then asks for the interface to
IExtractIcon, which I return. I've verified that, in fact, this is a pointer
to the vtable for my XExtractIcon internal subclass. It then immediately
calls IExtractIcon::Release(), not having called anything.

As I noted, however, if I call the required routines in my test app,
everything works.
Michael Phillips, Jr. replied on 06-Aug-07 01:24 PM
You are correct.

However, maybe his IClassFactory implementation is returning an IPersistFile
interface instead of an IExtractIcon interface.

I believe that it would be easier to diagnose the problem if the icon
handler was coded in ATL instead of Mfc.


Actually, that's IID_IStdIdentity. (IID_IPersistFile starts with 0000010b.)


In fact, "iid" is just the IID being passed to QueryInterface.

--
Jim Barry, MVP (Windows SDK)
Michael Phillips, Jr. replied on 06-Aug-07 01:41 PM
I feel your frustration.

On MSDN here:
http://msdn2.microsoft.com/EN-US/library/aa969289.aspx

Microsoft states that IPersistFile is optional for an icon handler and that
IInitializeWithItem is preferred.

My only other suggestion is write a test case icon handler using ATL.

I seem to find ATL much more pleasant to work with than Mfc.
Jim Barry replied on 06-Aug-07 02:22 PM
IInitializeWithFile, IInitializeWithItem, and IInitializeWithStream are =
all brand spanking new for Vista, however, and certainly won't be called =
on XP or earlier.

--=20
Jim Barry, MVP (Windows SDK)
Jim Barry replied on 06-Aug-07 02:51 PM
I think I see what the problem is. Your object is being queried for =
IMarshal, which means that COM marshalling is being done. This only =
happens when calls are being made between apartments. If your component =
is not apartment threaded, marshalling will fail for IExtractIcon, =
because there is no proxy-stub provided for that interface. Make sure =
your component is apartment threaded, i.e. it is registered with =
ThreadingModel=3DApartment under the InprocServer32 key.

--=20
Jim Barry, MVP (Windows SDK)
noma replied on 07-Aug-07 03:16 AM
ThreadingModel=Apartment

Well, halleluljah, that was it! Works great now (well, okay, a few minor
bugs, but those are all my doing).

Many, many thanks to Jim and Michael for your patience and suggestions. I
was almost ready to give up on the whole thing.

Cheers,

-Mike