使用c++编写com组件实现windows外壳扩展(自定义右键上下文菜单扩展)

news2024/12/28 12:39:23

一、作用

允许在 Windows 文件资源管理器中,当用户在文件、文件夹或空白处右键点击时,根据一定的逻辑显示自定义的菜单项

二、原理

COM组件

微软开发的一种软件架构模型,主要用于在不同编程语言之间实现二进制级别的可重用性和互操作性。它可以作为独立的模块分发,并能够在应用程序之间共享,广泛应用于 Windows 操作系统中的许多服务和应用程序中。(C++ 和 C# 是开发COM组件的主要语言。C++提供了最直接的访问,而C#通过.NET平台提供了现代化的工具和简化的开发流程。其他大多数编程语言,如Python、Java、和Go,尽管不能直接创建COM组件,但可以通过桥接(通常通过一个中间层(库或接口)来实现,比如JACOB允许Java程序通过调用DLL中的本地方法,与COM组件进行通信)或互操作库来使用现有的COM组件)
特点:

1、二进制标准

COM 是一种二进制标准,意味着 COM 组件可以用不同的编程语言(如 C++、C#、VB 等)来开发,并且可以在不需要重新编译的情况下互操作。它定义了如何在内存中布局数据和如何调用接口方法,因此只要遵循这个标准,组件就可以在不同的语言和环境中使用。


2、接口(Interface)

COM 组件通过接口来暴露功能。每个接口是一个纯虚拟函数表的集合,这意味着接口本身不包含任何实现,只定义了一组方法(函数)。客户端通过接口来调用组件的功能,而不关心组件的具体实现细节。
  特殊:
2.1、IUnknown 接口:COM组件中最基础、也是最重要的接口。它是所有 COM 接口的基类,所有的 COM 对象都必须实现 IUnknown 接口。IUnknown 提供了三种关键功能:接口查询、引用计数和对象的生命周期管理
           QueryInterface :
           作用:负责实现 COM 对象的接口查询功能。它允许客户端查询对象是否支持某个特定的接口
           签名:HRESULT QueryInterface(REFIID riid, void** ppvObject);(REFIID riid:传入要查询的接口的标识符(接口 ID,即 IID);void** ppvObject:输出参数,若查询成功,则指向请求的接口指针;返回值:如果对象支持请求的接口,则返回 S_OK,并且 ppvObject 将包含指向该接口的指针。否则返回 E_NOINTERFACE)
           AddRef:
           作用:用于增加对象的引用计数。当客户端获得一个 COM 对象的接口指针时,它需要调用 AddRef 来增加对象的引用计数,表示对该对象的引用,确保对象在使用期间不会被销毁。当有多个客户端使用同一个对象时,AddRef 通过增加引用计数来跟踪该对象的使用情况。
           签名: ULONG AddRef();(返回值:返回新的引用计数值。)
           Release :
           作用:用于减少对象的引用计数。当客户端不再需要使用对象时,它需要调用 Release 来减少引用计数。通过减少引用计数来管理对象的生命周期。当引用计数为零时,表示没有客户端在使用该对象,Release 会自动销毁对象并释放相关资源。
           签名:ULONG Release();(返回值:返回减少后的引用计数值。)
2.2、IClassFactory 接口 :类工厂(IClassFactory 接口) 是创建 COM 对象实例的关键机制。它为 COM 组件提供了一个标准化的创建实例的方式,使客户端可以通过系统函数如 CoCreateInstance 来请求创建组件实例。
             类工厂接口有两个主要的方法:
CreateInstance:
作用:用于创建一个新的 COM 对象实例。
签名:HRESULT CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppvObject);(pUnkOuter:指向控制对象(用于支持聚合),通常为 NULL;riid:表示客户端请求的接口的 IID。类工厂会创建对象并查询这个接口;pvObject:输出参数,指向创建的对象的接口指针)
LockServer:
作用:用于控制类工厂是否应该被锁定在内存中,以防止其被卸载。例如,当你预计会有短时间内的多次调用时,保持服务器在内存中可以避免反复加载和卸载的开销
签名:HRESULT LockServer(BOOL fLock);(fLock:如果为 TRUE,锁定类工厂;为 FALSE,解锁类工厂;返回值:通常返回 S_OK。)
对象实例创建的流程(客户端调用 CoCreateInstance 来创建一个 COM 对象实例时):
                1、注册表查找:CoCreateInstance 首先根据 rclsid(COM 对象的类 ID(CLSID)) 在注册表中查找该组件的相关信息,找到它的类工厂,包括 InprocServer32(DLL)或 LocalServer32(EXE)的位置
                2、加载组件:CoCreateInstance 会加载该组件并获取 DllGetClassObject 函数的地址,通过 DllGetClassObject 函数获取组件的类工厂对象(即 IClassFactory 的实例)
                3、调用 CreateInstance:调用类工厂对象的 CreateInstance 方法,传入 riid 和 ppv 等参数,创建实际的 COM 对象实例,并返回客户端请求的接口指针
                4、返回接口指针:CreateInstance 成功执行后,CoCreateInstance 将返回的接口指针传递给客户端,客户端可以通过这个接口指针与 COM 对象交互。

3、CLSID和IID

CLSID 用于标识 COM 组件,而 IID 用于标识接口。通过这些 ID,系统可以从注册表中查找到相应的组件并实例化它(每个 COM 组件都有一个唯一的类 ID(CLSID,可以通过VS工具栏中的创建工具生成),每个接口都有一个接口 ID(IID))

4、注册和注销

DllRegisterServer 和 DllUnregisterServer 函数用于将 COM 组件的 CLSID 和其他相关信息注册到 Windows 注册表中。这是 COM 组件能够被系统识别和使用的关键步骤。

5、DLL 入口点

一组特殊的函数,这些入口点函数在特定情况下会被操作系统调用,用于与动态链接库 (DLL) 进行交互。在 COM 组件的上下文中,入口点函数指的是那些导出的函数,它们是 COM 组件能够被操作系统或其他应用程序识别、加载、使用和卸载的关键点,通过这些入口点,操作系统能够执行 DLL 中的功能。
     分类:
          5.1、DllMain(可选的入口点):在 DLL 加载、卸载、线程创建或线程终止时被调用。通常用于执行一些初始化或清理操作。例如,当 DLL 被加载时,DllMain 可能会分配一些资源,而当 DLL 被卸载时,DllMain 会释放这些资源。
           5.2、DllCanUnloadNow:用于告诉操作系统是否可以卸载该 DLL。当 COM 组件的所有引用都被释放后,这个函数应该返回 S_OK,表示 DLL 可以被卸载。否则,返回 S_FALSE,表示 DLL 仍在使用中,不能卸载。作用是帮助管理 DLL 的生命周期,确保在不再使用时可以正确地卸载,释放系统资源。例如,当宿主进程(通常是调用了 COM 对象的应用程序)即将退出,或者当所有由 COM 组件创建的对象都被释放,并且没有其他对该组件的引用时,操作系统会调用 DllCanUnloadNow 来检查 DLL 是否可以卸载(已经加载到内存中的动态链接库 (DLL) 从内存中移除,并将占用的内存归还给系统的过程)。
           5.3、DllGetClassObject:作用是为指定的 CLSID(类 ID)提供类工厂对象。类工厂对象随后可以创建指定类的实例。例如,当应用程序需要创建 COM 对象时,它会调用 DllGetClassObject,传递所需的 CLSID,DLL 会返回一个类工厂对象(实现 IClassFactory 接口),然后通过这个类工厂对象来创建实际的 COM 对象实例
           5.4、DllRegisterServer 和 DllUnregisterServer:DllRegisterServer 是在注册 COM 组件时调用的入口点函数。它通常用于将 COM 组件的信息(如 CLSID、ProgID 等)写入 Windows 注册表,从而使得 COM 组件可以被操作系统和其他应用程序识别和使用。DllUnregisterServer 与之相反,用于注销 COM 组件,删除注册表中的相关信息   

DLL

DLL(Dynamic Link Library,动态链接库)是一种包含代码和数据的文件,可以在运行时由多个程序共享使用。在 Windows 操作系统中,DLL 文件通常具有 .dll 扩展名。DLL 是一种模块化的设计方式,用于将常用的函数、类、资源等封装起来,便于重用和共享。
特点

1、共享代码和资源

DLL 可以包含多个应用程序共享的代码和资源。例如,一个常用的数学函数库可以作为一个 DLL 被多个应用程序调用,而不需要每个应用程序都包含这段代码。

2、节省内存和减少重复

通过将公共功能打包到 DLL 中,多个应用程序可以共享同一段代码,从而节省内存和磁盘空间。这种方式减少了代码的重复,提高了系统的效率。

3、模块化开发

应用程序可以通过使用多个 DLL 进行模块化开发。各个功能模块可以独立开发和测试,然后通过 DLL 集成到一起。这样,更新某个模块时,只需要替换对应的 DLL,而不必重新编译整个应用程序。

4、动态链接

DLL 是在应用程序运行时动态加载的。这意味着一个应用程序不必在编译时包含 DLL 中的代码,而是在运行时加载需要的 DLL 文件。这种机制使得应用程序可以使用最新版本的 DLL 而无需重新编译。

5、延迟加载

Windows 支持 DLL 的延迟加载机制,即只有当 DLL 中的某个函数被调用时,DLL 才会被加载到内存中。这种机制有助于提高应用程序的启动速度。

DLL 与 COM 组件的关系

1、封装

COM 组件通常会被实现为一个 DLL 文件。这个 DLL 文件包含了 COM 接口的实现代码和相关数据。当一个应用程序想要使用这个 COM 组件时,它会通过 COM 接口调用 DLL 中的代码。

2、二者的交互

COM 组件是基于 DLL 技术的,但是与普通的 DLL 不同,COM 组件需要通过注册表进行注册(使用 regsvr32 等工具)。注册之后,COM 组件可以通过一个唯一的 CLSID(类标识符)或者 ProgID(程序标识符)在系统中被其他应用程序查找和实例化。

3、接口与实现分离

在 COM 中,接口定义与实现代码是分离的。接口定义是由客户端知道并使用的,而具体的实现是隐藏在 DLL 中的。这种分离使得 COM 组件可以被不同的编程语言和应用程序使用。

三、工具

    Visual Studio 
    操作:新建项目,选择动态链接库(DLL,一种包含可执行函数和数据的文件,多个程序可以同时使用这个文件中的代码和资源,而不需要在每个程序中都复制一份,也即可以被多个应用程序使用的共享库。动态链接的意思是,程序在运行时(而不是在编译时)将库文件链接到可执行程序的过程)项目
常用操作:工具-》创建GUID:可用于创建组件CLSID
                  工具-》Visual Studio命令提示符:可cd到dll,使用dumpbin /EXPORTS yourfile.dll查看导出函数
                 右键项目属性-》配置属性->常规-》修改输出目录和中间目录:比如输出目录:$(SolutionDir)$(ProjectName)\$(Configuration)\$(Platform)\ 中间目录:$(ProjectName)\$(Configuration)\$(Platform)\ ,配置合理的目录有助于生成的文件不产生清理异常(可用于所有配置)
                 右键项目属性-》配置属性-》链接器-》输入-》修改模块定义文件:比如C:\Users\86182\source\repos\test1\test1\Heqiao.def ,可在def中定义导出函数
                 菜单栏-》生成(B)-》配置管理器:可进行编译的具体配置,比如64位,32位的
                 菜单栏-》调试(D)-》附加到进程-》选择进程、代码类型为本机代码-》点击附加:可用于调试代码
                 菜单栏-》调试(D)-》选项(O)-》符号-》插入符号文件的位置:可用在调试时确定调试文件

四、代码实现

项目结构

1、引用

通常包含项目引用的其他库或程序集。但在这个截图中,"引用"是空的,表示当前项目没有添加外部引用

2、外部依赖项

项目所依赖的外部文件或库,这些文件通常包含操作系统提供的各种 API 和库的头文件,比如标准 C++ 库中的功能

3、核心头文件(.h 文件)

项目的头文件(.h 文件)。这些文件通常包含函数声明、宏定义(编程语言(尤其是C/C++)中一种预处理器指令,用来定义常量、表达式或简单的代码片段,在程序编译之前进行替换和扩展,通常通过 #define 预处理指令来实现。例如#define PI 3.14159 ,在编译时,所有出现 PI 的地方都会被替换为 3.14159。宏定义的几种常见形式:常量宏、表达式宏、带参数的宏、条件编译宏(可以控制代码的编译,例如只在某些条件下编译某些代码(防止文件被多次包含))、保护头文件的宏)、常量定义等。
    MyClassFactory.h:可能是一个类工厂的声明文件,用于创建 COM 对象的实例。
    MyShellExtension.h:可能是一个外壳扩展(Shell Extension)的声明文件,定义了外壳扩展类的接口和成员。
    pch.h:预编译头文件,包含了经常使用但不经常改变的头文件,以加快编译速度。
    resource.h:通常包含资源定义的头文件,例如菜单、字符串、对话框等的资源 ID。用来定义 Resource.rc 文件中用到的所有资源标识符(ID),允许你在代码中使用更具可读性的符号名来引用资源,而不是使用具体的数字,比如 #define IDI_APP_ICON 101

4、核心源文件(.cpp 文件)

    dllmain.cpp:这是 DLL 入口点的源文件,包含 DllMain 函数的实现,负责初始化和清理工作
    .def:模块定义文件,定义了导出函数列表和其他链接器相关的属性。
    MyShellExtension.cpp:可能包含 MyShellExtension 类的实现,负责外壳扩展的具体功能。
    pch.cpp:与预编译头文件 pch.h 相关联的源文件,通常仅包含 #include "pch.h" 这一行,用于生成预编译头。

5、资源文件

包含了项目中的资源文件,通常是图标、位图、对话框等    
     .ico:图标文件,可能用作 DLL 的图标资源。
     Resource.rc:资源脚本文件,用于定义和管理应用程序中使用的各种资源,如图标、对话框、菜单、字符串等,这些资源在 Resource.rc 文件中通常会被分配一个唯一的标识符(ID),这些 ID 用于在代码中引用相应的资源

代码详释

头文件

MyClassFactory.h:
 

// 头文件保护(防止头文件重复,如果没有定义,则定义MYCLASSFACTORY_H。有助于防止重复定义导致的编译错误)
#ifndef MYCLASSFACTORY_H
#define MYCLASSFACTORY_H
// 包含了windows API的头文件,包括有Win32(代表了 Windows 操作系统的 API 集,这些 API 同样适用于 64 位系统) 编程中使用的所有函数、类型和宏定义
#include <windows.h>
//  包含了COM接口定义(比如IUnknown)的头文件,它是COM编程的基础接口
#include <unknwn.h>

// 声明了一个CClassFactory集成了IClassFactory接口(IClassFactory是一个COM接口,用于创建对象实例的)
class CClassFactory : public IClassFactory
{
// 表明以下成员是公共的,可以在类的外部访问
public:
    // 构造函数,将自己定义的私有成员变量初始化为1
    CClassFactory() : m_cRef(1) {}
    // 析构函数,在对象的生命周期结束(/销毁)时自动调用,用于执行清理操作
    ~CClassFactory() {}

    // IUnknown接口实现
    // 用于查询是否支持指定接口
    STDMETHODIMP QueryInterface(REFIID riid, void** ppv);
    // 用于增加引用计数
    STDMETHODIMP_(ULONG) AddRef();
    // 用于减少引用计数,并在计数为 0 时销毁对象
    STDMETHODIMP_(ULONG) Release();

    // IClassFactory接口实现
    // 用于创建COM对象的实例
    STDMETHODIMP CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppv);
    // 控制服务器(DLL、EXE)是否要被卸载
    STDMETHODIMP LockServer(BOOL fLock);

// 表明以下成员是私有的,不能在类的外部访问
private:
    // 声明了一个私有成员变量 m_cRef,用于存储对象的引用计数
    LONG m_cRef;
};
// 与开头的#ifndef对应,表示条件编译的结束
#endif 

MyShellExtension.h:

// 头文件保护(防止头文件重复,如果没有定义,则定义MYSHELLEXTENSION_H。有助于防止重复定义导致的编译错误)
#ifndef MYSHELLEXTENSION_H
#define MYSHELLEXTENSION_H

// 包含了windows API的头文件,包括有Win32(代表了 Windows 操作系统的 API 集,这些 API 同样适用于 64 位系统) 编程中使用的所有函数、类型和宏定义
#include <windows.h>
// 包含 Shell API 的头文件,提供与 Windows Shell 相关的接口、结构和常量,用于实现诸如上下文菜单扩展等功能
#include <shlobj.h>
// 包含 C++ 标准库的字符串类,用于处理字符串操作
#include <string>

// 定义一个 CShellExt 类,并且继承了 IContextMenu 和 IShellExtInit 两个接口。IContextMenu 用于定义上下文菜单扩展的接口,IShellExtInit 用于初始化 Shell 扩展
class CShellExt : public IContextMenu, public IShellExtInit
{
// 定义以下成员为公共成员,可以在类的外部访问
public:
    // CShellExt 类的构造函数,在对象创建时初始化成员变量或进行其他初始化工作
    CShellExt();
    // CShellExt 类的析构函数,在对象销毁时释放资源
    ~CShellExt();

    // IUnknown接口实现
    // 用于查询是否支持指定接口
    STDMETHODIMP QueryInterface(REFIID riid, void** ppv);
    // 用于增加引用计数
    STDMETHODIMP_(ULONG) AddRef();
    // 用于减少引用计数,并在计数为 0 时销毁对象
    STDMETHODIMP_(ULONG) Release();

    // IShellExtInit接口实现
    // 用于在 Shell 扩展被初始化时传递必要的参数,如选中的文件或文件夹的信息
    STDMETHODIMP Initialize(LPCITEMIDLIST, LPDATAOBJECT, HKEY);

    // IContextMenu接口实现
    // 用于在右键菜单中插入自定义命令项
    STDMETHODIMP QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT, UINT uFlags);
    // 当用户选择了菜单中的命令时调用此方法执行相应的操作
    STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO pCmdInfo);
    // 用于提供菜单项的帮助字符串或其他信息
    STDMETHODIMP GetCommandString(UINT_PTR, UINT, UINT*, LPSTR, UINT);
    // 自定义的辅助方法,用于从数据库中获取目标路径
    std::wstring GetTargetPathFromDatabase();

