Shell Extension Class Factory
The COM plumbing of a Win32 shell extension DLL — DllGetClassObject, the class factory, reference counting, and QueryInterface across all six shell extension interfaces.
Every shell extension DLL is loaded by Explorer through the same two entry points shown here: DllGetClassObject (which must return an IClassFactory) and DllCanUnloadNow (which tells the shell when it is safe to unload). This file is that whole layer for "Project Epsilon", a context-menu/icon/property-sheet/copy-hook extension. The pattern: a global reference count (g_cRefThisDll) incremented in every object's constructor and decremented in its destructor; a class factory whose CreateInstance refuses aggregation and hands back the main CShellExt object; and one QueryInterface that serves six interfaces from the same object - IShellExtInit, IContextMenu, IExtractIcon, IPersistFile, IShellPropSheetExt, and IShellCopyHook. Use this as the skeleton when you are writing your own shell extension; the individual interface implementations live in their own files.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include <windows.h>
#include <windowsx.h>
#include <shlobj.h>
// Initialize GUIDs (should be done only and at-least once per DLL/EXE)
#pragma data_seg(".text")
#define INITGUID
#include <initguid.h>
#include <shlguid.h>
#pragma data_seg()
UINT g_cRefThisDll = 0; // Reference count of this DLL.
HINSTANCE g_hmodThisDll = NULL; // Handle to this DLL itself.
extern "C" int APIENTRY DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
// Extension DLL one-time initialization
g_hmodThisDll = hInstance;
}
else if (dwReason == DLL_PROCESS_DETACH)
{
}
return 1; // ok
}
STDAPI DllCanUnloadNow(void)
{
return (g_cRefThisDll == 0 ? S_OK : S_FALSE);
}
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppvOut)
{
*ppvOut = NULL;
if (IsEqualIID(rclsid, CLSID_ShellExtension))
{
CShellExtClassFactory *pcf = new CShellExtClassFactory;
return pcf->QueryInterface(riid, ppvOut);
}
return CLASS_E_CLASSNOTAVAILABLE;
}
CShellExtClassFactory::CShellExtClassFactory()
{
m_cRef = 0L;
g_cRefThisDll++;
}
CShellExtClassFactory::~CShellExtClassFactory()
{
g_cRefThisDll--;
}
STDMETHODIMP CShellExtClassFactory::QueryInterface(REFIID riid, LPVOID FAR *ppv)
{
*ppv = NULL;
// Any interface on this object is the object pointer
if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IClassFactory))
{
*ppv = (LPCLASSFACTORY)this;
AddRef();
return NOERROR;
}
return E_NOINTERFACE;
}
STDMETHODIMP_(ULONG) CShellExtClassFactory::AddRef()
{
return ++m_cRef;
}
STDMETHODIMP_(ULONG) CShellExtClassFactory::Release()
{
if (--m_cRef)
return m_cRef;
delete this;
return 0L;
}
STDMETHODIMP CShellExtClassFactory::CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid, LPVOID *ppvObj)
{
*ppvObj = NULL;
// Shell extensions typically don't support aggregation (inheritance)
if (pUnkOuter)
return CLASS_E_NOAGGREGATION;
// Create the main shell extension object. The shell will then call
// QueryInterface with IID_IShellExtInit--this is how shell extensions are
// initialized.
LPCSHELLEXT pShellExt = new CShellExt(); //Create the CShellExt object
if (NULL == pShellExt)
return E_OUTOFMEMORY;
return pShellExt->QueryInterface(riid, ppvObj);
}
STDMETHODIMP CShellExtClassFactory::LockServer(BOOL fLock)
{
return NOERROR;
}
And the main object, whose single QueryInterface fans out to all six interfaces:
CShellExt::CShellExt()
{
m_cRef = 0L;
m_pDataObj = NULL;
g_cRefThisDll++;
}
CShellExt::~CShellExt()
{
if (m_pDataObj)
m_pDataObj->Release();
g_cRefThisDll--;
}
STDMETHODIMP CShellExt::QueryInterface(REFIID riid, LPVOID FAR *ppv)
{
*ppv = NULL;
if (IsEqualIID(riid, IID_IShellExtInit) || IsEqualIID(riid, IID_IUnknown))
{
*ppv = (LPSHELLEXTINIT)this;
}
else if (IsEqualIID(riid, IID_IContextMenu))
{
*ppv = (LPCONTEXTMENU)this;
}
else if (IsEqualIID(riid, IID_IExtractIcon))
{
*ppv = (LPEXTRACTICON)this;
}
else if (IsEqualIID(riid, IID_IPersistFile))
{
*ppv = (LPPERSISTFILE)this;
}
else if (IsEqualIID(riid, IID_IShellPropSheetExt))
{
*ppv = (LPSHELLPROPSHEETEXT)this;
}
else if (IsEqualIID(riid, IID_IShellCopyHook))
{
*ppv = (LPCOPYHOOK)this;
}
if (*ppv)
{
AddRef();
return NOERROR;
}
return E_NOINTERFACE;
}
STDMETHODIMP_(ULONG) CShellExt::AddRef()
{
return ++m_cRef;
}
STDMETHODIMP_(ULONG) CShellExt::Release()
{
if (--m_cRef)
return m_cRef;
delete this;
return 0L;
}
Two details worth keeping: the #pragma data_seg(".text") around initguid.h keeps the GUID constants from bloating the read-only data section, and DllCanUnloadNow returning S_FALSE while any object is alive is what prevents Explorer from unloading the DLL mid-operation.