
方法函数IFACEMETHOD(EnumSubCommands)(IEnumExplorerCommand** ppEnum) override; IFACEMETHOD(GetCanonicalName)(GUID* pguidCommandName) override; IFACEMETHOD(GetFlags)(EXPCMDFLAGS* pFlags) override; IFACEMETHOD(GetIcon)(IShellItemArray* psiItemArray, LPWSTR* ppszIcon) override; IFACEMETHOD(GetState)(IShellItemArray* psiItemArray, BOOL fOkToBeSlow, EXPCMDSTATE* pCmdState) override; IFACEMETHOD(GetTitle)(IShellItemArray* psiItemArray, LPWSTR* ppszName) override; IFACEMETHOD(GetToolTip)(IShellItemArray* psiItemArray, LPWSTR* ppszInfotip) override; IFACEMETHOD(Invoke)(IShellItemArray* psiItemArray, IBindCtx* pbc) override;详解/** * 用于配置子命令集 * param ppEnum _out_ IEnumExplorerCommand** 返回包含可用于遍历子命令集 * return HRESULT */ HRESULT EnumSubCommands( IEnumExplorerCommand** ppEnum ); /** * 检索 当前命令的 GUID * param pguidCommandName [out] 当此方法成功返回时将接收命令的 GUID该 GUID 在注册表中声明它 * return S_OK */ HRESULT GetCanonicalName( GUID* pguidCommandName ); /** *获取与指定的 Windows 资源管理器命令项关联的标志。 *param pFlags [out] 此方法返回时此值指向当前命令标志。 ECF_DEFAULT (0x000)默认值 ECF_HASSUBCOMMANDS (0x001)子命令 ECF_HASSPLITBUTTON (0x002)拆分按钮 ECF_HIDELABEL (0x004)隐藏按钮 ECF_ISSEPARATOR (0x008)命令是分隔符。 ECF_HASLUASHIELD (0x010)显示 UAC 防护板。 ECF_SEPARATORBEFORE (0x020)命令位于紧邻分隔符下方的菜单中。 ECF_SEPARATORAFTER (0x040)命令位于紧靠分隔符上方的菜单中。 ECF_ISDROPDOWN (0x080)选择命令会打开一个下拉子菜单 (例如“ 包含在库) 中”。 ECF_TOGGLEABLE (0x100) ECF_AUTOMENUICONS (0x200) */ HRESULT GetFlags( EXPCMDFLAGS* pFlags ); /** * 返回该命令项的图标路径 * param psiItemArray _In_ IShellItemArray* 指向 IShellItemArray 的指针 * param ppszIcon _out_ LPWSTR* 接收指向包含图标路径的字符串的指针。 * return */ HRESULT GetIcon( IShellItemArray* psiItemArray, LPWSTR* ppszIcon ); /** * 返回该命令项的状态 * param psiItemArray _In_ IShellItemArray* 指向 IShellItemArray 的指针 * param fOkToBeSlow _In_ BOOL 指示是否可以等待。 * param pCmdState _Out_ EXPCMDSTATE* ECS_ENABLED 0, // 该项已启用 ECS_DISABLED 0x1, // 灰显且不可访问的项 ECS_HIDDEN 0x2, // 隐藏状态 ECS_CHECKBOX 0x4, // 检查框显示 ECS_CHECKED 0x8, // 带有检查框的项并选中该检查框 ECS_RADIOCHECK 0x10 // 项是通过单选按钮选择的一组互斥选项之一。 * return */ HRESULT GetState( IShellItemArray* psiItemArray, BOOL fOkToBeSlow, EXPCMDSTATE* pCmdState ); /** * 提供该命令项的名称 * param psiItemArray _In_ IShellItemArray* 指向 IShellItemArray 的指针 * param ppszName _Out_ LPWSTR* 获取指向包含命令项名称的字符串的指针。 * return */ HRESULT GetTitle( IShellItemArray* psiItemArray, LPWSTR* ppszName ); /** * 提供该命令项的提示 * param psiItemArray _In_ IShellItemArray* 指向 IShellItemArray 的指针 * param ppszInfotip _Out_ LPWSTR* 获取指向包含命令项信息提示的字符串的指针。 * return */ HRESULT GetToolTip( IShellItemArray* psiItemArray, LPWSTR* ppszInfotip ); /** * 命令函数 * param psiItemArray _In_ IShellItemArray* 你的代码通过它获取操作对象是谁 psiItemArray-EnumItems(pEnum) //获取 IEnumShellItems 枚举器(包括每个文件的信息) pEnum-Clone() pEnum-Next() pEnum-Reset() pEnum-Skip() psiItemArray-GetAttributes(); //获取项集的属性标志 psiItemArray-GetCount(a); //获取对象数量 psiItemArray-GetItemAt(0, psi); //获取索引处的项 psi-Compare() psi-GetAttributes() psi-GetDisplayName() psi-GetParent() psiItemArray-GetPropertyDescriptionList(); //属性描述列表 psiItemArray-GetPropertyStore(); //属性存储区 * param pbc _In_ IBindCtx* 提供在什么上下文/条件下操作 通常可为 nullptr大部分场景不需要使用 pbc-SetBindOptions(); //设置绑定选项 pbc-GetBindOptions(); //获取绑定选项 pbc-EnumObjectParam(); //枚举对象参数 pbc-RegisterObjectParam(); //注册对象参数 pbc-GetObjectParam(); //获取对象参数 pbc-RevokeObjectParam(); //撤销对象参数 pbc-RegisterObjectBound(); //注册对象绑定 pbc-RevokeObjectBound(); //撤销对象绑定 pbc-ReleaseBoundObjects(); //释放绑定对象 pbc-GetRunningObjectTable(); //获取运行对象表 * return */ HRESULT Invoke( IShellItemArray* psiItemArray, IBindCtx* pbc );案例// 模板 IFACEMETHOD(EnumSubCommands)(IEnumExplorerCommand** ppEnum) override{*ppEnum nullptr; return E_NOTIMPL;} IFACEMETHOD(GetCanonicalName)(GUID* pguidCommandName) {*pguidCommandNameGetObjectCLSID();return S_OK;} IFACEMETHOD(GetFlags)(EXPCMDFLAGS* pFlags){*pFlagsECF_DEFAULT;return S_OK;} IFACEMETHOD(GetIcon)(IShellItemArray* psiItemArray, LPWSTR* ppszIcon){return SHStrDup(_T(Resource.dll,201), ppszIcon);} IFACEMETHOD(GetState)(IShellItemArray* psiItemArray, BOOL fOkToBeSlow, EXPCMDSTATE* pCmdState){*pCmdStateECS_ENABLED;return S_OK;} IFACEMETHOD(GetTitle)(IShellItemArray* psiItemArray, LPWSTR* ppszName){std::wstring titlename_T( test);return SHStrDup(titlename.c_str(), ppszName);} IFACEMETHOD(GetToolTip)(IShellItemArray* psiItemArray, LPWSTR* ppszInfotip){std::wstring tooltip_T( test);return SHStrDup(tooltip.c_str(), ppszInfotip);} IFACEMETHOD(Invoke)(IShellItemArray* psiItemArray, IBindCtx* pbc) override{return E_NOTIMPL;}EnumSubCommandsIFACEMETHOD(EnumSubCommands)(IEnumExplorerCommand** ppEnum) override { if (!ppEnum) return E_POINTER; m_currentIndex 0; // 重置枚举位置 this-AddRef(); // 返回前增加引用计数 *ppEnum static_castIEnumExplorerCommand*(this); return S_OK; }CloneIFACEMETHOD(Clone)(IEnumExplorerCommand** ppenum) override { *ppenum NULL; CComObjectCIExCmdMainMenus* pNew; HRESULT hr CComObjectCIExCmdMainMenus::CreateInstance(pNew); if (SUCCEEDED(hr)) { pNew-AddRef(); pNew-m_currentIndex m_currentIndex; pNew-m_subCommands m_subCommands; // 共享子命令 *ppenum static_castIEnumExplorerCommand*(pNew); } return hr; }NextIFACEMETHOD(Next)(ULONG celt, IExplorerCommand** pUICommand, ULONG* pceltFetched) override { if (!pUICommand) return E_POINTER; ULONG fetched 0; while (fetched celt m_currentIndex m_subCommands.size()) { pUICommand[fetched] m_subCommands[m_currentIndex]; pUICommand[fetched]-AddRef(); // 必须增加引用计数 m_currentIndex; fetched; } if (pceltFetched) *pceltFetched fetched; return (fetched celt) ? S_OK : S_FALSE; }Invoke#include windows.h #include shobjidl_core.h #include shlobj.h #include shellapi.h #include atlbase.h #include string #include vector #include propvarutil.h #include propkey.h // // IExplorerCommand::Invoke 完整实现 // STDMETHODIMP CMyExplorerCommand::Invoke( _In_ IShellItemArray* psiItemArray, _In_ IBindCtx* pbc) { // // 一、参数校验 // if (!psiItemArray) return E_INVALIDARG; // // 二、从 psiItemArray 获取选中项数量 // DWORD dwCount 0; HRESULT hr psiItemArray-GetCount(dwCount); if (FAILED(hr) || dwCount 0) return S_OK; // 没有选中项静默返回 // // 三、方式一通过 GetItemAt 按索引遍历推荐简单直观 // std::vectorstd::wstring filePaths; filePaths.reserve(dwCount); for (DWORD i 0; i dwCount; i) { CComPtrIShellItem psi; hr psiItemArray-GetItemAt(i, psi); if (FAILED(hr) || !psi) continue; // ------ 3.1 获取文件系统完整路径最常用 ------ PWSTR pszPath nullptr; hr psi-GetDisplayName(SIGDN_FILESYSPATH, pszPath); if (SUCCEEDED(hr) pszPath) { filePaths.emplace_back(pszPath); CoTaskMemFree(pszPath); // 必须释放 } else { // 非文件系统项如虚拟文件夹回退获取显示名称 PWSTR pszName nullptr; hr psi-GetDisplayName(SIGDN_NORMALDISPLAY, pszName); if (SUCCEEDED(hr) pszName) { filePaths.emplace_back(pszName); CoTaskMemFree(pszName); } } // ------ 3.2 获取项的属性标志判断是文件还是文件夹 ------ SFGAOF sfgaoAttrs 0; hr psi-GetAttributes( SFGAO_FOLDER | SFGAO_FILESYSTEM | SFGAO_LINK, sfgaoAttrs); if (SUCCEEDED(hr)) { bool bIsFolder (sfgaoAttrs SFGAO_FOLDER) ! 0; bool bIsFileSystem (sfgaoAttrs SFGAO_FILESYSTEM) ! 0; bool bIsLink (sfgaoAttrs SFGAO_LINK) ! 0; // 可根据 bIsFolder / bIsFileSystem / bIsLink 做分支处理 } } // // 四、方式二通过 EnumItems 枚举器遍历适合流式处理 // /* CComPtrIEnumShellItems pEnum; hr psiItemArray-EnumItems(pEnum); if (SUCCEEDED(hr) pEnum) { CComPtrIShellItem psi; while (pEnum-Next(1, psi, nullptr) S_OK) { PWSTR pszPath nullptr; hr psi-GetDisplayName(SIGDN_FILESYSPATH, pszPath); if (SUCCEEDED(hr) pszPath) { // 处理 pszPath ... CoTaskMemFree(pszPath); } psi.Release(); // 释放当前项准备获取下一项 } // 枚举器其他方法 // pEnum-Reset(); // 重置到开头可重新遍历 // pEnum-Skip(n); // 跳过 n 项 // pEnum-Clone(pEnum2); // 克隆枚举器注意部分实现不支持 } */ // // 五、方式三批量获取属性标志适合需要整体判断的场景 // /* SFGAOF bulkAttrs 0; // SIATTRIBFLAGS_AND: 所有项都有的属性位才置1 // SIATTRIBFLAGS_OR: 任一项有的属性位就置1 // SIATTRIBFLAGS_APPCOMPAT: 兼容性模式 hr psiItemArray-GetAttributes( SIATTRIBFLAGS_AND, SFGAO_FOLDER | SFGAO_FILESYSTEM, bulkAttrs); if (SUCCEEDED(hr)) { bool allFolders (bulkAttrs SFGAO_FOLDER) ! 0; // 如果所有选中项都是文件夹执行文件夹专属逻辑 } */ // // 六、方式四通过属性存储获取文件元数据高级用法 // /* CComPtrIPropertyStore pPropStore; hr psiItemArray-GetPropertyStore( GPS_DEFAULT, // 默认属性存储 IID_PPV_ARGS(pPropStore)); if (SUCCEEDED(hr) pPropStore) { PROPVARIANT var; PropVariantInit(var); // 获取文件修改时间 hr pPropStore-GetValue(PKEY_DateModified, var); if (SUCCEEDED(hr)) { // 使用 var 中的值... PropVariantClear(var); } } */ // // 七、处理 pbcIBindCtx 绑定上下文 // // pbc 在大多数 Invoke 场景中为 nullptr不需要处理。 // 以下展示 pbc 不为空时可做的操作 if (pbc) { // ------ 7.1 获取绑定选项 ------ BIND_OPTS bindOpts {}; bindOpts.cbStruct sizeof(BIND_OPTS); hr pbc-GetBindOptions(bindOpts); if (SUCCEEDED(hr)) { // bindOpts.grfFlags - 绑定标志 // bindOpts.grfMode - 存储打开模式STGM_READ 等 // bindOpts.dwTickCountDeadline - 超时限制 } // ------ 7.2 读取自定义对象参数 ------ // Shell 可能通过 RegisterObjectParam 注册了上下文信息 CComPtrIUnknown pUnkParam; hr pbc-GetObjectParam(LMyCustomParam, pUnkParam); if (SUCCEEDED(hr) pUnkParam) { // 使用自定义参数... } } // // 八、执行实际业务操作示例用默认程序打开所有选中文件 // for (const auto path : filePaths) { // ShellExecuteW 会自动使用文件关联的默认程序打开 HINSTANCE hInst ShellExecuteW( nullptr, // 无父窗口 Lopen, // 操作类型 path.c_str(), // 文件路径 nullptr, // 无额外参数 nullptr, // 使用文件所在目录 SW_SHOWNORMAL // 正常显示窗口 ); // 返回值 32 表示成功 if ((INT_PTR)hInst 32) { // 处理错误... } } return S_OK; }