Shell Extension Context Menu
The IContextMenu implementation from the Project Epsilon shell extension — adding a menu item with a bitmap and launching the associated program on selection.
The two functions that make a right-click menu entry work. QueryContextMenu is called by Explorer just before the popup appears: it inserts "Open with Project Epsilon" at the position the shell allocated (starting at idCmdFirst, returning the count of items added via ResultFromShort), and attaches a small bitmap to the entry with SetMenuItemBitmaps. InvokeCommand fires when the user picks it: it pulls the selected file out of the data object as an HDROP, verifies exactly one file was selected, reads the target executable's path from the registry, and launches it with ShellExecute - checking every possible failure code along the way.
STDMETHODIMP CShellExt::QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
{
UINT idCmd = idCmdFirst;
if ((uFlags & 0x000F) == CMF_NORMAL)
{
indexMenu++;
InsertMenu(hMenu, indexMenu, MF_STRING|MF_BYPOSITION, idCmd++, "Open with Project Epsilon");
HBITMAP BitMapResource = LoadBitmap(g_hmodThisDll, MAKEINTRESOURCE(IDB_SHELLMENU) );
SetMenuItemBitmaps(hMenu, indexMenu, MF_BYPOSITION, BitMapResource, BitMapResource);
return ResultFromShort(idCmd-idCmdFirst);
}
if (uFlags & CMF_EXPLORE)
{
indexMenu++;
InsertMenu(hMenu, indexMenu++, MF_STRING|MF_BYPOSITION, idCmd++, "Open with Project Epsilon");
HBITMAP BitMapResource = LoadBitmap(g_hmodThisDll, MAKEINTRESOURCE(IDB_SHELLMENU) );
SetMenuItemBitmaps(hMenu, indexMenu, MF_BYPOSITION, BitMapResource, BitMapResource);
return ResultFromShort(idCmd-idCmdFirst);
}
if (uFlags & CMF_DEFAULTONLY)
{
return NOERROR;
}
return NOERROR;
}
STDMETHODIMP CShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
{
HRESULT hr = E_INVALIDARG;
//If HIWORD(lpcmi->lpVerb) then we have been called programmatically
//and lpVerb is a command that should be invoked. Otherwise, the shell
//has called us, and LOWORD(lpcmi->lpVerb) is the menu ID the user has
//selected. Actually, it's (menu ID - idCmdFirst) from QueryContextMenu().
if (!HIWORD(lpcmi->lpVerb))
{
UINT idCmd = LOWORD(lpcmi->lpVerb);
STGMEDIUM medium;
UINT cbFiles = 0;
HRESULT hres = 0;
FORMATETC fmte = {CF_HDROP, (DVTARGETDEVICE FAR *)NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
if (m_pDataObj) //Paranoid check, m_pDataObj should have something by now...
hres = m_pDataObj->GetData(&fmte, &medium);
if (SUCCEEDED(hres))
{
if (medium.hGlobal)
cbFiles = DragQueryFile((HDROP)medium.hGlobal, (UINT)-1, 0, 0);
DragQueryFile((HDROP)medium.hGlobal, 0, m_szFileUserClickedOn, sizeof(m_szFileUserClickedOn));
if (cbFiles > 1)
{
MessageBox(NULL, "Project Epsilon can work with only one item at a time." ,"Execute Error",0 + MB_ICONHAND + MB_SYSTEMMODAL);
return NOERROR;
}
if (cbFiles == 0)
{
MessageBox(NULL, "No working file selected." ,"Execute Error",0 + MB_ICONHAND + MB_SYSTEMMODAL);
return NOERROR;
}
}
else{
MessageBox(NULL, "Failed to retreive selection name." ,"Execute Error",0 + MB_ICONHAND + MB_SYSTEMMODAL);
return NOERROR;
}
if(idCmd == 0)
{
char StringBuffer[1024];
unsigned long SizeOfStringBuffer = 1024;
if(!Get_StringRegistryValue(HKEY_LOCAL_MACHINE, REGISTRYPATHKEY, "Project Epsilon", StringBuffer, SizeOfStringBuffer))
{
MessageBox(NULL, "Project Epsilon executable path key not found. \r\nPlease reinstall Project Epsilon." ,"Execute Error",0 + MB_ICONHAND + MB_SYSTEMMODAL);
return S_OK;
}
HANDLE Result = ShellExecute(lpcmi->hwnd, NULL, StringBuffer, m_szFileUserClickedOn, lpcmi->lpDirectory, lpcmi->nShow);
if(Result == (VOID *) 0) MessageBox(NULL, "The operating system is out of memory or resources.","Execute Error", MB_ICONHAND + MB_SYSTEMMODAL);
if(Result == (VOID *) ERROR_FILE_NOT_FOUND) MessageBox(NULL, "The specified file was not found.","Execute Error", MB_ICONHAND + MB_SYSTEMMODAL);
if(Result == (VOID *) ERROR_PATH_NOT_FOUND) MessageBox(NULL, "The specified path was not found.","Execute Error", MB_ICONHAND + MB_SYSTEMMODAL);
if(Result == (VOID *) SE_ERR_ACCESSDENIED) MessageBox(NULL, "The operating system denied access to the specified file.","Execute Error", MB_ICONHAND + MB_SYSTEMMODAL);
if(Result == (VOID *) SE_ERR_NOASSOC) MessageBox(NULL, "There is no application associated with the given filename extension.","Execute Error", MB_ICONHAND + MB_SYSTEMMODAL);
if(Result == (VOID *) SE_ERR_OOM) MessageBox(NULL, "There was not enough memory to complete the operation.","Execute Error", MB_ICONHAND + MB_SYSTEMMODAL);
return NOERROR;
}
}
return hr;
}
The CMF_* flag checks matter: CMF_NORMAL means a real right-click on files, CMF_EXPLORE means the File menu in an Explorer window, and CMF_VERBSONLY/CMF_DEFAULTONLY are contexts where you usually add nothing. The trimmed ShellExecute error table shows the shape; the original checks all twelve codes (SE_ERR_DDEBUSY through SE_ERR_SHARE) the same way.