Skip to content

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.

Published Dec 17, 1998

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.

An unhandled error has occurred. Reload 🗙