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 int
s. 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.