DLL Registering/Unregistering using Shell Extensions

Why are we still using the command prompt to register and unregister a DLL? A much easier and
more productive way is to use the Windows Explorer.

Sample Image

The key is shell extensions. If you’re tired of having to open a command window
each time you want to register or unregister a DLL, then this article not only
gives you a much more efficient mechanism for doing that, but also teaches give
you a very simply shell extension example in the process!

You will find several things that are not available on the MSDN or in other articles (that
I’ve been able to locate).

1. GUID. You can obtain this sinister number running one of the uuidgen or guidgen tools. This is CLSID_DLLRegisterer.

2. The main file, where the DLL’s entry point DllMain resides, is dllregshex.cpp. I prefer to load resources at start and free them at end, especially when are in little number.
So, the routines _LoadResources and _UnloadResources do this. The other two interesting routines are DllRegisterServer and DllUnregisterServer. If
you use one, be polite to the user and use the twin (I have seen may Reg, but few Unreg). That’s when you did not choose another way (as a .reg file, for example, or separate install/uninstall programs).

So, what’s happening registering a dll as shell context extension? You have to create the entry in CLSID key and to create under this the ‘samename’ key and the thread model key (I read somewhere that only
‘Apartment’ is accepted for context extensions – is really true?).


STDAPI
DllRegisterServer(void)
{
	HINSTANCE hInst = g_hmodThisDll;

	int      i;
	HKEY     hKey;
	LRESULT  lResult;
	DWORD    dwDisp;
	TCHAR    szSubKey[MAX_PATH];
	TCHAR    szCLSID[MAX_PATH];
	TCHAR    szModule[MAX_PATH];
	LPWSTR   pwszShellExt;

	StringFromIID(CLSID_DLLRegisterer, &pwszShellExt);

	if (pwszShellExt)
	{
		WideCharToLocal(szCLSID, pwszShellExt, ARRAYSIZE(szCLSID));

		LPMALLOC pMalloc;
		CoGetMalloc(1, &pMalloc);
		if(pMalloc)
		{
			pMalloc->Free(pwszShellExt);
			pMalloc->Release();
		}
	}

	GetModuleFileName(hInst, szModule, ARRAYSIZE(szModule));
	REGSTRUCT ShExClsidEntries[] =
	{
		HKEY_CLASSES_ROOT,	TEXT("CLSID\\%s"),			NULL,                   TEXT(DLLREGUNREGNAME),
		HKEY_CLASSES_ROOT,	TEXT("CLSID\\%s\\InProcServer32"),	NULL,                   TEXT("%s"),
		HKEY_CLASSES_ROOT,	TEXT("CLSID\\%s\\InProcServer32"),	TEXT("ThreadingModel"), TEXT("Apartment"),
		NULL,                	NULL,					NULL,                   NULL
	};

	for(i = 0; ShExClsidEntries[i].hRootKey; i++)
    	{
		wsprintf(szSubKey, ShExClsidEntries[i].lpszSubKey, szCLSID);
		lResult = RegCreateKeyEx(ShExClsidEntries[i].hRootKey, szSubKey, 0, NULL, REG_OPTION_NON_VOLATILE,
			KEY_WRITE, NULL, &hKey, &dwDisp);

		if(NOERROR == lResult)
		{
			TCHAR szData[MAX_PATH];
			wsprintf(szData, ShExClsidEntries[i].lpszData, szModule);
			lResult = RegSetValueEx(hKey, ShExClsidEntries[i].lpszValueName, 0, REG_SZ,
				(LPBYTE)szData, lstrlen(szData) + 1);
			RegCloseKey(hKey);
		}
		else
			return SELFREG_E_CLASS;
   	}

The other entries bind the desired extension (in our case, the DLLs) to our CLSID:


	REGSTRUCT OtherShExEntries[] =
	{
		HKEY_LOCAL_MACHINE,	TEXT("software\\classes\\clsid\\"DLLREGUNREGNAME)	      , NULL,	TEXT("%s"),
		HKEY_CLASSES_ROOT,	TEXT("dllfile\\shellex\\ContextMenuHandlers\\"DLLREGUNREGNAME), NULL,	TEXT("%s"),
		NULL,			NULL,								NULL,	NULL
	};

	for (i = 0; OtherShExEntries[i].hRootKey; i++)
    	{
		wsprintf(szSubKey, OtherShExEntries[i].lpszSubKey, szCLSID);
		lResult = RegCreateKeyEx(OtherShExEntries[i].hRootKey, szSubKey, 0, NULL, REG_OPTION_NON_VOLATILE,
			KEY_WRITE, NULL, &hKey, &dwDisp);

		if(NOERROR == lResult)
		{
			TCHAR szData[MAX_PATH];
			wsprintf(szData, OtherShExEntries[i].lpszData, szCLSID);
			lResult = RegSetValueEx(hKey, OtherShExEntries[i].lpszValueName, 0, REG_SZ,
				(LPBYTE)szData, lstrlen(szData) + 1);
			RegCloseKey(hKey);
		}
		else
			return SELFREG_E_CLASS;
	}

A special mention when Windows is NT: register the extension as ‘Approved’:


	OSVERSIONINFO  osvi;
	osvi.dwOSVersionInfoSize = sizeof(osvi);
	GetVersionEx(&osvi);
	if (VER_PLATFORM_WIN32_NT == osvi.dwPlatformId)
	{
		lstrcpy( szSubKey, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved"));
		lResult = RegCreateKeyEx(  HKEY_LOCAL_MACHINE, szSubKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE,
			NULL, &hKey, &dwDisp);
		if(NOERROR == lResult)
		{
			TCHAR szData[MAX_PATH];
			lstrcpy(szData, DLLREGUNREGNAME);
			lResult = RegSetValueEx(hKey, szCLSID, 0, REG_SZ, (LPBYTE)szData, lstrlen(szData) + 1);
			RegCloseKey(hKey);
		}
		else
			return SELFREG_E_CLASS;
	}

	return S_OK;
}

The unregister routine performs the inverse way. The rest is history: AddRef, Release, QueryInterface.

3. The utils file (dllreg_util.cpp) contains conversion routines. This UNICODE job really kills me, I simply copied one of the 1000 other ways to deal with it.

4. The menu file (dllreg_ctxm.cpp) contains 3 standard routines: QueryContextMenu, which builds the added popup menu and his options, InvokeCommand and
GetCommandString – this will show you a simple text on Explorer’ status bar. Nothing unusual.


#include "dllreg_xhdr.h"
#include "dllregshex.h"
#include "dllreg_util.h"
#include "resource.h"

extern UINT		g_cRefThisDll;
extern HINSTANCE	g_hmodThisDll;

extern HBITMAP		hBmp_Install;
extern HBITMAP		hBmp_Uninstall;
extern HBITMAP		hBmp_About;
extern HMENU		hSubMenu;

//	IContextMenu
STDMETHODIMP
CShellExt::QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
{
    _UNUSED_PARAMETER(idCmdLast);
    UINT idCmd = idCmdFirst;

    char *szMenuText_Popup	= "Shell e&xtension";
    char *szMenuText_Install	= "&Install";
    char *szMenuText_Uninstall	= "&Uninstall";
    char *szMenuText_About	= "&About...";

    BOOL bAppendItems = TRUE;

    if((uFlags & 0x000F) == CMF_NORMAL)
	bAppendItems = TRUE;
    else if (uFlags & CMF_VERBSONLY)
	bAppendItems = TRUE;
    else if (uFlags & CMF_EXPLORE)
	bAppendItems = TRUE;
    else
	bAppendItems = FALSE;

    if(bAppendItems)
    {
        InsertMenu(hMenu, indexMenu++, MF_SEPARATOR | MF_BYPOSITION, 0, NULL);

	HMENU hSubMenu = CreateMenu();
	if(hSubMenu)
	{
		InsertMenu(hSubMenu, 0, MF_STRING	 | MF_BYPOSITION, idCmd++, szMenuText_Install);
		SetMenuItemBitmaps(hSubMenu, 0, MF_BYPOSITION, hBmp_Install, hBmp_Install);
    		InsertMenu(hSubMenu, 1, MF_STRING	 | MF_BYPOSITION, idCmd++, szMenuText_Uninstall);
		SetMenuItemBitmaps(hSubMenu, 1, MF_BYPOSITION, hBmp_Uninstall, hBmp_Uninstall);

		InsertMenu(hSubMenu, 2, MF_SEPARATOR | MF_BYPOSITION, 0, NULL);

		InsertMenu(hSubMenu, 3, MF_STRING	 | MF_BYPOSITION, idCmd++, szMenuText_About);
		SetMenuItemBitmaps(hSubMenu, 3, MF_BYPOSITION, hBmp_About, hBmp_About);
	}

    	InsertMenu(hMenu, indexMenu++, MF_STRING | MF_POPUP | MF_BYPOSITION, (UINT_PTR)hSubMenu, szMenuText_Popup);
    	InsertMenu(hMenu, indexMenu++, MF_SEPARATOR | MF_BYPOSITION, 0, NULL);

    	return ResultFromShort(idCmd - idCmdFirst);
   }
   return NOERROR;
}

STDMETHODIMP
CShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
{
    HRESULT hr = E_INVALIDARG;
    if (!HIWORD(lpcmi->lpVerb))
    {
        UINT idCmd = LOWORD(lpcmi->lpVerb);
        switch (idCmd)
        {
            case 0:
                hr = DoInstall(lpcmi->hwnd, lpcmi->lpDirectory, lpcmi->lpVerb, lpcmi->lpParameters, lpcmi->nShow);
                break;
            case 1:
                hr = DoUninstall(lpcmi->hwnd, lpcmi->lpDirectory, lpcmi->lpVerb, lpcmi->lpParameters, lpcmi->nShow);
                break;
            case 2:
                hr = DoAbout(lpcmi->hwnd, lpcmi->lpDirectory, lpcmi->lpVerb, lpcmi->lpParameters, lpcmi->nShow);
                break;
	    default:
                break;
        }
    }
    return hr;
}

STDMETHODIMP
CShellExt::GetCommandString(UINT idCmd, UINT uFlags, UINT FAR *reserved, LPSTR pszName, UINT cchMax)
{
    _UNUSED_PARAMETER(reserved);
    _UNUSED_PARAMETER(uFlags);

    *pszName = 0;
    cchMax   = 40;
    char psz[40];

    switch (idCmd)
    {
        case 0:
	    LoadString(g_hmodThisDll, IDCMD_INSTALL, psz, cchMax);
            break;
        case 1:
	    LoadString(g_hmodThisDll, IDCMD_UNINSTALL, psz, cchMax);
            break;
        case 2:
	    LoadString(g_hmodThisDll, IDCMD_ABOUT, psz, cchMax);
            break;
	default:
	    break;
    }
    wcscpy((unsigned short *)pszName, _WCSTR(psz));
    return NOERROR;
}

4. The most interesting stuff is contained in file dllreg_job.cpp, and this is the _GetFullFileName function. The others are handled in on eroutine, which simply format a command line using the regsvr32.exe tool (_DoRegisterJob).


STDMETHODIMP
CShellExt::_GetFullFileName()
{
    HRESULT hr = S_FALSE;

    //	IEnumFORMATETC. Needed for format enumeration.
    IEnumFORMATETC *pefEtc = 0;
    hr = m_pDataObj->EnumFormatEtc(DATADIR_GET, &pefEtc);
    if(SUCCEEDED(hr))
    {
	hr = pefEtc->Reset();	//	Reset enumeration.
	if(SUCCEEDED(hr))
	{
	    //	FORMATETC. Needed for get data about object.
	    FORMATETC fEtc;
	    ULONG ulFetched = 0L;
	    while(TRUE)
	    {
	        hr = pefEtc->Next(1, &fEtc, &ulFetched);
		if(FAILED(hr) || (ulFetched <= 0))
		    break;

		//	'Arm' format and 'launch' to obtain STGMEDIUM...
		fEtc.cfFormat	= CF_HDROP;
		fEtc.dwAspect	= DVASPECT_CONTENT;
		fEtc.lindex	= -1;
		fEtc.ptd	= NULL;
		fEtc.tymed	= TYMED_HGLOBAL;

		//	IDataObject : GetData. Returned as TYMED_HGLOBAL.
		STGMEDIUM stgM;
		hr = m_pDataObj->GetData(&fEtc, &stgM);
		if(SUCCEEDED(hr))
		{
			if(stgM.tymed == TYMED_HGLOBAL)
			{
			    m_szFileUserClickedOn[0] = '\0';

			    if(DragQueryFile((HDROP)stgM.hGlobal, (UINT)(-1), NULL, 0) == 1)
			    {
				    //	one file only; eliminate this and improve code to allow multiple-DLL file (un)registering
				    DragQueryFile((HDROP)stgM.hGlobal, 0, m_szFileUserClickedOn, _MAX_PATH + 1);
			    }
			}
		}
	    }
        }
    }

    if(pefEtc)
	    pefEtc->Release();
    return hr;
}

Downloads

Download Release DLL – 23 Kb
Download source code – 23 Kb

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read