// 定义以下成员为私有成员,只有类的内部可以访问这些成员
private:
    // 引用计数,用于管理对象的生命周期
    LONG m_cRef;
    // 指向 ITEMIDLIST 结构的指针,这个指针通常用于表示当前 Shell 扩展操作的文件夹。使用它可以获取文件夹的路径或其他信息
    LPITEMIDLIST m_pidlFolder;
    // 指向 IDataObject 接口的指针,在 Shell 扩展中,IDataObject 用于处理选中的文件或文件夹的数据
    LPDATAOBJECT m_pDataObj;
    // 用于判断给定路径是否指向一个特殊文件夹
    bool IsSpecialFolder(const std::wstring& folderPath);
    // 用于从 m_pidlFolder 或 m_pDataObj 获取当前操作的文件或文件夹的路径
    std::wstring GetFolderPath();
    // 用于释放 GDI 资源.使用HBITMAP 来存储位图资源,这些资源需要在不再使用时负责释放这些位图资源,以避免内存泄漏
    void ReleaseResources();
    // 用于存储位图资源的句柄,用于存储在菜单项中显示的图标位图
    HBITMAP hBitmap1;
    HBITMAP hBitmap2;
    HBITMAP hBitmap3;
    // 用于保存选中的项的数量
    UINT m_nSelectedItems; 
};

// DLL导出函数
// 用于判断 DLL 是否可以从内存中卸载。通常是检查引用计数是否为零
STDAPI DllCanUnloadNow();
// 用于返回类工厂对象的指针
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv);
// 用于注册 COM 服务器。在注册表中添加所需的注册表项
STDAPI DllRegisterServer();
// 用于取消注册 COM 服务器。从注册表中删除相关的注册表项
STDAPI DllUnregisterServer();

// 注册Shell扩展的辅助函数
HRESULT RegisterShellExtContextMenuHandler(PCWSTR pszFileType, const CLSID& clsid, PCWSTR pszFriendlyName);
// 取消注册 Shell 扩展上下文菜单处理程序的辅助函数
HRESULT UnregisterShellExtContextMenuHandler(PCWSTR pszFileType, const CLSID& clsid);

// 与开头的 #ifndef 配对,表示条件编译的结束
#endif 

resource.h:

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ 生成的包含文件。
// 供 Resource.rc 使用

// 定义了一个宏 IDI_ICON1,它的值是 106。通常,这个宏用于资源文件(如图标、光标、对话框等)中,用来标识一个资源。
#define IDI_ICON1                       106

// 检查是否定义了 APSTUDIO_INVOKED,这个宏通常由 Visual Studio 的资源编辑器定义,用来确保某些代码块只有在资源编辑器环境下才会被处理
#ifdef APSTUDIO_INVOKED
// 如果没有定义 APSTUDIO_READONLY_SYMBOLS,则继续处理下面的代码._APS_NEXT_* 系列定义的是新资源、命令、控件等的默认 ID。这个文件主要用于帮助 Visual Studio 管理和分配资源 ID
#ifndef APSTUDIO_READONLY_SYMBOLS
// 定义下一个资源的默认值为 107。当你添加新资源时,资源编辑器会自动使用这个值作为新资源的 ID
#define _APS_NEXT_RESOURCE_VALUE        107
// 定义下一个命令的默认值为 40001。命令通常对应于菜单项、按钮或其他可触发的动作
#define _APS_NEXT_COMMAND_VALUE         40001
// 定义下一个控件的默认值为 1001。控件通常指窗口中的 UI 元素(如按钮、文本框等)
#define _APS_NEXT_CONTROL_VALUE         1001
// 定义下一个符号值的默认值为 101。这通常用于生成符号(如对话框、菜单项等)的标识符
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif

源文件

dllmain.cpp:

// 包含预编译头文件 pch.h。预编译头文件通常用于提高编译速度,包含了常用但不经常更改的头文件
#include "pch.h"
// 包含 MyShellExtension.h 头文件,它是我定义的 Shell 扩展类的头文件
#include "MyShellExtension.h"
// 全局变量,保存DLL模块句柄
HMODULE g_hModule = NULL;

