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


Comments

  • Using shortcut in SendTo menu

    Posted by Legacy on 12/03/1999 12:00am

    Originally posted by: Alpha Rauscheid

    It is also possible to put a shortcut to regsvr32.exe into the folder %SystemRoot%\Profiles\<UserName>\SendTo. Then a DLL can simply be registered by invoking the Sent To context menu.
    To get the equilvalent for unregistering, you can copy and rename the shortcut within the folder, then call the new shortcut's properties and extend the Target line in the shortcut tab with the parameter /u.
    This method (and also Glenn's proposal of a REG file) are fast paced solutions.
    The value of Sardaukar's contribution is the demonstration of interesting programming background.
    Thank you for this

    Alpha

    Reply
  • just add some registry entries for the same results

    Posted by Legacy on 12/02/1999 12:00am

    Originally posted by: Glenn Carr

    Maybe I'm missing something but couldn't you just add the following to the registry with this .reg file?  The following adds 'Register' and 'Unregister' context menus for dlls. 
    
    

    REGEDIT4

    [HKEY_CLASSES_ROOT\dllfile\shell]

    [HKEY_CLASSES_ROOT\dllfile\shell\SelfRegister]
    @="&Register"

    [HKEY_CLASSES_ROOT\dllfile\shell\SelfRegister\command]
    @="regsvr32 %1"

    [HKEY_CLASSES_ROOT\dllfile\shell\SelfUnregister]
    @="&Unregister"

    [HKEY_CLASSES_ROOT\dllfile\shell\SelfUnregister\command]
    @="regsvr32 /U %1"

    This works for me. What does your shell extension do besides register and unregister the DLLs? BTW, this originated on www.worldofatl.com.

    Glenn

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • This ESG study by Mark Peters evaluated a common industry-standard disk VTl deduplication system (with 15:1 reduction ratio) versus a tape library with LTO-5, drives with full nightly backups, over a five-year period.  The scenarios included replicated systems and offsite tape vaults.  In all circumstances, the TCO for VTL with deduplication ranged from about 2 to 4 times more expensive than the LTO-5 tape library TCO. The paper shares recent ESG research and lots more. 

  • Packaged application development teams frequently operate with limited testing environments due to time and labor constraints. By virtualizing the entire application stack, packaged application development teams can deliver business results faster, at higher quality, and with lower risk.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds