Visual C++右键菜单Shell扩展故障排查求助
右键菜单Shell扩展无法生效的排查
我基于Visual C++开发右键菜单Shell扩展,目标是右键点击时显示自定义菜单项。已经用regsvr32.exe注册ShellExtension.dll并重启资源管理器,但功能完全没效果,求排查问题。代码如下:
ShellExtension.h
#pragma once #include <windows.h> #include <shlobj.h> #include <comdef.h> #include <tchar.h> // 请添加此项 #define MYUUID "{6f81fd22-1c80-4619-a401-22d66dc3410b}" #define NomeDaExtensao "ShellExtension" #define Title "Shell Extension" class __declspec(uuid(MYUUID)) ShellExtension : public IShellExtInit, public IContextMenu { public: // IUnknown STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject); STDMETHODIMP_(ULONG) AddRef(); STDMETHODIMP_(ULONG) Release(); // IShellExtInit STDMETHODIMP Initialize(LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDataObj, HKEY hKeyProgID); // IContextMenu STDMETHODIMP QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags); STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO pici); STDMETHODIMP GetCommandString(UINT_PTR idCmd, UINT uType, UINT* pReserved, LPSTR pszName, UINT cchMax); private: ULONG m_cRef; };
ShellExtension.cpp
#include <windows.h> #include <strsafe.h> #include "ShellExtension.h" #include "resource.h" HANDLE g_hEventSource = NULL; STDMETHODIMP ShellExtension::QueryInterface(REFIID riid, void** ppvObject) { if (riid == IID_IUnknown || riid == IID_IShellExtInit || riid == IID_IContextMenu) { *ppvObject = this; AddRef(); return S_OK; } *ppvObject = NULL; return E_NOINTERFACE; } STDMETHODIMP_(ULONG) ShellExtension::AddRef() { return InterlockedIncrement(&m_cRef); } STDMETHODIMP_(ULONG) ShellExtension::Release() { ULONG cRef = InterlockedDecrement(&m_cRef); if (cRef == 0) delete this; return cRef; } STDMETHODIMP ShellExtension::Initialize(LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDataObj, HKEY hKeyProgID) { // Initialize实现 m_cRef = 1; // 在此初始化引用计数器 return S_OK; } #define IDI_MYICON 101 // 确保此编号与资源文件中的一致 #define IDM_DISPLAY 0 STDMETHODIMP ShellExtension::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) { if (uFlags & CMF_DEFAULTONLY) return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(0)); // 指定要使用的图标 HICON hIcon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_MYICON)); // 使用ReportEvent记录事件的示例 if (g_hEventSource) { LPCWSTR pInsertStrings[1] = { TEXT("尝试添加菜单项。") }; ReportEvent(g_hEventSource, EVENTLOG_INFORMATION_TYPE, // 事件类型 0, // 事件类别 1, // 事件ID,您需在消息文件中定义 NULL, // 用户SID 1, // 插入字符串数量 0, // 二进制数据大小 pInsertStrings, // 插入字符串数组 NULL); // 二进制数据 } InsertMenu(hmenu, indexMenu, MF_STRING | MF_BYPOSITION, idCmdFirst + IDM_DISPLAY, _T("&Display File Name")); return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(1)); // 表示已添加一个菜单项 } STDMETHODIMP ShellExtension::InvokeCommand(LPCMINVOKECOMMANDINFO pici) { if (!HIWORD(pici->lpVerb)) { if (LOWORD(pici->lpVerb) == 0) // 第一个命令(idCmdFirst) { MessageBox(NULL, TEXT("菜单项已点击!"), TEXT("提示"), MB_OK); } } return S_OK; } STDMETHODIMP ShellExtension::GetCommandString(UINT_PTR idCmd, UINT uType, UINT* pReserved, LPSTR pszName, UINT cchMax) { return E_NOTIMPL; } STDAPI DllRegisterServer(void) { HKEY hKey; LONG lRes; // 注册事件源 lRes = RegCreateKeyEx(HKEY_LOCAL_MACHINE, TEXT("SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\") NomeDaExtensao, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL); if (lRes == ERROR_SUCCESS) { TCHAR szPath[MAX_PATH]; GetModuleFileName(GetModuleHandle(NULL), szPath, MAX_PATH); // 指定消息文件路径(可以是模块本身,但通常会有单独的消息文件) RegSetValueEx(hKey, TEXT("EventMessageFile"), 0, REG_SZ, (BYTE*)szPath, (lstrlen(szPath) + 1) * sizeof(TCHAR)); // 指定消息文件类型 DWORD dwSupport = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE; RegSetValueEx(hKey, TEXT("TypesSupported"), 0, REG_DWORD, (BYTE*)&dwSupport, sizeof(DWORD)); RegCloseKey(hKey); } else { // 处理注册错误 return SELFREG_E_CLASS; } // 创建类的CLSID键 lRes = RegCreateKeyEx(HKEY_CLASSES_ROOT, TEXT("CLSID\\") MYUUID, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL); if (lRes != ERROR_SUCCESS) return SELFREG_E_CLASS; // 指定类名称 RegSetValueEx(hKey, NULL, 0, REG_SZ, (BYTE*)TEXT(Title), sizeof(TEXT(Title))); // 添加InprocServer32项 HKEY hSubKey; lRes = RegCreateKeyEx(hKey, TEXT("InprocServer32"), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hSubKey, NULL); if (lRes == ERROR_SUCCESS) { // DLL的完整路径 TCHAR szModule[MAX_PATH]; GetModuleFileName(GetModuleHandle(NULL), szModule, MAX_PATH); RegSetValueEx(hSubKey, NULL, 0, REG_SZ, (BYTE*)szModule, (lstrlen(szModule) + 1) * sizeof(TCHAR)); RegSetValueEx(hSubKey, TEXT("ThreadingModel"), 0, REG_SZ, (BYTE*)TEXT("Apartment"), sizeof(TEXT("Apartment"))); RegCloseKey(hSubKey); } // 将Shell扩展添加到"发送到"菜单 lRes = RegCreateKeyEx(HKEY_CLASSES_ROOT, TEXT("AllFileSystemObjects\\shellex\\ContextMenuHandlers\\") NomeDaExtensao, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hSubKey, NULL); if (lRes == ERROR_SUCCESS) { RegSetValueEx(hSubKey, NULL, 0, REG_SZ, (BYTE*)TEXT(MYUUID), sizeof(TEXT(MYUUID))); RegCloseKey(hSubKey); } RegCloseKey(hKey); return lRes == ERROR_SUCCESS ? S_OK : SELFREG_E_CLASS; } STDAPI DllUnregisterServer(void) { RegDeleteKey(HKEY_LOCAL_MACHINE, TEXT("SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\") NomeDaExtensao); // 删除对应的注册表项 RegDeleteKey(HKEY_CLASSES_ROOT, TEXT("AllFileSystemObjects\\shellex\\ContextMenuHandlers\\") NomeDaExtensao); RegDeleteTree(HKEY_CLASSES_ROOT, TEXT("CLSID\\") MYUUID); return S_OK; } // DllMain - DLL的入口点 BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: g_hEventSource = RegisterEventSource(NULL, TEXT(NomeDaExtensao)); break; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: if (g_hEventSource) { DeregisterEventSource(g_hEventSource); g_hEventSource = NULL; } break; } return TRUE; }
config.def
LIBRARY "ShellExtensionCpp" EXPORTS DllRegisterServer PRIVATE DllUnregisterServer PRIVATE
排查关键点
缺少COM类工厂实现
COM DLL必须导出DllGetClassObject函数,系统通过它获取类实例。添加以下代码到ShellExtension.cpp:class CShellExtensionClassFactory : public IClassFactory { public: STDMETHODIMP QueryInterface(REFIID riid, void** ppv) { if (riid == IID_IUnknown || riid == IID_IClassFactory) { *ppv = this; AddRef(); return S_OK; } *ppv = nullptr; return E_NOINTERFACE; } STDMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&m_cRef); } STDMETHODIMP_(ULONG) Release() { ULONG cRef = InterlockedDecrement(&m_cRef); if (cRef == 0) delete this; return cRef; } STDMETHODIMP CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppv) { if (pUnkOuter != nullptr) return CLASS_E_NOAGGREGATION; ShellExtension* pExt = new ShellExtension(); HRESULT hr = pExt->QueryInterface(riid, ppv); pExt->Release(); return hr; } STDMETHODIMP LockServer(BOOL fLock) { return S_OK; } private: ULONG m_cRef = 1; }; STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void** ppv) { if (rclsid != __uuidof(ShellExtension)) return CLASS_E_CLASSNOTAVAILABLE; CShellExtensionClassFactory* pFactory = new CShellExtensionClassFactory(); HRESULT hr = pFactory->QueryInterface(riid, ppv); pFactory->Release(); return hr; }同时在
config.def的EXPORTS中添加DllGetClassObject PRIVATE。Initialize方法错误
引用计数应在类构造函数中初始化,而非Initialize;且该方法需验证传入的文件对象,否则系统可能拒绝加载扩展:// 给ShellExtension添加构造函数 ShellExtension() : m_cRef(1) {} STDMETHODIMP ShellExtension::Initialize(LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDataObj, HKEY hKeyProgID) { if (!pDataObj) return E_INVALIDARG; FORMATETC fmt = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; STGMEDIUM stg; HRESULT hr = pDataObj->GetData(&fmt, &stg); if (FAILED(hr)) return hr; HDROP hDrop = static_cast<HDROP>(GlobalLock(stg.hGlobal)); if (!hDrop) { ReleaseStgMedium(&stg); return E_FAIL; } UINT fileCount = DragQueryFile(hDrop, 0xFFFFFFFF, nullptr, 0); GlobalUnlock(hDrop); ReleaseStgMedium(&stg); if (fileCount == 0) return E_FAIL; return S_OK; }64位系统兼容性
若系统为64位,需确保编译的DLL为64位版本,32位DLL无法在64位资源管理器中加载,检查项目平台目标设置。注册表权限
注册DLL需管理员权限,右键命令提示符选择「以管理员身份运行」,再执行regsvr32.exe ShellExtension.dll。GetCommandString实现完善
部分系统版本需要该方法返回菜单项描述,修改为:STDMETHODIMP ShellExtension::GetCommandString(UINT_PTR idCmd, UINT uType, UINT* pReserved, LPSTR pszName, UINT cchMax) { if (idCmd != IDM_DISPLAY) return E_INVALIDARG; if (uType == GCS_HELPTEXT) { return StringCchCopyA(pszName, cchMax, "Display the selected file name"); } return S_OK; }资源文件验证
确保IDI_MYICON在资源文件中存在,若暂时没有图标,可注释LoadIcon代码测试菜单项是否显示。资源管理器重启方式
打开任务管理器,结束「Windows资源管理器」进程后重新启动,确保注册表变更完全生效。
内容的提问来源于stack exchange,提问作者user12707
相关产品推荐
相关产品推荐