// 这是 DLL 的入口点函数,每当 DLL 被加载或卸载,或当一个线程附加或分离时,系统都会调用此函数.hModule:DLL 模块的句柄;ul_reason_for_call:指示调用 DllMain 的原因;lpReserved:加载方式
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
    // 根据调用 DllMain 的原因执行不同的操作。
    switch (ul_reason_for_call) {
    // 当进程首次加载 DLL 时触发。
    case DLL_PROCESS_ATTACH:
        // 将 DLL 的模块句柄 hModule 保存到全局变量 g_hModule 中
        g_hModule = hModule; 
        break;
    // 当新线程创建并附加到 DLL 时触发。
    case DLL_THREAD_ATTACH:
    // 当线程从 DLL 分离时触发
    case DLL_THREAD_DETACH:
    // 当进程卸载 DLL 时触发
    case DLL_PROCESS_DETACH:
        break;
    }
    // 返回 TRUE,表示 DllMain 函数成功执行。如果返回 FALSE,则表示 DLL 初始化失败,系统会终止加载 DLL
    return TRUE;
}

MyShellExtension.cpp:

// 包含预编译头文件,通常用于加快编译速度
#include "pch.h"
// 包含我定义的 MyShellExtension 类的头文件
#include "MyShellExtension.h"
// 包含 MyClassFactory 类的头文件,用于创建 COM 对象的工厂类
#include "MyClassFactory.h"
// 包含安全字符串操作的头文件,提供了一些安全的字符串操作函数
#include <strsafe.h>
// 包含资源文件头文件,通常用于定义资源 ID,如图标、菜单项等
#include "Resource.h"
// 包含 Shell API 相关的头文件,提供与 Windows Shell 交互的接口和功能
#include <shlobj.h>
// 包含 C++ 标准库中的字符串类 std::string 和 std::wstring。
#include <string>
//#include <sqlite3.h>
//包含 C++ 标准库中的算法函数,如 std::replace
#include <algorithm> 
// 包含 Shell 轻量级实用函数的头文件,提供一些字符串操作和路径处理函数
#include <shlwapi.h> 

// 定义一个静态的类标识符(CLSID),它唯一标识这个 COM 对象。这个 CLSID 是在注册 COM 组件时使用的
const CLSID CLSID_ShellExt = { 0x89b5eb36, 0x4363, 0x44d4, { 0x8a, 0x61, 0x24, 0xea, 0xee, 0xdd, 0x3a, 0xd4 } };

// 声明一个外部的全局模块句柄变量 g_hModule,通常用于获取当前模块的句柄  
extern HMODULE g_hModule;

// 构造函数,初始化引用计数为1,并将 m_pidlFolder 和 m_pDataObj 指针设置为 nullptr(表示指针的空值)
CShellExt::CShellExt() : m_cRef(1), m_pidlFolder(nullptr), m_pDataObj(nullptr)
{
}

// 析构函数,释放对象资源,包括调用 ReleaseResources() 释放 GDI 资源,并释放 PIDL 和数据对象的内存。
CShellExt::~CShellExt()
{
    ReleaseResources();
    if (m_pidlFolder) {
        // 用于释放 m_pidlFolder 指向的内存
        CoTaskMemFree(m_pidlFolder);
        m_pidlFolder = nullptr;
    }
    if (m_pDataObj) {
        // 减少由 m_pDataObj 指向的 COM 对象的引用计数,并在引用计数为零时释放该对象的资源
        m_pDataObj->Release();
        m_pDataObj = nullptr;
    }
}

// 用于释放 GDI 资源(指由 GDI 进行管理的各种图形资源,比如位图,它是一个由像素数组组成的图像,来存储和操作图像数据。其他还有画笔,字体,图标等),以防止资源泄漏
void CShellExt::ReleaseResources() {
    if (hBitmap1) {
        DeleteObject(hBitmap1);
        hBitmap1 = nullptr;
    }
    if (hBitmap2) {
        DeleteObject(hBitmap2);
        hBitmap2 = nullptr;
    }
    if (hBitmap3) {
        DeleteObject(hBitmap3);
        hBitmap3 = nullptr;
    }
}

// 用于查询对象是否支持特定的接口。根据传入的接口 ID(IID),返回相应的接口指针
STDMETHODIMP CShellExt::QueryInterface(REFIID riid, void** ppv)
{
    HRESULT hr;

    if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IContextMenu)) {
        *ppv = static_cast<IContextMenu*>(this);
        hr = S_OK;
    }
    else if (IsEqualIID(riid, IID_IShellExtInit)) {
        *ppv = static_cast<IShellExtInit*>(this);
        hr = S_OK;
    }
    else {
        *ppv = NULL;
        hr = E_NOINTERFACE;
    }

    if (SUCCEEDED(hr)) {
        AddRef();
    }

    return hr;
}

// 增加引用计数,表示有新的引用指向该对象
STDMETHODIMP_(ULONG) CShellExt::AddRef()
{
    return InterlockedIncrement(&m_cRef);
}

// 减少引用计数,当引用计数降为零时,删除对象释放内存
STDMETHODIMP_(ULONG) CShellExt::Release()
{
    ULONG cRef = InterlockedDecrement(&m_cRef);
    if (0 == cRef) {
        delete this;
    }
    return cRef;
}

// IShellExtInit 接口方法的实现
// 初始化 Shell 扩展的实例,存储当前文件夹的 PIDL 和数据对象。如果有多个文件或文件夹被选中,获取并存储它们的数量。
STDMETHODIMP CShellExt::Initialize(LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDataObj, HKEY hKeyProgID)
{    
    // 释放之前的PIDL(如果有)
    if (m_pidlFolder) {
        CoTaskMemFree(m_pidlFolder);
        m_pidlFolder = nullptr;
    }

    // 释放之前的 IDataObject
    if (m_pDataObj) {
        m_pDataObj->Release();
        m_pDataObj = nullptr;
    }

    // 复制传入的PIDL
    if (pidlFolder) {
        m_pidlFolder = ILClone(pidlFolder);
    }

    // 初始化选中项的数量
    m_nSelectedItems = 0;

    // 保存数据对象
    if (pDataObj) {
        m_pDataObj = pDataObj;
        m_pDataObj->AddRef();

        // 获取选中的文件/文件夹数量
        FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
        STGMEDIUM stg = { TYMED_HGLOBAL };

        if (SUCCEEDED(pDataObj->GetData(&fmt, &stg))) {
            HDROP hDrop = static_cast<HDROP>(GlobalLock(stg.hGlobal));
            if (hDrop != NULL) {
                // 获取选中的文件/文件夹数量
                m_nSelectedItems = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
                GlobalUnlock(stg.hGlobal);
            }
            ReleaseStgMedium(&stg);
        }
    }

    return S_OK;
}


// CClassFactory 类的实现
// IUnknown方法实现
STDMETHODIMP CClassFactory::QueryInterface(REFIID riid, void** ppv)
{
    if (ppv == NULL) return E_POINTER;

    if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IClassFactory)) {
        *ppv = static_cast<IClassFactory*>(this);
        AddRef();  // 注意在返回指针前增加引用计数
        return S_OK;
    }
    else {
        *ppv = NULL;
        return E_NOINTERFACE;
    }
}

STDMETHODIMP_(ULONG) CClassFactory::AddRef()
{
    return InterlockedIncrement(&m_cRef);
}

STDMETHODIMP_(ULONG) CClassFactory::Release()
{
    ULONG cRef = InterlockedDecrement(&m_cRef);
    if (cRef == 0) {
        delete this;
    }
    return cRef;
}
// 创建 CShellExt 实例,并查询它是否支持请求的接口
STDMETHODIMP CClassFactory::CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppv)
{
    if (pUnkOuter != NULL) {
        return CLASS_E_NOAGGREGATION;
    }

    CShellExt* pShellExt = new CShellExt();
    if (!pShellExt) {
        return E_OUTOFMEMORY;
    }

    HRESULT hr = pShellExt->QueryInterface(riid, ppv);
    pShellExt->Release(); // 释放初始引用

    return hr;
}

STDMETHODIMP CClassFactory::LockServer(BOOL fLock)
{
    if (fLock) {
        InterlockedIncrement(&m_cRef);
    }
    else {
        InterlockedDecrement(&m_cRef);
    }
    return S_OK;
}

// 辅助函数,用于将图标 (HICON) 转换为位图 (HBITMAP) 对象
HBITMAP BitmapFromIcon(HICON hIcon)
{
    HDC hDC = CreateCompatibleDC(NULL);
    if (!hDC)
        return NULL;

    BITMAPINFO bmi;
    ZeroMemory(&bmi, sizeof(bmi));
    bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmi.bmiHeader.biWidth = GetSystemMetrics(SM_CXSMICON);
    bmi.bmiHeader.biHeight = -GetSystemMetrics(SM_CYSMICON); // top-down
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biBitCount = 32; // 32-bit to support alpha channel
    bmi.bmiHeader.biCompression = BI_RGB;

    void* pvBits;
    HBITMAP hBmp = CreateDIBSection(hDC, &bmi, DIB_RGB_COLORS, &pvBits, NULL, 0);
    if (!hBmp)
    {
        DeleteDC(hDC);
        return NULL;
    }

    HBITMAP hOldBmp = (HBITMAP)SelectObject(hDC, hBmp);
    if (hOldBmp)
    {
        // 将图标绘制到位图上
        DrawIconEx(hDC, 0, 0, hIcon, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0, NULL, DI_NORMAL);
        SelectObject(hDC, hOldBmp);
    }

    DeleteDC(hDC);

    return hBmp;
}

// QueryContextMenu 方法的实现.在右键菜单中添加自定义菜单项,每个菜单项都有相应的图标和文本。如果没有添加菜单项(例如选择了多个文件或在桌面上右键点击),则返回 0
STDMETHODIMP CShellExt::QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT, UINT uFlags)
{
    // 仅在右键菜单不是默认选项时执行
    if (!(uFlags & CMF_DEFAULTONLY)) {
        // 如果选择了多个文件或文件夹,则不添加自定义菜单项
        if (m_nSelectedItems > 1) {
            return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(0));
        }

        // 获取当前文件夹路径
        std::wstring folderPath = GetFolderPath();
        // 获取桌面路径
        wchar_t desktopPath[MAX_PATH];
        SHGetSpecialFolderPath(NULL, desktopPath, CSIDL_DESKTOPDIRECTORY, FALSE);
        // 如果路径为空或与桌面路径相同,则表示在桌面上下文中
        if (folderPath.empty() || folderPath == desktopPath) {
            return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(0));
        }


        // 从SQLite数据库中获取targetPath
 //std::wstring targetPath = GetTargetPathFromDatabase();

 // 判断 folderPath 是否在 targetPath 以内或以下,也即如果 folderPath 以 targetPath 开头
 //if (folderPath.find(targetPath) == 0) {
      隐藏指定菜单
     //UINT menuCount = GetMenuItemCount(hMenu);
     //for (UINT i = 0; i < menuCount+5; i++) {
     //    MENUITEMINFO mii = { sizeof(MENUITEMINFO) };
     //    mii.fMask = MIIM_ID | MIIM_STRING;
     //    wchar_t menuText[512] = { 0 };
     //    mii.dwTypeData = menuText;
     //    mii.cch = ARRAYSIZE(menuText);
     //    if (GetMenuItemInfo(hMenu, i, TRUE, &mii)) {
     //        // 打印菜单项的ID、类型、状态、菜单文本等信息
     //        //MessageBox(NULL, menuText, L"Menu Item Properties", MB_OK);
     //        //                 wchar_t debugInfo[1024];
     //        //if (wcscmp(menuText, L"Get_Updates") == 0 || wcscmp(menuText, L"提交更新") == 0 || wcscmp(menuText, L"获取更新") == 0) {
     //            RemoveMenu(hMenu, i, MF_BYPOSITION);
     //            // 移除后需要调整索引,避免跳过项
     //            i--;
     //            menuCount--;
     //        //}
     //    }
     //}

        // 配置第一个菜单项
        HICON hIcon1 = LoadIcon(g_hModule, MAKEINTRESOURCE(IDI_ICON1));
        hBitmap1 = BitmapFromIcon(hIcon1);
        WCHAR szText1[] = L"获取更新";
        MENUITEMINFO mii1 = { sizeof(MENUITEMINFO) };
        mii1.fMask = MIIM_BITMAP | MIIM_STRING | MIIM_ID;
        mii1.wID = idCmdFirst;
        mii1.dwTypeData = szText1;
        mii1.hbmpItem = hBitmap1;
        InsertMenuItem(hMenu, indexMenu, TRUE, &mii1);

        // 释放图标资源
        DestroyIcon(hIcon1);

        // 配置第二个菜单项
        HICON hIcon2 = LoadIcon(g_hModule, MAKEINTRESOURCE(IDI_ICON1));
        hBitmap2 = BitmapFromIcon(hIcon2);
        WCHAR szText2[] = L"提交更新";
        MENUITEMINFO mii2 = { sizeof(MENUITEMINFO) };
        mii2.fMask = MIIM_BITMAP | MIIM_STRING | MIIM_ID;
        mii2.wID = idCmdFirst + 1;
        mii2.dwTypeData = szText2;
        mii2.hbmpItem = hBitmap2;
        InsertMenuItem(hMenu, indexMenu + 1, TRUE, &mii2);

        // 释放图标资源
        DestroyIcon(hIcon2);

        // 配置第三个菜单项
        HICON hIcon3 = LoadIcon(g_hModule, MAKEINTRESOURCE(IDI_ICON1));
        hBitmap3 = BitmapFromIcon(hIcon3);
        WCHAR szText3[] = L"重新同步";
        MENUITEMINFO mii3 = { sizeof(MENUITEMINFO) };
        mii3.fMask = MIIM_BITMAP | MIIM_STRING | MIIM_ID;
        mii3.wID = idCmdFirst + 2;
        mii3.dwTypeData = szText3;
        mii3.hbmpItem = hBitmap3;
        InsertMenuItem(hMenu, indexMenu + 2, TRUE, &mii3);

        // 释放图标资源
        DestroyIcon(hIcon3);
        return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(3));
    //}
    }
    return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(0)); // 如果没有添加菜单项,则返回0
}



