Contents
Introduction
In Parts I and II of the Guide,
I showed how to write context menu extensions. In Part III, I'll demonstrate a different type of extension, explain
how to share memory with the shell, and show how to use MFC alongside ATL. This part assumes that you understand
the basics of shell extensions from the first two parts, and are familiar with MFC.
The Active Desktop shell introduced a new feature: tooltips that show a description of certain objects if you
hover the mouse over them. For example, hovering over My Documents shows this tooltip:
Other objects like My Computer and Control Panel have similar tooltips. These bits of text are called infotips,
since they are tooltips that provide information about the file, folder, or object that the mouse is over. We can
provide our own infotip text for other objects in the shell, using an infotip extension. An example of an
infotip extension that you have probably already seen is in WinZip, which shows the contents of compressed files:
This article's sample extension will be a quick text file viewer - it will display the first line of the file,
along with the total file size. This information will appear in an infotip when the user hovers the mouse over
a .TXT
file.
Remember that VC 7 (and probably VC 8) users will need to change some settings before compiling. See the
README section in Part I for the details.
Using AppWizard to Get Started
Run the AppWizard and make a new ATL COM application, just as we did in the earlier articles. We'll call it
TxtInfo. Since we are going to use MFC this time, check the Support MFC checkbox, and then click
Finish.
To add a COM object to the DLL, go to the ClassView tree, right-click the TxtInfo classes item, and pick
New ATL Object. (In VC 7, right-click the item and pick Add|Add Class.) As before, choose Simple
Object in the wizard, and use the name TxtInfoShlExt for the object. This will create a C++ class CTxtInfoShlExt
that will implement the extension
If you look in the ClassView tree, you'll see a CTxtInfoApp
class, which is derived from CWinApp
.
The presence of this class, and the global variable theApp
, makes MFC available to us, just as if
we were writing a normal MFC DLL without any ATL.
The Initialization Interface
In the previous articles' context menu extensions, we implemented the IShellExtInit
interface,
which was how Explorer initialized our object. There is another initialization interface used for some shell extensions,
IPersistFile
, and this is the one an infotip extension uses. Why the difference? If you remember,
IShellExtInit::Initialize()
receives an IDataObject
pointer with which it can enumerate
all of the files that were selected. Extensions that can only ever operate on a single file use IPersistFile
.
Since the mouse can't hover over more than one object at a time, an infotip extension only works on one file at
a time, so it uses IPersistFile
.
We first need to add IPersistFile
to the list of interfaces that CTxtInfoShlExt
implements.
Open up TxtInfoShlExt.h, and add the lines shown in bold:
#include <comdef.h>
#include <shlobj.h>
class CTxtInfoShlExt :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CTxtInfoShlExt, &CLSID_TxtInfoShlExt>,
public IPersistFile
{
BEGIN_COM_MAP(CTxtInfoShlExt)
COM_INTERFACE_ENTRY(IPersistFile)
END_COM_MAP()
We'll also need a variable to hold the filename that Explorer gives us during our initialization:
protected:
CString m_sFilename;
Notice that we can use an MFC object anywhere we please.
If you look up the docs on IPersistFile
, you'll see a lot of methods. Fortunately, for the
purposes of a shell extension, we only have to implement Load()
, and ignore the others. Here are the
prototypes for the IPersistFile
methods:
class CTxtInfoShlExt : ...
{
public:
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; }
};
All methods aside from Load()
return E_NOTIMPL
to indicate that we don't implement
them. And to make this situation even nicer, our Load()
implementation is really simple. We'll just
store the name of the file that Explorer passes us. This is the full path to the file that the mouse is hovering
over.
HRESULT CTxtInfoShlExt::Load (
LPCOLESTR wszFilename, DWORD dwMode )
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
m_sFilename = wszFilename;
return S_OK;
}
The first line in the function is critical. The AFX_MANAGE_STATE
macro is necessary for MFC to
work properly. Because our DLL is being loaded by a non-MFC app, every exported function that uses MFC must initialize
MFC manually. If you don't include that line, many MFC functions (mostly the ones related to resources) will break
or have assertion failures.
The filename is stored in m_sFilename
, for later use. Note that I'm taking advantage of the CString
assignment operator to convert the string to ANSI, if the DLL is built as ANSI.
Creating Text for the Tooltip
After Explorer calls our Load()
method, it calls QueryInterface()
to get another interface:
IQueryInfo
. IQueryInfo
is a pretty simple interface, with just two methods (and only
one is actually used). Open up TxtInfoShlExt.h again, and add the lines listed here in bold:
class CTxtInfoShlExt :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CTxtInfoShlExt, &CLSID_TxtInfoShlExt>,
public IPersistFile,
public IQueryInfo
{
BEGIN_COM_MAP(CTxtInfoShlExt)
COM_INTERFACE_ENTRY(IPersistFile)
COM_INTERFACE_ENTRY(IQueryInfo)
END_COM_MAP()
Then add the prototypes for the IQueryInfo
methods:
class CTxtInfoShlExt : ...
{
STDMETHODIMP GetInfoFlags(DWORD*) { return E_NOTIMPL; }
STDMETHODIMP GetInfoTip(DWORD, LPWSTR*);
};
The GetInfoFlags()
method is not used, so we can just return E_NOTIMPL
. GetInfoTip()
is where we will return text to Explorer for it to show in the tooltip. First, the boring stuff at the beginning:
HRESULT CTxtInfoShlExt::GetInfoTip (
DWORD dwFlags, LPWSTR* ppwszTip )
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
USES_CONVERSION;
LPMALLOC pMalloc;
CStdioFile file;
DWORD dwFileSize;
CString sFirstLine;
BOOL bReadLine;
CString sTooltip;
Again, the AFX_MANAGE_STATE
call is first, to initialize MFC. This must be done as the very first
thing in the function, even before variable declarations, because local variable constructors might call MFC functions.
dwFlags
is not used currently. ppwszTip
is a pointer to an LPWSTR
that
we'll set to point at a buffer that we must allocate.
First, we'll try to open the file for reading. We know the filename, since we stored it in the Load()
function earlier.
if ( !file.Open(m_sFilename, CFile::modeRead|CFile::shareDenyWrite) )
return E_FAIL;
Now, since we need to use the shell's memory allocator to allocate a buffer, we need an IMalloc
interface pointer. We get this by calling the SHGetMalloc()
function:
if ( FAILED(SHGetMalloc(&pMalloc)) )
return E_FAIL;
I'll have more to say about IMalloc
a bit later. The next step is to get the file size, and read
the first line:
dwFileSize = file.GetLength();
bReadLine = file.ReadString ( sFirstLine );
bReadLine
will be usually be TRUE, unless the file was inaccessible or 0 bytes in length. The next
step is to create the first part of the tooltip, which lists the file size.
sTooltip.Format ( _T("File size: %lu"), dwFileSize );
Now, if we were able to read the first line of the file, we add it to the tooltip.
if ( bReadLine )
{
sTooltip += _T("\n");
sTooltip += sFirstLine;
}
Now that we have the complete tooltip, we need to allocate a buffer. Here's where we use IMalloc
.
The pointer returned by SHGetMalloc()
is a copy of the shell's IMalloc
interface. Any
memory we allocate with that interface resides in the shell's process space, so the shell can use it. More importantly,
the shell can also free it. So what we do is allocate the buffer, and then forget about it. The shell will
free the memory once it's done using it.
One other thing to be aware of is that the string we return to the shell must be in Unicode. That's why
the calculation in the Alloc()
call below multiplies by sizeof(wchar_t)
; just allocating
memory for lstrlen(sToolTip)
would only allocate half the required amount of memory.
*ppwszTip = (LPWSTR) pMalloc->Alloc ( (1 + lstrlen(sTooltip)) * sizeof(wchar_t) );
if ( NULL == *ppwszTip )
{
pMalloc->Release();
return E_OUTOFMEMORY;
}
wcscpy ( *ppwszTip, T2COLE(LPCTSTR(sTooltip)) );
The last thing to do is release the IMalloc
interface we got earlier.
pMalloc->Release();
return S_OK;
}
And that's all there is to it! Explorer takes the string in *ppwszTip
and displays it in a tooltip.
Registering the Shell Extension
Infotip extensions are registered a bit differently than context menu extensions. Our extension is registered
under a subkey of HKEY_CLASSES_ROOT
whose name is the file extension we want to handle. In this case,
that's HKCR\.txt
. But wait, it gets stranger! You'd think the ShellEx
subkey would be
something logical like "TooltipHandlers". Close! The key is called "{00021500-0000-0000-C000-000000000046}".
I think Microsoft is trying to sneak some shell extensions past us here! If you poke around the registry, you'll
find other ShellEx
subkeys whose names are GUIDs. That GUID above happens to be the GUID of IQueryInfo
.
Here's the RGS script that tells Explorer to invoke our extension on .TXT
files:
HKCR
{
NoRemove .txt
{
NoRemove shellex
{
{00021500-0000-0000-C000-000000000046} = s '{F4D78AE1-05AB-11D4-8D3B-444553540000}'
}
}
}
You can easily have the extension invoked for other extensions by duplicating the above snippet, and changing
".txt" to whatever extension you want. Unfortunately, you can't register an infotip extension under the
*
or AllFileSystemObjects
keys to have it invoked on all files.
As in our previous extensions, on NT-based OSes, we need to add our extension to the system's list of approved
extensions. The code to do this is in the DllRegisterServer()
and DllUnregisterServer()
functions in the sample project.
To Be Continued...
Coming up in Part IV, we'll return to the world of context menus and see another new type of extension, the
drag and drop handler. We'll also see more usage of MFC.
Copyright and License
This article is copyrighted material, ©2000-2006 by Michael Dunn. I realize this isn't going to stop people
from copying it all around the 'net, but I have to say it anyway. If you are interested in doing a translation
of this article, please email me to let me know. I don't foresee denying anyone permission to do a translation,
I would just like to be aware of the translation so I can post a link to it here.
The demo code that accompanies this article is released to the public domain. I release it this way so that
the code can benefit everyone. (I don't make the article itself public domain because having the article available
only on CodeProject helps both my own visibility and the CodeProject site.) If you use the demo code in your own
application, an email letting me know would be appreciated (just to satisfy my curiosity about whether folks are
benefitting from my code) but is not required. Attribution in your own source code is also appreciated but not
required.
Revision History
March 30, 2000: Article first published.
June 6, 2000: Something updated. ;)
May 17, 2006: Updated to cover changes in VC 7.1, cleaned up code snippets.
Series Navigation: « Part II | Part
IV »