Click here to Skip to main content
15,883,705 members
Articles / Desktop Programming / ATL
Article

The Mini Shell Extension Framework – Part I

Rate me:
Please Sign up or sign in to vote.
4.50/5 (6 votes)
22 Aug 20046 min read 77.3K   958   58   11
Discussion of a small C++ framework to create Windows shell extensions.

Introduction

This article discusses a Mini Shell extension Framework that can be used to create Windows Shell extensions. Windows shell extensions allow the standard Windows shell (Explorer) to be extended for specific types of files. For a complete and detailed discussion of several types of shell extensions, the reader is referred to the excellent articles by Michael Dunn available on the CodeProject web site.

The framework consists of a collection of C++ templates that can be used in combination with ATL (7.0 and 7.1) to implement the required COM objects. The framework consists of template header files, a sample, and a small unit test application. To use the framework and build the sample with Visual C++ .NET 2002, the header files of the Platform SDK February 2003 are needed (can be downloaded from the Microsoft web site). Visual C++ .NET 2003 already contains the updated SDK files.

Design Philosophy

Shell extensions are, in concept, easy to develop. Using ATL, it is simple to create COM objects that support the required interfaces. But, creating error-handling code, registration scripts, localization support, and testing code makes the implementation complex.

The goal of the framework is to provide a collection of simple classes that help to move this kind of ‘plumbing’ code outside the shell extension into ‘implementation’ helper classes. This allows the author of the shell extension to focus on the functionality of the extension.

All framework classes are contained in the MSF namespace and have their own include file. This makes it easy to use one class of the framework without pulling in any of the other classes.

The framework classes expect that any unexpected error will raise an exception. The types of exceptions that can be thrown are the _com_error class (HRESULT errors) and the std::bad_alloc class (memory allocation errors).

Overview of the Classes

Shell Extension support classes: IColumnProviderImpl, IContextMenuImpl, IInfoTipImpl, IShellFolderImpl, IDataObjectPtr, IShellFolderViewCBImpl.

Test support classes: CCoInitialize, CTestRunner, IShellPropSheetExtPtr, CFileList, CPropSheetHost.

The VVV Sample

In order to show how to use the classes and to provide a sample of how to create a shell extension, the code includes a small sample that can open .vvv files. VVV files are just renamed .ini files that act as a container. Most shell extensions are container views (for example, the Windows .zip and .cab shell extensions).

Automatic Testing: Test Example

It is possible to verify by hand if a shell extension works, but it is much more effective to perform automatic testing. The test example provides a small test application that can perform automatic testing of the shell extension. A couple of default test are already provided using the test classes provided by the framework. The test sample is also the ideal application to track down memory bugs with commercial memory bug detection tools (using explorer.exe is often not practical for this purpose).

Debugging and Tracing

The easiest way to debug shell extensions it to output debug strings. ATL provides support with the TRACE macro. These log statements can be captured with an attached debugger or with the free DebugView tool from sysinternals.

If compiled with the _ATL_DEBUG_QI, ATL will also display which interfaces are requested. Due to a bug in ATL, it will only print the interface name if it is registered in the registry. The framework contains a file called “Undocumented Shell Interfaces.reg” that can be used to enter the missing info into the registry.

Creating an Infotip

An infotip is a tooltip that shows info about a file if the user hovers his mouse pointer above a file in an Explorer window. The basic task of an infotip object is therefore to return the ‘info’.

virtual CString CInfoTip::GetInfoTip(const TCHAR* szFilename)
{
    return _T("info");
}

The GetInfoTip function must be implemented by the extension, all other required functions (most are just stubs) are handled by IInfoTipImpl.

Before a COM object can do its job, it must be registered:

HRESULT WINAPI CinfoTip::UpdateRegistry(BOOL bRegister) throw()
{
  return IInfoTipImpl<CInfoTip>::UpdateRegistry(
                      IDR_INFOTIP, IDR_EXTENSION, bRegister,
                     L"Sample InfoTip", L"VVVFile", L".vvv");
}

The registry resource scripts IDR_INFOTIP and IDR_EXTENSION are provided by the framework but must be included in the resource file of the DLL. There are three extra arguments: description, the root extension, and the file extensions. All registered file extensions point to the same root extension. In the sample, only one file extension is registered, but the framework allows registering more file extensions to the same root. The full class definition is:

class ATL_NO_VTABLE CInfoTip :
  public CComObjectRootEx<CComSingleThreadModel>,
  public CComCoClass<CInfoTip &__uuidof(CInfoTip)>,
  public MSF::IInfoTipImpl<CInfoTip>
{
public:
  BEGIN_COM_MAP(CInfoTip)
    COM_INTERFACE_ENTRY(IPersistFile)
    COM_INTERFACE_ENTRY(IQueryInfo)
  END_COM_MAP()

  static HRESULT WINAPI UpdateRegistry(BOOL bRegister) throw();
  virtual CString GetInfoTip(const TCHAR* szFilename);
};

Creating a ColumnProvider

A ColumnProvider is a COM object that the shell queries for extra information to be displayed in the detailed view mode of an Explorer window. The framework model is simple but effective. It assumes that it is cheap to compute all columns for a particular file. As the Shell doesn’t cache info about files, the IColumnProviderImpl class makes sure that it caches every column. Caching is required to ensure that sorting on the provided column has an acceptable performance. The three basic steps to use the CColumnProviderImpl class are:

Registration of the COM object:

static HRESULT WINAPI UpdateRegistry(BOOL bRegister) throw()
{
  return IColumnProviderImpl<CColumnProvider>::
    UpdateRegistry(IDR_COLUMNPROVIDER,
                   bRegister, L"Sample ColumnProvider");
}