//std::wstring CShellExt::GetTargetPathFromDatabase()
//{
//    std::wstring targetPath;
//    sqlite3* db = nullptr;
//    sqlite3_stmt* stmt = nullptr;
//
//    // 数据库文件路径
//    const char* dbPath = "C:\\ProgramData\\heqiaosoft\\HQUpload\\HQ_NET_DISK.db";
//
//    // SQL 查询语句
//    const char* sqlQuery = "SELECT value FROM tb_net_disk_web_settings WHERE type='rootPath'";
//
//    if (sqlite3_open(dbPath, &db) == SQLITE_OK) {
//        if (sqlite3_prepare_v2(db, sqlQuery, -1, &stmt, nullptr) == SQLITE_OK) {
//            if (sqlite3_step(stmt) == SQLITE_ROW) {
//                const unsigned char* text = sqlite3_column_text(stmt, 0);
//                if (text) {
//                    // 将 UTF-8 转换为 std::wstring
//                    int bufferSize = MultiByteToWideChar(CP_UTF8, 0, reinterpret_cast<const char*>(text), -1, NULL, 0);
//                    if (bufferSize > 0) {
//                        wchar_t* wText = new wchar_t[bufferSize];
//                        MultiByteToWideChar(CP_UTF8, 0, reinterpret_cast<const char*>(text), -1, wText, bufferSize);
//                        targetPath = std::wstring(wText);
//                        delete[] wText;
//
//                        // 将反斜杠 ''/' 转换为正斜杠 '\'
//                        std::replace(targetPath.begin(), targetPath.end(), L'/', L'\\');
//                    }
//                }
//            }
//            sqlite3_finalize(stmt);
//        }
//        sqlite3_close(db);
//    }
//
//    return targetPath;
//}


// InvokeCommand 方法的实现
STDMETHODIMP CShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO pCmdInfo) {
     // 确保 lpVerb 是一个数值类型(即你的菜单项ID)
    if (HIWORD(pCmdInfo->lpVerb) != 0) {
        return E_FAIL;
    }

    // 获取菜单项ID的相对偏移量
    UINT idCmd = LOWORD(pCmdInfo->lpVerb);

    // 根据相对偏移量处理命令
    std::wstring params;
    // 检查 idCmd 是否在你定义的菜单项范围内
    if (idCmd >= 0 && idCmd <=  2) {
        // 获取当前操作对象的路径
        std::wstring folderPath = GetFolderPath();
        //MessageBox(NULL, folderPath.c_str(), L"Menu Item Properties", MB_OK);

        switch (idCmd) {
            case 0:
                params = L"\"" + folderPath + L"\" \"type=update&data=1&from=folder\"";
                break;
            case 1:
                params = L"\"" + folderPath + L"\" \"type=commit&data=2&from=folder\"";
                break;
            case 2:
                params = L"\"" + folderPath + L"\" \"type=revert&data=3&from=folder\"";
                break;
        }

        // 从注册表中读取 InprocServer32 的路径
        WCHAR szModule[MAX_PATH] = { 0 };
        HKEY hKey;
        WCHAR szCLSID[50];
        StringFromGUID2(CLSID_ShellExt, szCLSID, ARRAYSIZE(szCLSID));
        WCHAR szSubkey[MAX_PATH];
        StringCchPrintfW(szSubkey, ARRAYSIZE(szSubkey), L"Software\\Classes\\CLSID\\%s\\InprocServer32", szCLSID);
        // 读取HKEY_LOCAL_MACHINE下的注册表值、HKEY_CURRENT_USER 
        if (RegOpenKeyExW(HKEY_CURRENT_USER, szSubkey, 0, KEY_READ, &hKey) == ERROR_SUCCESS) {
            DWORD dwSize = sizeof(szModule);
            RegQueryValueExW(hKey, NULL, NULL, NULL, (LPBYTE)szModule, &dwSize);
            RegCloseKey(hKey);
        }
        else {
            MessageBox(NULL, L"Failed to open registry key", L"Error", MB_OK);
            return E_FAIL;
        }

        // 如果无法从注册表读取路径,返回失败
        if (wcslen(szModule) == 0) {
            MessageBox(NULL, L"Cannot read InprocServer32 path", L"Error", MB_OK);
            return E_FAIL;
        }

        // 查找倒数第二个 '\' 的位置
        WCHAR* pLastBackslash = wcsrchr(szModule, L'\\');  // 找到最后一个 '\'
        if (pLastBackslash) {
            *pLastBackslash = L'\0';  // 移除最后一个部分
            pLastBackslash = wcsrchr(szModule, L'\\');  // 再次查找倒数第二个 '\'
            if (pLastBackslash) {
                *pLastBackslash = L'\0';  // 移除倒数第二个部分
            }
        }

        // 拼接 "netdisk.exe"
        wcscat_s(szModule, ARRAYSIZE(szModule), L"\\netdisk.exe");

        // 执行相应的命令
        ShellExecute(NULL, L"open", szModule,
            params.c_str(),
            NULL, SW_SHOWNORMAL);
        return S_OK;
    }

    // 如果 idCmd 不在你定义的范围内,返回 E_FAIL,表示不处理
    return E_FAIL;
}

// 方法通常用于提供菜单项的帮助字符串或工具提示文本
STDMETHODIMP CShellExt::GetCommandString(UINT_PTR, UINT, UINT*, LPSTR, UINT)
{
    return E_NOTIMPL;
}

 用于判断一个文件夹路径是否是指定的“特殊文件夹”路径
//bool CShellExt::IsSpecialFolder(const std::wstring& folderPath)
//{
//    return folderPath == L"C:\\MySpecialFolder";
//}

// 用于获取当前选中文件夹的路径或文件的路径
std::wstring CShellExt::GetFolderPath()
{
    // 初始化路径缓冲区
    wchar_t szFolderPath[MAX_PATH] = { 0 };

    // 检查是否初始化过
    if (m_pidlFolder)
    {
        // 将PIDL转换为文件系统路径
        if (SHGetPathFromIDList(m_pidlFolder, szFolderPath))
        {
            return std::wstring(szFolderPath);  // 选择文件夹空白处,返回文件夹路径
        }
    }

    // 如果没有直接从PIDL获取路径,尝试从pDataObj中获取
    if (m_pDataObj)
    {
        FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
        STGMEDIUM stg = { TYMED_HGLOBAL };

        if (SUCCEEDED(m_pDataObj->GetData(&fmt, &stg)))
        {
            HDROP hDrop = static_cast<HDROP>(GlobalLock(stg.hGlobal));
            if (hDrop != NULL)
            {
                // 获取选中的第一个文件/文件夹路径
                if (DragQueryFile(hDrop, 0, szFolderPath, ARRAYSIZE(szFolderPath)))
                {
                    std::wstring fullPath(szFolderPath);

                    // 获取文件或文件夹的属性
                    DWORD attributes = GetFileAttributes(szFolderPath);
                    GlobalUnlock(stg.hGlobal);
                    ReleaseStgMedium(&stg);

                    if (attributes != INVALID_FILE_ATTRIBUTES)
                    {
                        if (attributes & FILE_ATTRIBUTE_DIRECTORY)
                        {
                            // 如果选中的是文件夹,直接返回该文件夹的路径
                            return fullPath;
                        }
                        else
                        {
                            // 如果选中的是文件,返回文件的完整路径(包含文件名)
                            return fullPath;
                        }
                    }
                }
                GlobalUnlock(stg.hGlobal);
            }
            ReleaseStgMedium(&stg);
        }
    }

    // 如果未能获取路径,则返回空字符串
    return L"";
}



// 负责检查是否可以卸载DLL
STDAPI DllCanUnloadNow()
{
    return S_OK;
}

// 返回类工厂,用于创建CShellExt实例
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
    if (IsEqualCLSID(rclsid, CLSID_ShellExt)) {
        CClassFactory* pClassFactory = new CClassFactory();
        if (!pClassFactory) {
            return E_OUTOFMEMORY;
        }

        HRESULT hr = pClassFactory->QueryInterface(riid, ppv);
        pClassFactory->Release();  // 确保只在最后一次使用后调用 Release

        return hr;
    }
    return CLASS_E_CLASSNOTAVAILABLE;
}


