以编程方式在 C++ 中创建快捷方式






4.89/5 (14投票s)
本文介绍如何在 Win32 C++ 中为对象(文件和非文件对象,如打印机、文件夹、文件、驱动器等)创建快捷方式。
1.0 引言
快捷方式,或者用程序员的行话来说,Shell 链接,是 PC 世界中一个非常重要的对象。它被广泛用于从本地系统或网络上的任何地方方便地访问“被引用对象”(如快捷方式指向的文件、文件夹、驱动器、打印机等)。安装程序也使用它来引用从桌面安装的程序。请注意,虽然许多人报告成功使用本文讨论的技术创建指向 URL(Internet 快捷方式)的快捷方式,但这并不是创建 Internet 快捷方式的理想方式。要了解如何创建 Internet 快捷方式,请阅读我的文章(提示)“您的网站快捷方式”。
本文将展示如何创建快捷方式
- 指向基于文件的对象(本地和网络文件、文件夹、驱动器等)
- 指向非基于文件的对象(以打印机为例)
2.0 先决条件
必须对 Win32、OLE COM 编程和 C++ 有所了解。
3.0 创建快捷方式
快捷方式本质上是带有 .lnk 扩展名的二进制文件,其中包含被引用对象的路径以及访问和/或描述该对象所需的其他一些信息。
在 Win32
中,快捷方式的创建是通过使用 OLE COM IShellLink
接口来创建快捷方式,并使用 IPersistFile
接口将其保存到磁盘(持久化存储)。
3.1 使用代码示例
下载本文附带的源代码。它包含两个文件:一个头文件CreateShortCut.h和一个源文件“CreateShortCut.cpp”。将这两个文件添加到您的项目中,并在您的项目中#include
CreateShortCut.h。
#include "CreateShortCut.h"
3.1.1 基于文件的对象
创建指向基于文件的对象的快捷方式。
CreateShortCut csc;
//Using with the newly introduced CSC_* constants - this example will create a shortcut on the current machine's desktop
ih.CreateLinkFileBase("C:\\somedirectory\\afile.exe", CSC_DESKTOP, "The Comment", MAKEWORD(0x41, HOTKEYF_ALT + HOTKEYF_CONTROL), "-help");
//To force the creation of the destination directory if it doesn't exist
ih.CreateLinkFileBase("C:\\somedirectory\\afile.exe", "C:\\a-non-existing-directory\\", "The Comment", MAKEWORD(0x41, HOTKEYF_ALT + HOTKEYF_CONTROL), "-help", TRUE);
//For the Wide Character version
csc.CreateLinkFileBase(L"C:\\somedirectory\\afile.exe",
L"C:/Users/somedirectory/Desktop", L"The comment to this file",
MAKEWORD(0x41, HOTKEYF_ALT + HOTKEYF_CONTROL), L"-help");
//For the ANSI version
csc.CreateLinkFileBase("C:\\somedirectory\\afile.exe",
"C:/Users/somedirectory/Desktop", "The comment to this file",
MAKEWORD(0x41, HOTKEYF_ALT + HOTKEYF_CONTROL), "-help");
通过此方法,将在由 `pathToLink` 参数指定的目录中创建一个名为 `afile.lnk` 的快捷方式,该快捷方式指向 `C:\\somedirectory\\afile.exe`。该快捷方式的注释为“The comment to this file”,快捷键为 Ctrl + Alt + A。
用于宽字符版本的 `CreateLinkFileBase` 的原型是 ::CreateLinkFileBase(LPCWSTR pathToObj, LPCWSTR pathToLink, LPCWSTR description, WORD hotkey, LPCWSTR cmdLine, BOOL forceCreate = FALSE);
,而 ANSI 版本的原型是 ::CreateLinkFileBase(LPCSTR pathToObj, LPCSTR pathToLink, LPCSTR description, WORD hotkey, LPCSTR cmdLine
, BOOL forceCreate = FALSE)
。
pathToObj
:一个以 null 结尾的字符串(宽字符或 ANSI),包含被引用对象的路径。它必须是绝对路径。这可以是指向文件的路径,例如"C:/somedirectory/afile.exe"
,指向目录的路径,例如"C:/somedirectory/"
,指向驱动器的路径,例如"c:/"
,指向网络文件的路径,例如"\\\\computername\\directory\\file.txt"
,或者指向网络文件夹的路径,例如"\\\\computername\\directory\\"
。重要提示:网络文件夹的路径必须始终以双反斜杠“\\\\computername\\directory\\”结尾。pathToLink
:一个以 null 结尾的字符串(宽字符或 ANSI)或CSC_*
常量,包含保存快捷方式的目录的路径。可以省略末尾的目录分隔符,即"c:/directory"
和"c:/directory/"
都是正确的。CSC_*
常量包括:
CSC_DESKTOP
- 用于在桌面物理存储文件对象的目录的路径。
CSC_ALL_DESKTOP
- 包含出现在所有用户桌面上的文件和文件夹的目录的路径。
CSC_PROGRAMFILES
- Program Files 文件夹的路径。
CSC_PROGRAMFILESX86
- Program Files(x86) 文件夹的路径(在 64 位系统上)。
CSC_STARTUP
- 对应于用户启动程序组的目录的路径。
CSC_SENDTO
- 包含“发送到”菜单项的目录的路径。
CSC_WINDOWS
- Windows 目录的路径。
CSC_SYSTEM32
- System32 目录的路径。
CSC_STARTMENU
- 包含“开始”菜单项的目录的路径。
description
:一个以 null 结尾的字符串(宽字符或 ANSI),包含被引用对象的描述。此文本出现在链接文件的注释中,以及用户鼠标悬停在快捷方式上时显示的工具提示中。可以为NULL
。hotkey
:这是一个WORD
。它表示激活快捷方式的按键组合。有关如何为快捷键MAKEWORD
的更多信息,HOTKEYF_ALT
代表 ALT 键,HOTKEYF_CONTROL
代表 CTRL 键,HOTKEYF_SHIFT
代表 SHIFT 键,0x41 代表 A,0x42 代表 B,以此类推。可以为NULL
。cmdLine
:一个以 null 结尾的字符串(宽字符或 ANSI),包含要传递给被引用对象的命令行参数,例如"-help"
。可以为NULL
。forceCreate
:这是一个可选参数,默认为FALSE
。如果设置为TRUE
,则将在pathToLink
中指定的目录不存在时创建它。
3.1.2 非基于文件的对象
也可以为非基于文件的对象创建快捷方式。此处以已安装的打印机为例。要为名为“Fax”的打印机创建快捷方式:
CreateShortCut csc;
//Using with the newly introduced CSC_* constants - this example will create a shortcut on the current machine's desktop
ih.CreateLinkFileBase("fax", CSC_DESKTOP, "The Printer named Fax", MAKEWORD(0x43, HOTKEYF_ALT + HOTKEYF_CONTROL));
//To force the creation of the destination directory if it doesn't exist
ih.CreateLinkFileBase("fax", "
C:\\a-non-existing-directory\\
", "The Printer named Fax", MAKEWORD(0x43, HOTKEYF_ALT + HOTKEYF_CONTROL), TRUE);
//For Wide Character Version
csc.CreateLinkToPrinter(L"fax",
L"C:/somedirectory/", L"", MAKEWORD(0x43, HOTKEYF_ALT + HOTKEYF_CONTROL));
//For ANSI version
csc.CreateLinkToPrinter("fax", "C:/somedirectory/",
"", MAKEWORD(0x43, HOTKEYF_ALT + HOTKEYF_CONTROL));
`CreateLinkToPrinter` 方法的原型是 ::CreateLinkToPrinter(LPCWSTR printerName, LPCWSTR pathToLink, LPCWSTR description, WORD hotkey, BOOL forceCreate = FALSE);
,ANSI 版本的原型是 ::CreateLinkToPrinter(LPCSTR printerName, LPCSTR pathToLink, LPCSTR description, WORD hotkey, BOOL forceCreate = FALSE);
。
printerName
:一个以 null 结尾的字符串(宽字符或 ANSI),包含已安装打印机的名称。此名称不区分大小写,因此“Fax”和“fax”是相同的。pathToLink
:一个以 null 结尾的字符串(宽字符或 ANSI)或CSC_*
常量,包含保存快捷方式的目录的路径。可以省略末尾的目录分隔符,即"c:/directory"
和"c:/directory/"
都是正确的。CSC_*
常量包括:CSC_DESKTOP
- 用于在桌面物理存储文件对象的目录的路径。CSC_ALL_DESKTOP
- 包含出现在所有用户桌面上的文件和文件夹的目录的路径。CSC_PROGRAMFILES
- Program Files 文件夹的路径。CSC_PROGRAMFILESX86
- Program Files(x86) 文件夹的路径(在 64 位系统上)。CSC_STARTUP
- 对应于用户启动程序组的目录的路径。CSC_SENDTO
- 包含“发送到”菜单项的目录的路径。CSC_WINDOWS
- Windows 目录的路径。CSC_SYSTEM32
- System32 目录的路径。CSC_STARTMENU
- 包含“开始”菜单项的目录的路径。
description
: 一个以 null 结尾的字符串(宽字符或 ANSI),包含被引用对象的描述。此文本出现在链接文件的注释中,以及用户鼠标悬停在快捷方式上时显示的工具提示中。可以为NULL
。hotkey
:这是一个WORD
。它表示激活快捷方式的按键组合。有关如何为快捷键MAKEWORD
的更多信息,HOTKEYF_ALT
代表 ALT 键,HOTKEYF_CONTROL
代表 CTRL 键,HOTKEYF_SHIFT
代表 SHIFT 键,0x41 代表 A,0x42 代表 B,以此类推。可以为NULL
。forceCreate
:这是一个可选参数,默认为FALSE
。如果设置为TRUE
,则将在 `pathToLink` 中指定的目录不存在时创建它。
4.0 代码解释
对于想了解整个类工作原理的人。
4.1 基于文件的对象
基于文件的对象快捷方式创建由重载的 ::CreateLinkFileBase
方法处理。ANSI 版本仅将其参数转换为宽字符,然后调用宽字符版本。其原理是首先使用 _wstat()
函数来验证 `pathToObj` 是否为文件夹或(文件或驱动器),然后根据结果调用 CoCreateInstance()
。
根据 MSDN 文档,如果引用对象是文件夹,CoCreateInstance()
应以 CLSID_FolderShortCut
作为第一个参数调用,但结果对我来说不令人满意。快捷方式创建得很好,但是:
- 即使所有扩展名都设置为隐藏,Windows 资源管理器仍显示 .lnk 扩展名。
- 双击快捷方式时,它会正常打开指向的文件夹,但 Windows 资源管理器中的地址栏会指向快捷方式的位置而不是文件夹的位置。
将 CoCreateInstance
的第一个参数设置为 CLSID_ShellLink
,而不是遵循 MSDN 的建议,解决了这两个问题。
.
.
.
if(isFile)
hRes = CoCreateInstance(CLSID_ShellLink, NULL,
CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
else
{
//hRes = CoCreateInstance(CLSID_FolderShortcut, NULL,
// CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
hRes = CoCreateInstance(CLSID_ShellLink, NULL,
CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
}
.
.
.
创建 IShellLink
指针,相应地设置值,并查询对象以获取 IPersistFile
接口,该接口用于将快捷方式保存到持久化存储。
BOOL isFile = FALSE;
//Find out if it is a file or a folder
struct _stat s;
if(_wstat(pathToObj, &s) == 0)
{
if( s.st_mode & S_IFDIR )
isFile = FALSE;
else if( s.st_mode & S_IFREG )
isFile = TRUE;
else
{
ReportError("Not file, device, or folder");
return 0;
}
}
else
{
ReportError("Path not found");
return 0;
}
//Appending '/' to pathToLink if not present
memset(path, '\0', MAX_PATH);
_wmakepath_s(path, MAX_PATH, NULL, pathToLink, NULL, NULL);
//***create the directory
if(forceCreate)
{
createDir(pathToLink);
}
HRESULT hRes;
IShellLink* psl;
if(isFile)
hRes = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
else
{
//hRes = CoCreateInstance(CLSID_FolderShortcut, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
hRes = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
}
if(SUCCEEDED(hRes))
{
IPersistFile* ppf;
psl->SetPath(pathToObj);
psl->SetDescription(description);
psl->SetHotkey(hotkey);
psl->SetArguments(cmdLine);
//Query IShellLink for IPersistFile interface
hRes = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf);
if(SUCCEEDED(hRes))
{
//Build the output path
wchar_t* fname = new wchar_t[MAX_PATH];
wchar_t* drive = new wchar_t[MAX_PATH];
_wsplitpath(pathToObj, drive, NULL, fname,NULL);
if(wcscmp(fname,L"") != 0)//For files
{
wcscat_s(path, MAX_PATH, fname);
}
else if(wcscmp(drive,L"") != 0)//For drives
{
wchar_t d[2];
d[0] = drive[0];
d[1] = '\0';
wcscat_s(path, MAX_PATH, d);
}
delete [] drive;
delete [] fname;
wcscat_s(path, MAX_PATH, L".lnk");
ppf->Save(path, TRUE);
ppf->Release();
}
psl->Release();
}
else
{
ReportError("CoCreateInstance Error");
return 0;
}
return 1;
4.2 非基于文件的对象
非基于文件的对象快捷方式创建由重载的 ::CreateLinkToPrinter
方法处理。SHGetSpecialFolderLocation()
用于获取已安装打印机的位置,方法是将第二个参数设置为 CSIDL_PRINTERS
。使用 `BindToObject()` 将 IShellFolder
对象绑定到由 `SHGetSpecialFolderLocation()` 函数设置的 `LPITEMIDLIST`。
枚举 IShellLink
对象,并将每个已安装打印机的名称(从 IShellFolder
的 `GetDisplayNameOf()` 获取)与输入的名称(`printerName`)进行比较。如果匹配,则将打印机文件夹的 `LPITEMIDLIST` 追加(使用 `Append` 方法)到打印机的 `LPITEMIDLIST`。
追加这两个
非常重要,因为 LPITEMIDLIST
IShellLink
接口的 SetIDList
需要一个绝对 `LPITEMIDLIST`,而从 IShellFolder
对象枚举获得的 `LPITEMIDLIST` 是相对于打印机文件夹的。
根据 MSDN,要创建指向非文件对象的快捷方式,请将 `SetIDList` 设置为对象的 `LPITEMIDLIST`,而不是像基于文件的对象那样设置 `SetPath()`,其他一切都保持不变。
HRESULT hr = S_OK;
ULONG celtFetched;
LPITEMIDLIST pidlItems = NULL;
LPITEMIDLIST netItemIdLst = NULL;
IShellFolder* pPrinterFolder = NULL;
IEnumIDList* pEnumIds = NULL;
IShellFolder* pDesktopFolder = NULL;
LPITEMIDLIST full_pid;
hr = SHGetMalloc(&pMalloc);
hr = SHGetDesktopFolder( &pDesktopFolder );
hr = SHGetSpecialFolderLocation( NULL, CSIDL_PRINTERS, &netItemIdLst );
hr = pDesktopFolder->BindToObject( netItemIdLst, NULL, IID_IShellFolder, (void **)&pPrinterFolder );
//Appending '/' to pathToLink if not present
memset(path, '\0', MAX_PATH);
_wmakepath_s(path, MAX_PATH, NULL, pathToLink, NULL, NULL);
//***create the directory
if(forceCreate)
{
createDir(pathToLink);
}
if(SUCCEEDED(hr))
{
hr = pPrinterFolder->EnumObjects( NULL, SHCONTF_NONFOLDERS, &pEnumIds );
if(SUCCEEDED(hr))
{
STRRET strDisplayName;
while((hr = pEnumIds->Next( 1, &pidlItems, &celtFetched)) == S_OK && celtFetched > 0)
{
hr = pPrinterFolder->GetDisplayNameOf(pidlItems, SHGDN_NORMAL, &strDisplayName);
if(SUCCEEDED(hr))
{
if(_wcsicmp(strDisplayName.pOleStr, printerName) == 0)
{
wcscat_s(path, MAX_PATH, strDisplayName.pOleStr);
wcscat_s(path, MAX_PATH, L".lnk");
full_pid=Append(netItemIdLst, pidlItems);
//Create the shortcut
CreateLink(full_pid, path, description, hotkey);
}
}
}
}
}
return 0;
5.0 结论
使用 `::ReportError` 来跟踪错误。它仅将错误消息打印到屏幕(使用 `printf()`),您可以根据需要重写它。
6.0 历史记录
2013 年 7 月 19 日
- 引入了位置常量(
CSC_*
)。 - 引入了参数 `BOOL forceCreate`(默认为 FALSE)。
2013 年 7 月 11 日
- 包含完整的 ANSI 和宽字符版本。
- 检测到在使用类的单个对象多次后,存在一些 DLL 提前卸载的情况。这会导致应用程序停止工作,并显示调试信息“已检测到无效的异常处理程序例程(参数:0x00000003)”。为了修复此问题,将
CoInitializeEx
移至类构造函数,将CoUninitialize
移至析构函数。
2013 年 5 月 22 日
- 初始项目。