返回

Windows注册表命令占位符详解及C++解析实现

windows

Windows 注册表命令占位符详解及 C++ 解析实现

在 Windows 注册表中,我们经常会在 HKEY_CLASSES_ROOT 下的子键,例如HKEY_CLASSES_ROOT\Directory\Background\shell 里面看到一些配置好的右键菜单处理程序。 这些处理程序其实就是一些命令,当你在右键菜单里点一下,这些命令就被执行了。 除了上面说的那个位置, 你还可能在注册表其他地方看见类似的东西。 注册这些命令时, 用的占位符可能不太一样, 比如 %1%V, 还有 %v 之类的。

这次我们要解决的问题是: 能不能找个地方把这些占位符都列出来, 说明每个代表啥意思?另外就是一条命令里能不能塞进去好几个占位符?

最后,咱们还想用 C++ 写一个类似于 std::sprintf() 的函数, 不过是专门处理这些占位符的。

一、 注册表命令占位符解析

1.1. 产生原因

Windows 使用这些占位符,是为了在执行命令时动态地替换成实际的值。例如,你在哪个文件或文件夹上点右键,相应的路径信息就要替换到命令里。 不用占位符,你就得为每一个可能的文件或文件夹写一条单独的命令,这肯定不行。

1.2. 常见占位符

虽然微软没有明确给出一个包含所有占位符的官方文档, 但咱们可以根据平时的经验和一些网上的资料,整理出一个常用的列表:

占位符 说明 适用范围
%1%L 被选中的文件/文件夹的完整路径(带引号)。 如果选中了多个,通常只代表第一个。 常用
%v%V 当前项的默认值。 对于文件夹, 通常是文件夹的完整路径(不带引号)。 常用
%* 所有选中的文件/文件夹(带引号,以空格分隔)。 shell 扩展
%~ 和批处理参数修饰符类似,可以扩展%1,比如获取文件名,如%~n1 常用
%h, %i, %s IDLIST items 不常用
%c The address of a ITEMIDLIST structure. 不常用
%d The path to a folder. 不常用

注意: 上面只是一些常见的, 实际情况可能更复杂,有的占位符可能在不同的 context 下,意义还不一样. 而且,有些是只用于特定的 shell 扩展的.

1.3. 占位符组合使用

多个占位符是可以在同一个命令里使用的。比如:

"C:\MyProgram.exe" "%1" "%v"  "%~n1"

这条命令会把第一个选中的文件/文件夹的完整路径、当前文件夹路径、还有第一个选中项的文件名部分都传给 MyProgram.exe

二、 解决方案: 自行实现解析

既然没法直接得到官方列表, 比较好的解决办法,就是咱自己来解析:

  1. 根据上面提供的常用占位符表, 构建一个映射关系. 这样, 就可以查找每个占位符代表什么意思.
  2. 实现一个解析函数。 输入是注册表里读出来的命令字符串, 输出是一个处理后的字符串 (或者一个字符串列表), 其中占位符都换成了实际的值.
  3. 注意不同环境使用不通占位符组合,解析需进行对应支持。

2.1. 解析原理

基本思路是这样的:

  1. 扫描输入的命令字符串。
  2. 找到 '%' 符号。
  3. '%' 后面紧跟着的就是占位符 (可能是单个字符,也可能是几个字符).
  4. 根据之前建立的映射关系, 把占位符换成对应的值.
  5. 重复以上步骤,直到字符串结尾。

2.2. C++ 代码示例

#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <windows.h> // 获取文件路径等