// 将组件的CLSID和Shell扩展注册到系统
STDAPI DllRegisterServer()
{
    HRESULT hr = S_OK;

    // 注册到文件夹背景上下文菜单
    hr = RegisterShellExtContextMenuHandler(L"Directory\\Background", CLSID_ShellExt, L"MyShellExtension");
    if (FAILED(hr)) {
        return hr;
    }

    // 注册到文件夹上下文菜单
    hr = RegisterShellExtContextMenuHandler(L"Directory", CLSID_ShellExt, L"MyShellExtension");
    if (FAILED(hr)) {
        return hr;
    }

    // 注册到文件上下文菜单
    hr = RegisterShellExtContextMenuHandler(L"*", CLSID_ShellExt, L"MyShellExtension");
    if (FAILED(hr)) {
        return hr;
    }

    // 注册COM组件的CLSID
    WCHAR szCLSID[50];
    StringFromGUID2(CLSID_ShellExt, szCLSID, ARRAYSIZE(szCLSID));

    // CLSID 的注册表项路径
    WCHAR szSubkey[MAX_PATH];
    hr = StringCchPrintfW(szSubkey, ARRAYSIZE(szSubkey), L"Software\\Classes\\CLSID\\%s", szCLSID);
    if (SUCCEEDED(hr)) {
        HKEY hKey;
        LONG lResult = RegCreateKeyExW(HKEY_CURRENT_USER, szSubkey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &hKey, NULL);
        if (lResult == ERROR_SUCCESS) {
            // 设置COM对象的友好名称
            const WCHAR szFriendlyName[] = L"MyShellExtension COM Object";
            lResult = RegSetValueExW(hKey, NULL, 0, REG_SZ, (const BYTE*)szFriendlyName, sizeof(szFriendlyName));

            // 创建InprocServer32子项
            HKEY hSubKey;
            lResult = RegCreateKeyExW(hKey, L"InprocServer32", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &hSubKey, NULL);
            if (lResult == ERROR_SUCCESS) {
                // 设置InprocServer32的默认值为DLL的路径
                WCHAR szModule[MAX_PATH];
                GetModuleFileNameW(g_hModule, szModule, ARRAYSIZE(szModule));
                lResult = RegSetValueExW(hSubKey, NULL, 0, REG_SZ, (const BYTE*)szModule, (DWORD)((wcslen(szModule) + 1) * sizeof(WCHAR)));

                // 设置ThreadingModel为Apartment
                const WCHAR szModel[] = L"Apartment";
                lResult = RegSetValueExW(hSubKey, L"ThreadingModel", 0, REG_SZ, (const BYTE*)szModel, sizeof(szModel));

                RegCloseKey(hSubKey);
            }

            RegCloseKey(hKey);

            if (lResult != ERROR_SUCCESS) {
                hr = HRESULT_FROM_WIN32(lResult);
            }
        }
        else {
            hr = HRESULT_FROM_WIN32(lResult);
        }
    }

    return hr;
}

// 辅助函数,用于在注册表中为指定文件类型注册 Shell 扩展的上下文菜单处理程序
HRESULT RegisterShellExtContextMenuHandler(PCWSTR pszFileType, const CLSID& clsid, PCWSTR pszFriendlyName)
{
    if (pszFileType == NULL || pszFriendlyName == NULL) {
        return E_INVALIDARG;
    }

    WCHAR szCLSID[50];
    StringFromGUID2(clsid, szCLSID, ARRAYSIZE(szCLSID));

    WCHAR szSubkey[MAX_PATH];
    HRESULT hr = StringCchPrintfW(szSubkey, ARRAYSIZE(szSubkey),
        L"Software\\Classes\\%s\\ShellEx\\ContextMenuHandlers\\%s", pszFileType, pszFriendlyName);
    if (SUCCEEDED(hr)) {
        HKEY hKey;
        LONG lResult = RegCreateKeyExW(HKEY_CURRENT_USER, szSubkey, 0, NULL,
            REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &hKey, NULL);
        if (lResult == ERROR_SUCCESS) {
            lResult = RegSetValueExW(hKey, NULL, 0, REG_SZ,
                (LPBYTE)szCLSID, sizeof(szCLSID));
            RegCloseKey(hKey);
        }
        if (lResult != ERROR_SUCCESS) {
            hr = HRESULT_FROM_WIN32(lResult);
        }
    }
    return hr;
}

// 负责撤销DLL的注册
STDAPI DllUnregisterServer()
{
    HRESULT hr = S_OK;

    // 注销文件夹背景上下文菜单
    hr = UnregisterShellExtContextMenuHandler(L"Directory\\Background", CLSID_ShellExt);
    if (FAILED(hr)) {
        return hr;
    }

    // 注销文件夹上下文菜单
    hr = UnregisterShellExtContextMenuHandler(L"Directory", CLSID_ShellExt);
    if (FAILED(hr)) {
        return hr;
    }

    // 注销文件上下文菜单
    hr = UnregisterShellExtContextMenuHandler(L"*", CLSID_ShellExt);
    if (FAILED(hr)) {
        return hr;
    }

    // 删除CLSID的注册表项
    WCHAR szCLSID[50];
    StringFromGUID2(CLSID_ShellExt, szCLSID, ARRAYSIZE(szCLSID));

    // CLSID 的注册表项路径
    WCHAR szSubkey[MAX_PATH];
    hr = StringCchPrintfW(szSubkey, ARRAYSIZE(szSubkey), L"Software\\Classes\\CLSID\\%s", szCLSID);
    if (SUCCEEDED(hr)) {
        LONG lResult = RegDeleteTreeW(HKEY_CURRENT_USER, szSubkey);
        if (lResult != ERROR_SUCCESS) {
            hr = HRESULT_FROM_WIN32(lResult);
        }
    }

    return hr;
}

// 辅助函数:注销Shell扩展的上下文菜单处理程序
HRESULT UnregisterShellExtContextMenuHandler(PCWSTR pszFileType, const CLSID& clsid)
{
    if (pszFileType == NULL) {
        return E_INVALIDARG;
    }

    WCHAR szSubkey[MAX_PATH];
    HRESULT hr = StringCchPrintfW(szSubkey, ARRAYSIZE(szSubkey),
        L"Software\\Classes\\%s\\ShellEx\\ContextMenuHandlers\\MyShellExtension", pszFileType);
    if (SUCCEEDED(hr)) {
        LONG lResult = RegDeleteTreeW(HKEY_CURRENT_USER, szSubkey);
        if (lResult != ERROR_SUCCESS) {
            hr = HRESULT_FROM_WIN32(lResult);
        }
    }
    return hr;
}


.def:

LIBRARY "customMenu"
EXPORTS
    DllCanUnloadNow      PRIVATE
    DllGetClassObject    PRIVATE
    DllRegisterServer    PRIVATE
    DllUnregisterServer  PRIVATE

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2085444.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

文件上传面板中限制需要的文件格式,并且隐藏“所有文件”选项

直接说需求&#xff1a;需要实现在文件上传面板中限制需要的文件格式&#xff0c;并且不想展示“所有文件”这个选项&#xff0c;应该怎么做嘞&#xff1f;效果如下图&#xff1a; 这里用到了 window.showOpenFilePicker 方法实现&#xff0c;首先定义接受的格式及限制&#xf…

Python 生成随机的国内 ip

示例代码&#xff1a; import randomdef generate_random_cn_ip():# 中国大陆IP范围start_ip "36.54.0.0"end_ip "123.255.255.254"# 将IP地址转换为整数start_ip_num int(start_ip.replace(".", ""))end_ip_num int(end_ip.rep…

