Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

The Complete Idiot's Guide to Writing Shell Extensions - Part III

0.00/5 (No votes)
17 May 2006 1  
A tutorial on writing a shell extension that shows pop-up info for files.

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:

 [My Computer tooltip - 6K]

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:

 [WinZip tooltip - 5K]

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());  // init MFC

 
  // Let CString convert the filename to ANSI.

  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());  // init MFC

 
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:

  // Get the size of the file.

  dwFileSize = file.GetLength();
 
  // Read in the first line from the file.

  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;
    }
 
  // Use the Unicode string copy function to put the tooltip text in the buffer.

  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.

 [text file tooltip - 4K]

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 »

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here