std::string ExpandRegistryCommand(const std::string& command, const std::vector<std::string>& selectedItems) {
    std::map<std::string, std::string> placeholders;
    if (!selectedItems.empty()) {
        placeholders["%1"] = "\"" + selectedItems[0] + "\"";  // 第一个选中项
        placeholders["%L"] = "\"" + selectedItems[0] + "\"";  // 兼容性

        //获取第一个文件的文件名
           size_t lastSlash = selectedItems[0].find_last_of("/\\");
            if (lastSlash != std::string::npos)
            {
                placeholders["%~n1"] =  selectedItems[0].substr(lastSlash + 1) ;
            }


    }


        //获取当前路径
    char currentDir[MAX_PATH];
        if (GetCurrentDirectory(MAX_PATH, currentDir) > 0)
        {
             placeholders["%v"] =  currentDir ;
              placeholders["%V"] = currentDir;
        }

    std::string expandedCommand;
    for (size_t i = 0; i < command.size(); ++i) {
        if (command[i] == '%') {
            std::string placeholder;
            // 从%后面开始构建占位符,处理 %~n1 类型的情况
                size_t j = i +1;
                 while(j < command.size() && (isalnum(command[j]) || command[j] == '~' ))
                 {
                  placeholder +=command[j];
                   j++;
                 }

                placeholder = "%" + placeholder;
            //查看是否查找到内容
            if (placeholders.count(placeholder) > 0) {
                expandedCommand += placeholders[placeholder];

                 i = j-1;//调整游标,
            } else {

              // 对于找不到的占位符, 直接原样保留 (或者你可以选择报错).
                expandedCommand += '%';
                 if(j ==i+1) i=j; //针对单纯一个%的情况, 需要游标步进
                 else  i= j -1;


            }

        } else {
            expandedCommand += command[i];
        }
    }

    return expandedCommand;
}
//简便起见, 不处理 `*` 这种需要多个参数的。

int main() {
    std::vector<std::string> selected;
    selected.push_back("C:\\Test\\MyFile.txt");
        selected.push_back("D:\\Test\\MyFolder");

        std::string regCommand = R"("C:\MyProgram\MyProgram.exe" "%1" --file="%~n1" -in  "%v")";
    //注意: 上面用了 C++11 的 raw string literal,为了避免转义反斜杠
        std::string finalCommand = ExpandRegistryCommand(regCommand, selected);

    std::cout << "Original command: " << regCommand << std::endl;
    std::cout << "Expanded command: " << finalCommand << std::endl;
   getchar();
    return 0;
}

代码解释:

  • ExpandRegistryCommand 函数: 核心的解析函数.
  • placeholders:存储占位符和对应值的 map.
  • GetCurrentDirectory:用来获取 %v 这类代表当前目录的占位符需要的值(这个例子没获取选中的文件夹的路径,只是一个示例).
  • 支持扩展文件名占位符 %~n1.
    *循环逻辑: 通过循环遍历,查找%, 构建出key,到map里查找。处理游标步进,没有找到就按原样输出

运行结果示例:

Original command: "C:\MyProgram\MyProgram.exe" "%1" --file="%~n1" -in  "%v"
Expanded command: "C:\MyProgram\MyProgram.exe" "C:\Test\MyFile.txt" --file="MyFile.txt" -in  C:\Windows

2.3. 安全建议

  • 参数验证: 如果你要基于解析出来的命令来执行某些操作, 一定要验证参数, 避免命令注入攻击. 比如,MyProgram.exe如果设计上存在漏洞, 别人就可以利用你注册的命令干坏事.
  • 最小权限原则: 给你的程序或服务分配权限时, 只给它必要的, 别太大方了.

####2.4 进阶技巧

  1. 支持更多的占位符 :

    • 你可以通过测试不同的右键菜单命令, 还有查看 MSDN 上关于 Shell 编程的一些零散资料, 逐步扩充你的 placeholders 表.
    • %*, 需要把所有的选中项都加进去,用空格隔开,要稍微改一下代码逻辑.
  2. 处理更复杂的格式 :

    • 有的时候注册表里存的可能不是直接的命令, 而是一个 COM 对象的 CLSID,你可能还需要去查 CLSID 对应的 InProcServer32 键, 才能找到真正要执行的东西.
    • 有些占位符会和数字组合(比如, %1, %2... 代表多个选中的项目), 你可能需要更精细的解析逻辑。
  3. 错误处理 :

    • 对于未知的占位符, 除了简单地保留, 你可以记录一个错误日志, 或者弹个提示.
    • 注册表读取失败、 路径获取失败等都要有处理。

三、 总结要点

  • 注册表里的命令占位符没有特别全的官方文档, 只能自己整理和实现。
  • 处理这问题主要靠字符串解析, 注意不同场景不同组合.
  • 安全问题必须重视, 命令注入是大问题。
  • C++ 代码仅作基本处理,完整处理各种异常很繁琐。