【FreeRTOS】信号量实验-控制车辆运行

目录 0 前言1 控制车辆运行2 不使用信号量3 使用计数型信号量3.1 运行两辆车运行3.2 运行三辆车运行 4 使用二进制信号量5 补充信号量知识5.1 两种信号量对比5.2 信号量函数5.3 创建5.4 删除5.5 Take / Give5.5.1 xSemaphoreGive5.5.2 pxHigherPriorityTaskWoken5.5.3 xSemapho…

库存零件耗尽 任天堂宣布停止New 3DS的维修服务

由于库存零件已耗尽&#xff0c;任天堂宣布自8月28日起停止接受New 3DS游戏机的维修服务。在今年3月份时&#xff0c;任天堂就宣布过2DS、New 3DS、New 3DS LL的维修服务将在库存零件耗尽后终止&#xff0c;目前来看2DS和New 3DS LL的维修服务还将继续&#xff0c;直到零件耗尽…

算法中常用的排序

1.概念 排序是将一组数据,依指定的顺序进行排列的过程. 2.排序的分类 (1).内部排序 指将需要处理的所有数据都加载到内部存储器中进行排序.包括:交换式排序法,选择式排序法和插入式排序法 (2).外部排序 数据量过大,无法全部加载到内存中,需要借助外部存储进行排序.包括:合并排序…

FTP主动与被动模式

文件传送协议FTP&#xff1a; 提供交互式访问FTP屏蔽了各计算机系统的细节&#xff0c;因⽽适合于在异构⽹络中任意计算机之间传送⽂件。 传统FTP默认不加密 工作模式&#xff1a;&#xff08;站在服务器的角度&#xff09; 主动模式&#xff1a;服务器主动使用TCP20端口发起数…

群晖NAS配置SFTP服务并结合内网穿透工具实现无公网IP远程传输文件

文章目录 前言1. 开启群晖SFTP连接2. 群晖安装Cpolar工具3. 创建SFTP公网地址4. 群晖SFTP远程连接5. 固定SFTP公网地址6. SFTP固定地址连接 前言 本文主要介绍如何将在群晖NAS中开启SFTP服务&#xff0c;并安装cpolar内网穿透工具配置公网地址&#xff0c;轻松打造一套高效、安…

LRN正则化是什么?

LRN正则化&#xff0c;全称为Local Response Normalization&#xff08;局部响应归一化&#xff09;&#xff0c;是一种在深度学习&#xff0c;特别是在卷积神经网络&#xff08;CNN&#xff09;中常用的正则化技术。该技术旨在通过模拟生物视觉系统中的侧抑制现象&#xff0c;…

【第0003页 · 递归】N皇后问题

【前言】本文以及之后的一些题解都会陆续整理到目录中&#xff0c;若想了解全部题解整理&#xff0c;请看这里&#xff1a; 第0003页 N皇后问题 今天我们来看一个著名的问题&#xff1a;N皇后问题。在此之前&#xff0c;我们先温习一下递归的思想。当然&#xff0c;温习的方式…

阅读笔记:明朝那些事儿人间再无魏忠贤

持续了10多天时间&#xff0c;明朝那些事儿第八部人间再无魏忠贤截止到今天凌晨0&#xff1a;58分看完了&#xff0c;给我印象比较深刻的人物杨涟&#xff0c;努尔哈赤&#xff0c;孙承宗&#xff0c;袁崇焕&#xff0c;魏忠贤&#xff0c;皇太极&#xff0c;熊廷弼&#xff0c…

C#MDI子窗体通过TabControl列表显示的控制实现过程

类似excel表格中各个表单sheet的切换效果&#xff0c;使用tabcontrol控件实现类似的功能。效果如下&#xff1a; 过程涉及父窗体MDIParent1、子窗体main、自定义基础功能类MdiChildBase。 基础功能类MdiChildBase继承自Form创建&#xff0c;定义了一个委托SetTabControlDelega…

项目:基于TCP的文件传输系统

项目介绍: 模拟FTP原理&#xff1a;客户端连接服务器后&#xff0c;向服务器发送一个文件。文件名可以通过参数指定&#xff0c;服务器端接收客户端传来的文件&#xff08;文件名随意&#xff09;&#xff0c;如果文件不存在自动创建文件&#xff0c;如果文件存在&#xff0c;…

2024年整理的自动化测试面试题及答案

selenium中如何判断元素是否存在&#xff1f; 没有提供原生的方法判断元素是否存在&#xff0c;一般我们可以通过定位元素异常捕获的方式判断selenium中hidden或者是display &#xff1d; none的元素是否可以定位到&#xff1f;不可以&#xff0c;想点击的话&#xff0c;可以用…

C# 爬虫技术:京东视频内容抓取的实战案例分析

摘要 随着互联网技术的飞速发展&#xff0c;数据的获取和分析变得愈发重要。爬虫技术作为数据获取的重要手段之一&#xff0c;广泛应用于各个领域。本文将重点探讨C#语言在京东视频抓取中的实现过程&#xff0c;分析其技术细节&#xff0c;并提供相应的代码实现。 引言 京东…

python学习之路 - 面向对象编程

目录 一、面向对象编程1、成员方法a、类的定义和使用b、案例 2、类和对象3、构造方法4、其他内置方法&#xff08;魔术方法&#xff09;5、面向对象三大特性——封装a、介绍&#xff1a;b、表现形式&#xff1a;私有成员变量与私有成员方法c、作用 6、面向对象三大特性——继承…

iview Cascader 组件动态数据回显

在使用Cascader组件动态加载数据后&#xff0c;编辑的时候回显会有问题 问题如下&#xff1a;回显的时候&#xff0c;如果是多级&#xff0c;只显示了一级且&#xff0c;中间会闪一下 经过多方查找资料发现&#xff0c;是callback造成的。给组件增加on-visible-change事件监听…

如何下载淘宝的主图视频

目录&#xff1a; 1、通过插件插件下载短视频 1&#xff09;获取“Microsoft Edge扩展” 2&#xff09;搜索“aix智能下载器” 3&#xff09;将插件钉在浏览器上 4&#xff09;嗅控并下载视频 2、从其他来源安装插件 1、通过插件插件下载短视频 1&#xff09;获取“M…

孙宇晨:以区块链科技为翼,青年企业家引领社会进步新航向

​ 孙宇晨&#xff0c;作为区块链领域的一位青年企业家&#xff0c;以其大胆的创新精神和卓越的远见&#xff0c;正在用区块链技术推动社会的进步。他不仅在加密货币和区块链技术领域取得了令人瞩目的成就&#xff0c;还通过不断的努力&#xff0c;致力于将这些技术应用…

FreeRTOS 列表 List 源码解析

目录 一、链表及链表项的定义1、链表节点数据结构 xList_ITEM2、链表精简节点结构 xMINI_LIST_ITEM3、链表根节点结构 xLIST 二、链表的相关操作1、初始化1.1 链表节点初始化1.2 链表根节点初始化 2、插入2.1 将节点插入到链表的尾部2.2 将节点按照升序排列插入到链表 3、删除4…

(go)线性表的顺序存储

闲来无事&#xff0c;更新一下&#xff0c;线性表的顺序存储&#xff0c;go语言版本&#xff0c;效果都已经测试过&#xff0c;下面给出各部分细节 文章目录 1、生成一个线性表2、查找3、插入4、求长度5、改值6、删除7、遍历8、测试程序9、完整代码总结 package mainimport &q…