使用c++编写com组件实现windows外壳扩展(自定义右键上下文菜单扩展)
允许在 Windows 文件资源管理器中,当用户在文件、文件夹或空白处右键点击时,根据一定的逻辑显示自定义的菜单项。
一、作用
允许在 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 组件能够被系统识别和使用的关键步骤。可使用 Windows 的 regsvr32
工具来注册和注销 COM组件(比如注册 PS C:\windows\system32> regsvr32 /u C:\Users\86182\Desktop\java-study\customMenu\Debug\x64\customMenu.dll
注销 PS C:\windows\system32> regsvr32 C:\Users\86182\Desktop\java-study\customMenu\Release\x64\customMenu.dll)
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 组件可以被不同的编程语言和应用程序使用。
三、代码实现
项目结构
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
sqlite.h:sqlite的头文件,下载解压后放到这里
4、核心源文件(.cpp 文件)
dllmain.cpp:这是 DLL 入口点的源文件,包含 DllMain 函数的实现,负责初始化和清理工作
MyShellExtension.cpp:可能包含 MyShellExtension 类的实现,负责外壳扩展的具体功能。
pch.cpp:与预编译头文件 pch.h 相关联的源文件,通常仅包含 #include "pch.h" 这一行,用于生成预编译头。
5、资源文件
包含了项目中的资源文件,通常是图标、位图、对话框等
.ico:图标文件,可能用作 DLL 的图标资源。
Resource.rc:资源脚本文件,用于定义和管理应用程序中使用的各种资源,如图标、对话框、菜单、字符串等,这些资源在 Resource.rc 文件中通常会被分配一个唯一的标识符(ID),这些 ID 用于在代码中引用相应的资源
6、模块定义文件
.def:模块定义文件,定义了导出函数列表和其他链接器相关的属性。
7、其他
.gitignore 文件:用于指定哪些文件或文件夹在 Git 中应被忽略,不纳入版本控制。
com-windows.sln 文件:Visual Studio 的解决方案文件,包含整个项目的构建配置和项目组织信息。
com-windows.vcxproj 文件:Visual Studio 项目的配置文件,包含构建设置、编译选项、包含路径等。
com-windows.vcxproj.filters 文件:Visual Studio 的过滤器文件,通常用于定义项目的文件在 IDE 中的显示结构,例如将文件按类别组织在不同的文件夹中。
代码详释
头文件
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>
// 包含sqlite的头文件,需要从官网下载sqlite3.dll放到与customMenu.dll同级的位置
#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 (!targetPath.empty() && folderPath.find(targetPath) == 0) {
// 配置第一个菜单项
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_ICON2));
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_ICON3));
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_LOCAL_MACHINE, 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
四、额外补充
Visual Studio
操作:新建项目,选择动态链接库(DLL)项目
常用操作:
项目配置:
右键项目属性-》配置属性->常规-》修改输出目录和中间目录:比如输出目录:$(SolutionDir)$(ProjectName)\$(Configuration)\$(Platform)\ 中间目录:$(ProjectName)\$(Configuration)\$(Platform)\ ,配置合理的目录有助于生成的文件不产生清理异常(可用于所有配置)
右键项目属性-》配置属性-》链接器-》输入-》修改模块定义文件:比如$(SolutionDir)\defs\Heqiao.def,可在def中定义导出函数
右键项目属性-》配置属性-》C/C++-》预编译头-》对于pch.cpp选择创建 (/Yc),对于整体选择使用 (/Yu):在指定的头文件(如 pch.h
或其他)中,/Yc告诉编译器,将该文件及其包含的内容编译为一个预编译头文件(通常是 .pch
文件),/Yu告诉编译器需要使用预先生成的 .pch
文件
右键项目属性-》配置属性-》C/C++-》常规-》附加包含目录:可用于配置统一的头文件目录位置,比如sqlite.h,这样再cpp中就可以通过#include "<sqlite3.h>"引入,否则就需要#include "..\sqlite3.h"引入;或者比如#include "pch.h",不配置则需要通过#include "..\pch.h"引入
右键项目属性-》配置属性-》链接器-》常规-》附件库目录:可用于配置额外得依赖库得文件位置,比如sqlite.lib,配置内容比如$(ProjectDir)libs(项目根目录下得libs目录)
右键项目属性-》配置属性-》链接器-》输入-》附件依赖项:可用于配置构建时需要链接的额外库文件,比如sqlite3.lib;如果添加了额外依赖但没有配置,则会在生成obj构建中间文件后报错无法解析链接的依赖库符号
编译及调试配置:
菜单栏-》生成(B)-》配置管理器:可进行编译的具体配置,比如64位,32位的(在重新生成解决方案后,项目的过滤器文件内容也会自动更新)
菜单栏-》调试(D)-》选项(O)-》符号-》插入符号文件的位置:可用在调试时确定调试文件(.pdb文件)
菜单栏-》调试(D)-》附加到进程-》选择进程、代码类型为本机代码-》点击附加:可用于调试代码(调试时需要组件处于加载运行状态,对于本文,组件则要先注册运行,再开始调试)
添加资源文件:
在不打开resource.h的前提下双击资源文件中的Resource.rc,右键点击Resource.rc添加资源文件,选择文件类型并添加。它会在与Resource.rc同级的目录下自动生成一个resource.h(不影响在项目结构中分开展示,因为项目结构不是依据实际文件夹结构,而是com-windows.vcxproj.filters 过滤器文件。不过在主文件引入resource.h即使配置了附加库目录,因为路径不指向资源目录,所以就得改成#include "..\resources\resource.h",而Resource.rc中默认即为#include "resource.h"【可通过右键点击Resource.rc文件,点击查看代码查看引用】)
其他:
工具-》创建GUID:可用于创建组件CLSID
工具-》Visual Studio命令提示符:可cd到dll,使用dumpbin /EXPORTS yourfile.dll查看导出函数
更多推荐
所有评论(0)