Configuration of the supported file extensions and the column names:

CColumnProvider()
{
  RegisterColumn(IDS_SHELLEXT_NAME, 9);
  RegisterColumn(IDS_SHELLEXT_FILECOUNT, 14, LVCFMT_RIGHT,
                 SHCOLSTATE_TYPE_INT);

  RegisterExtension(L".vvv");
}

In the above code example, we configure two columns. The first column is for the ‘name’ and initial nine characters wide. The next column is for the ‘filecount’ and is right aligned and sorted by the Shell as ints. The columnprovider is only interested in .vvv files.

virtual void CacheFile(const CString& strFilename,
                       vector<CString>& strColumnInfos)
{
  CVVVFile vvvfile(strFilename);

  strColumnInfos.push_back(vvvfile.GetName());
  strColumnInfos.push_back(vvvfile.GetFileCount());
}

When the Shell needs column info, the CColumnProviderImpl will forward the request to the CacheFile function. The column information should be added to the output array as strings in the same order as the columns are registered in the constructor. If something bad happens during retrieving of the column info, the function is allowed to throw a com_error exception.

Creating a Shell Property Sheet Extension and Property Sheet Page

The standard property sheet pages used by the Shell can be extended with our own pages. To do this, a ShellPropSheetExt COM object must be used. Using the framework provided, for CShellPropSheetExtImpl class three functions need to be implemented:

Registration of the COM object:

static HRESULT WINAPI UpdateRegistry(BOOL bRegister) throw()
{
  return CshellPropSheetExtImpl<CShellPropSheetExt>::
    UpdateRegistryFromResource(IDR_PROPSHEETEXT, IDR_EXTENSION,
                               bRegister, L"Sample PropertySheet",
                               L"VVVFile", L".vvv");
}

The IDR_PROPSHEETEXT refers to a framework provided registration resource script (.rgs) that uses parameters to register the COM object. The parameters in this case are the description, root extension, and the file extension.

The next few lines show the constructor:

CShellPropSheetExt()
{
  RegisterExtension(_T(".vvv"));
}

In our example, we only register the propsheetext for .vvv files. We could add more file extensions we would like to provide property pages for.

The last function that needs to be implemented is the AddPages function:

virtual void AddPages(const CAddPage& addpage,
                      const std::vector<CString>& filenames,
                      bool bContainsUnknownExtensions)
{
  if (bContainsUnknownExtensions || filenames.size() != 1)
      return;

  addpage(CPropertyPageVVV::CreateInstance(filenames[0]));
}

In our example, we don’t add any page if the user has selected more than one file or has selected files that don’t have the .vvv extension. If that is not the case, we call the functor addpage passing it a HPROPSHEETPAGE handle created by the CreateInstance function.

The framework provides the class CShellExtPropertyPageImpl that is derived from the ATL class CSnapInPropertyPageImpl to create property pages. The added functionality is that the MSF class locks the COM module to prevent that the DLL is unloaded. The lifetime of the property sheet is not coupled to the lifetime of the COM ShellPropSheetExt object as it is no COM object.

Post Notes

If you build and execute the sample, you will discover that VVV files can also be opened inside Explorer. This functionality is provides with a custom shellfolder object. I have planned to document this shellfolderimpl in part 2 of this article, as its functionality is complex and would make this article too long. The same applies to the extra context menu items. Nevertheless, the framework currently contains already a fully working IShellFolderImpl and IContextMenuImpl classes (with sample code).

History

  • Version 0.85 : Improved release for the CodeProject article.
  • Version 0.80 : Initial release.

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


Written By
Software Developer (Senior) Hitachi High-Tech Analytical Science
Netherlands Netherlands
Victor lives in Nijmegen, the oldest city in The Netherlands.
He studied Applied Physics in Delft and works Hitachi High-Tech Analytical Science.

Comments and Discussions

 
Questioninheriting from the unregistered shell interfaces Pin
eweitzman17-Jul-07 15:22
eweitzman17-Jul-07 15:22 
AnswerRe: inheriting from the unregistered shell interfaces Pin
Victor Derks1-Aug-07 11:31
professionalVictor Derks1-Aug-07 11:31 
GeneralSample does not build with Visual Studio 2005 Pin
Dave N 218-Feb-06 6:50
Dave N 218-Feb-06 6:50 
GeneralRe: Sample does not build with Visual Studio 2005 Pin
Victor Derks22-Feb-06 10:58
professionalVictor Derks22-Feb-06 10:58 
GeneralRe: Sample does not build with Visual Studio 2005 Pin
Dave N 225-Feb-06 8:26
Dave N 225-Feb-06 8:26 
AnswerRe: Sample does not build with Visual Studio 2005 Pin
Victor Derks20-Feb-07 22:02
professionalVictor Derks20-Feb-07 22:02 
QuestionAcquiring the Property Sheet From a DLL Pin
cSaRebel17-Jan-06 5:51
cSaRebel17-Jan-06 5:51 
GeneralBad link to sysinternals.com Pin
dwilliss223-May-05 3:55
dwilliss223-May-05 3:55 
GeneralRe: Bad link to sysinternals.com Pin
Victor Derks23-May-05 10:40
professionalVictor Derks23-May-05 10:40 
GeneralFormatting of article Pin
Philippe Lhoste23-Aug-04 22:46
Philippe Lhoste23-Aug-04 22:46 
GeneralRe: Formatting of article Pin
Victor Derks24-Aug-04 11:02
professionalVictor Derks24-Aug-04 11:02 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.