求可收集Windows第三方右键菜单项及处理程序的C++实现方案
获取Windows第三方右键菜单项及处理程序的C++实现
Windows右键菜单项主要存储在注册表的多个位置,部分第三方应用会通过**Shell扩展(COM组件)**动态添加菜单项,这类项不会直接显示文本字符串,而是通过CLSID指向对应的组件,这也是你搜索Notepad++文本却找不到的原因之一。以下是实现思路和完整代码:
一、关键注册表位置
右键菜单项分为两类,对应不同注册表路径:
- 普通静态菜单项
- 所有文件:
HKEY_CLASSES_ROOT\*\shell - 文件夹:
HKEY_CLASSES_ROOT\Directory\shell、HKEY_CLASSES_ROOT\Folder\shell - 指定文件类型:
HKEY_CLASSES_ROOT\<扩展名>\shell(如.txt\shell)
- 所有文件:
- Shell扩展菜单项
- 所有文件:
HKEY_CLASSES_ROOT\*\shellex\ContextMenuHandlers - 文件夹:
HKEY_CLASSES_ROOT\Directory\shellex\ContextMenuHandlers - 指定文件类型:
HKEY_CLASSES_ROOT\<扩展名>\shellex\ContextMenuHandlers
- 所有文件:
二、C++实现代码
以下代码会遍历指定注册表路径,收集菜单项名称和对应的处理程序路径,同时支持解析Shell扩展的CLSID:
#include <Windows.h> #include <Shlwapi.h> #include <vector> #include <string> #include <iostream> #pragma comment(lib, "Shlwapi.lib") // 展开环境变量(如%ProgramFiles%) BOOL ExpandEnvironmentStringsWrapper(LPCWSTR lpSrc, std::wstring& lpDst) { DWORD bufSize = ExpandEnvironmentStringsW(lpSrc, nullptr, 0); if (bufSize == 0) return FALSE; lpDst.resize(bufSize); bufSize = ExpandEnvironmentStringsW(lpSrc, &lpDst[0], bufSize); if (bufSize == 0) return FALSE; lpDst.resize(bufSize - 1); // 去掉末尾的NULL字符 return TRUE; } // 根据CLSID获取Shell扩展处理程序路径 BOOL GetShellExHandlerPath(LPCWSTR lpCLSID, std::wstring& handlerPath) { std::wstring clsidPath = L"CLSID\\"; clsidPath += lpCLSID; clsidPath += L"\\InprocServer32"; // 优先找进程内组件,找不到再试LocalServer32 HKEY hKey; LONG lResult = RegOpenKeyExW(HKEY_CLASSES_ROOT, clsidPath.c_str(), 0, KEY_READ, &hKey); if (lResult != ERROR_SUCCESS) { clsidPath = L"CLSID\\"; clsidPath += lpCLSID; clsidPath += L"\\LocalServer32"; lResult = RegOpenKeyExW(HKEY_CLASSES_ROOT, clsidPath.c_str(), 0, KEY_READ, &hKey); if (lResult != ERROR_SUCCESS) return FALSE; } WCHAR szPath[MAX_PATH] = {0}; DWORD dwSize = sizeof(szPath); lResult = RegQueryValueExW(hKey, nullptr, nullptr, nullptr, (LPBYTE)szPath, &dwSize); RegCloseKey(hKey); if (lResult != ERROR_SUCCESS) return FALSE; return ExpandEnvironmentStringsWrapper(szPath, handlerPath); } // 遍历指定注册表路径下的所有右键菜单项 BOOL EnumShellMenuItems(LPCWSTR lpRegPath, std::vector<std::pair<std::wstring, std::wstring>>& menuItems) { HKEY hKey; LONG lResult = RegOpenKeyExW(HKEY_CLASSES_ROOT, lpRegPath, 0, KEY_READ, &hKey); if (lResult != ERROR_SUCCESS) return FALSE; WCHAR szSubKey[MAX_PATH] = {0}; DWORD dwSubKeySize = MAX_PATH; DWORD dwIndex = 0; while (RegEnumKeyExW(hKey, dwIndex++, szSubKey, &dwSubKeySize, nullptr, nullptr, nullptr, nullptr) == ERROR_SUCCESS) { std::wstring itemName = szSubKey; // 读取菜单项显示文本(默认值) WCHAR szDisplayName[MAX_PATH] = {0}; DWORD dwDisplayNameSize = sizeof(szDisplayName); lResult = RegQueryValueExW(hKey, szSubKey, nullptr, nullptr, (LPBYTE)szDisplayName, &dwDisplayNameSize); if (lResult == ERROR_SUCCESS && wcslen(szDisplayName) > 0) { itemName = szDisplayName; } // 判断是普通菜单项还是Shell扩展 std::wstring commandPath = lpRegPath; commandPath += L"\\"; commandPath += szSubKey; commandPath += L"\\command"; HKEY hCmdKey; lResult = RegOpenKeyExW(HKEY_CLASSES_ROOT, commandPath.c_str(), 0, KEY_READ, &hCmdKey); if (lResult == ERROR_SUCCESS) { // 普通菜单项:读取command值 WCHAR szCmd[MAX_PATH] = {0}; DWORD dwCmdSize = sizeof(szCmd); lResult = RegQueryValueExW(hCmdKey, nullptr, nullptr, nullptr, (LPBYTE)szCmd, &dwCmdSize); RegCloseKey(hCmdKey); if (lResult == ERROR_SUCCESS) { std::wstring expandedCmd; if (ExpandEnvironmentStringsWrapper(szCmd, expandedCmd)) { menuItems.emplace_back(itemName, expandedCmd); } } } else { // 尝试Shell扩展路径 std::wstring shellExPath = lpRegPath; shellExPath += L"\\"; shellExPath += szSubKey; WCHAR szCLSID[MAX_PATH] = {0}; DWORD dwCLSIDSize = sizeof(szCLSID); lResult = RegQueryValueExW(hKey, szSubKey, nullptr, nullptr, (LPBYTE)szCLSID, &dwCLSIDSize); if (lResult == ERROR_SUCCESS) { std::wstring handlerPath; if (GetShellExHandlerPath(szCLSID, handlerPath)) { menuItems.emplace_back(itemName, handlerPath); } } } dwSubKeySize = MAX_PATH; ZeroMemory(szSubKey, sizeof(szSubKey)); } RegCloseKey(hKey); return TRUE; } // 获取指定文件的类型关联(如.txt对应txtfile) BOOL GetFileType(LPCWSTR lpFilePath, std::wstring& fileType) { WCHAR szFileType[MAX_PATH] = {0}; if (!AssocQueryStringW(ASSOCF_NONE, ASSOCSTR_CLASS, lpFilePath, nullptr, szFileType, nullptr)) { fileType = szFileType; return TRUE; } return FALSE; } int wmain(int argc, wchar_t* argv[]) { std::vector<std::pair<std::wstring, std::wstring>> menuItems; if (argc == 2) { // 收集指定文件的右键菜单项 std::wstring fileType; if (GetFileType(argv[1], fileType)) { std::wstring regPath = L"\\"; regPath += fileType; regPath += L"\\shell"; EnumShellMenuItems(regPath.c_str(), menuItems); // 同时收集所有文件通用的菜单项 EnumShellMenuItems(L"*\\shell", menuItems); } } else { // 默认收集所有文件和文件夹的右键菜单项 EnumShellMenuItems(L"*\\shell", menuItems); EnumShellMenuItems(L"Directory\\shell", menuItems); } // 输出结果 std::wcout << L"右键菜单项列表:" << std::endl; for (const auto& item : menuItems) { std::wcout << L"名称: " << item.first << std::endl; std::wcout << L"处理程序: " << item.second << std::endl; std::wcout << L"-------------------------" << std::endl; } return 0; }
三、关键说明
- 权限要求:运行程序需要管理员权限,否则无法访问部分受保护的注册表项。
- 动态菜单项:部分应用会通过
IContextMenu接口动态生成菜单项(如一些云盘客户端),这类项无法通过注册表枚举,需要调用Shell API的SHCreateShellItem和IContextMenu接口来枚举,如需支持可扩展代码。 - Notepad++示例:Notepad++通常会在
HKEY_CLASSES_ROOT\*\shell\Notepad++下添加静态菜单项,其command值为带参数的程序路径,代码会正确解析并输出。 - 环境变量处理:代码会自动展开注册表中的环境变量(如
%ProgramFiles%),转换为实际路径。
内容的提问来源于stack exchange,提问作者Joe J
相关产品推荐
相关产品推荐

