Menu Handler实现右键菜单控制
微软提供了一系列强大并且灵活Handler供开发者使用,帮助我们实现更加多样化的功能;在shell执行命令或者工作之前,留给开发者一个机会来自定义实现;
关联文件类型或者可以指定文件类型的handler
没有关联文件类型但是可以被调用的Handler
我们的目标是添加右键菜单,也就是Shortcut menu Handler,在文件右键菜单显示之前,自定义添加右键菜单
我用的开发平台是VS2010,VS创建ATL COM组件的方法网上很多,也可以用其他工具,平台没有局限性
具体实现接口建议参考官方文档:
https://docs.microsoft.com/zh-cn/windows/desktop/shell/shortcut-menu-using-dynamic-verbs
实现menu handler主要实现两个接口IShellExtInit和IContextMenu
// IShellExtInit
STDMETHODIMP Initialize(LPCITEMIDLIST, LPDATAOBJECT, HKEY);
// IContextMenu
STDMETHODIMP GetCommandString(UINT_PTR, UINT, UINT*, LPSTR, UINT);
STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO);
STDMETHODIMP QueryContextMenu(HMENU, UINT, UINT, UINT, UINT);
STDMETHODIMP Initialize(LPCITEMIDLIST, LPDATAOBJECT, HKEY):初始化接口,可以获取右键目标,自定义是否需要添加右键功能,本例中通过此函数判断目标文件名,如果右键的是我们的对象则添加右键菜单,否则不添加
TDMETHODIMP CMenuHandler::Initialize(LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDataObj, HKEY hProgID)
{
FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
STGMEDIUM stg = { TYMED_HGLOBAL };
HDROP hDrop = NULL;
if (NULL == pDataObj)
return E_INVALIDARG;
// 从IDataObject接口中获取CF_HDROP数据
if (FAILED(pDataObj->GetData(&fmt, &stg)))
return E_INVALIDARG;
// 获取指向实际数据的指针
hDrop = (HDROP)GlobalLock(stg.hGlobal);
if (hDrop == NULL)
{
ReleaseStgMedium(&stg);
return E_INVALIDARG;
}
// 查询选择的文件数量
UINT uFilesCount = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
if (1 != uFilesCount)
{
GlobalUnlock(stg.hGlobal);
ReleaseStgMedium(&stg);
return E_INVALIDARG;
}
// 将文件名称保存在变量中
DragQueryFile(hDrop, 0, m_szFilename, MAX_PATH);
// 判断名称是否包含GCCP5 以及是否存在exe
TCHAR *sGCCP64 = _T("XXXX64.exe");
TCHAR *sGCCP = _T("XXXX.exe");
HRESULT state = E_INVALIDARG;
if (_tcsstr(m_szFilename, sGCCP64) && (needCheckVersionInfo(TRUE)))
{
// 64位版本
state = S_OK;
}
else if (_tcsstr(m_szFilename, sGCCP) && (needCheckVersionInfo(FALSE)))
{
// 32位版本
state = S_OK;
}
else
{
// 不是目标程序 不添加右键
state = E_INVALIDARG;
}
GlobalUnlock(stg.hGlobal);
ReleaseStgMedium(&stg);
return state;
}
STDMETHODIMP GetCommandString(UINT_PTR, UINT, UINT*, LPSTR, UINT):系统帮助信息接口,本例中不提供帮助信息直接返回
STDMETHODIMP CMenuHandler::GetCommandString(UINT_PTR idCmd, UINT uFlags, UINT* pwReserved, LPSTR pszName, UINT cchMax)
{
// 不实现 不提供帮助信息
return E_NOTIMPL;
}
STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO):右键菜单响应接口,当点击右键菜单时执行,本例中我们直接调用第三方exe显示版本信息
STDMETHODIMP CMenuHandler::InvokeCommand(LPCMINVOKECOMMANDINFO pCmdInfo)
{
if (0 != HIWORD(pCmdInfo->lpVerb))
return E_INVALIDARG;
switch (LOWORD(pCmdInfo->lpVerb))
{
case 0:
{
// dll注册是根据系统位数来的,但是调用exe位数得根据版本来
DWORD dwRet, dwErr;
LPTSTR pszOldVal = NULL;
LPTSTR pszNewVal = NULL;
BOOL fExist;
pszOldVal = (LPTSTR)malloc(BUFSIZE*sizeof(TCHAR));
if (NULL == pszOldVal)
{
return E_INVALIDARG;
}
// 获取当前环境变量
dwRet = GetEnvironmentVariable(_T("Path"), pszOldVal, BUFSIZE);
if (0 == dwRet)
{
// 获取失败 没有拿到该环境变量
dwErr = GetLastError();
if (ERROR_ENVVAR_NOT_FOUND == dwErr)
{
fExist = FALSE;
}
}
else if (BUFSIZE < dwRet)
{
// Path路劲太长,缓存大小不够,从新根据dwRet大小分配
pszOldVal = (LPTSTR)realloc(pszOldVal, dwRet*sizeof(TCHAR));
if (NULL == pszOldVal)
{
return E_INVALIDARG;
}
// 获取环境变量
dwRet = GetEnvironmentVariable(_T("Path"), pszOldVal, dwRet);
if (!dwRet)
{
// 获取失败 没有拿到环境变量
fExist = FALSE;
}
else
{
fExist = TRUE;
}
}
else
{
// 直接获取环境变量
fExist = TRUE;
}
// MessageBox(pCmdInfo->hwnd, pszOldVal, _T("Test"), MB_OK);
// 追加Bin目录到环境变量
pszNewVal = (LPTSTR)malloc((dwRet + MAX_PATH)*sizeof(TCHAR));
if (NULL == pszNewVal)
{
if (!pszOldVal)
{
free(pszOldVal);
pszOldVal = NULL;
}
return E_INVALIDARG;
}
_tcsncpy_s(pszNewVal, (dwRet + MAX_PATH), m_szBinname, _tcslen(m_szBinname));
_tcscat_s(pszNewVal, (dwRet + MAX_PATH), _T(";"));
_tcscat_s(pszNewVal, (dwRet + MAX_PATH), pszOldVal);
// MessageBox(pCmdInfo->hwnd, pszNewVal, _T("Test"), MB_OK);
if (!SetEnvironmentVariable(_T("Path"), pszNewVal))
{
// 环境变量设置失败 exe是无法启动的 直接退出
if (!pszOldVal)
{
free(pszOldVal);
pszOldVal = NULL;
}
if (!pszNewVal)
{
free(pszNewVal);
pszNewVal = NULL;
}
return E_INVALIDARG;
}
try
{
UINT code = (UINT)ShellExecute(pCmdInfo->hwnd, _T("open"), m_szExename, _T(""), m_szBinname, SW_SHOWNORMAL);
}
catch (...)
{
}
// 重置环境变量
if (fExist)
{
if (!SetEnvironmentVariable(_T("Path"), pszOldVal))
{
return E_INVALIDARG;
}
}
else
{
SetEnvironmentVariable(_T("Path"), NULL);
}
// 内存释放
if (!pszOldVal)
{
free(pszOldVal);
pszOldVal = NULL;
}
if (!pszNewVal)
{
free(pszNewVal);
pszNewVal = NULL;
}
break;
}
default:
{
return E_INVALIDARG;
break;
}
}
return S_OK;
}
STDMETHODIMP QueryContextMenu(HMENU, UINT, UINT, UINT, UINT):右键菜单添加接口,本例中添加一个右键菜单
STDMETHODIMP CMenuHandler::QueryContextMenu(HMENU hmenu, UINT uMenuIndex, UINT uidFirstCmd, UINT uidLastCmd, UINT uFlags)
{
if (uFlags & CMF_DEFAULTONLY)
return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0);
// 添加菜单
InsertMenu(hmenu, uMenuIndex, MF_BYPOSITION, uidFirstCmd, _T("查看版本信息"));
return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 1);
}
DLL创建完成之后,进行注册,测试效果OK,如果需要版本有安装程序,安装时候将dll注册
刚开始做的时候网上搜了很多人的例子,但是自己没有成功,之后将查看文档搞懂接口之后其实工作量不大,中间也遇到一些傻瓜式问题
1)dll注册的时候,64位注册64Dll,32位注册32位dll
2)32位dll无法注册成功:平台工具集要选择支持XP的平台工具集
3)dll被占用无法删除:可以进行重命名移动