65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (14投票s)

2013 年 5 月 22 日

CPOL

8分钟阅读

viewsIcon

70870

downloadIcon

2260

本文介绍如何在 Win32 C++ 中为对象(文件和非文件对象,如打印机、文件夹、文件、驱动器等)创建快捷方式。

1.0 引言 

快捷方式,或者用程序员的行话来说,Shell 链接,是 PC 世界中一个非常重要的对象。它被广泛用于从本地系统或网络上的任何地方方便地访问“被引用对象”(如快捷方式指向的文件、文件夹、驱动器、打印机等)。安装程序也使用它来引用从桌面安装的程序。请注意,虽然许多人报告成功使用本文讨论的技术创建指向 URL(Internet 快捷方式)的快捷方式,但这并不是创建 Internet 快捷方式的理想方式。要了解如何创建 Internet 快捷方式,请阅读我的文章(提示)“您的网站快捷方式”。

本文将展示如何创建快捷方式

  1. 指向基于文件的对象(本地和网络文件、文件夹、驱动器等)
  2. 指向非基于文件的对象(以打印机为例)

2.0 先决条件

必须对 Win32、OLE COM 编程和 C++ 有所了解。

3.0 创建快捷方式

快捷方式本质上是带有 .lnk 扩展名的二进制文件,其中包含被引用对象的路径以及访问和/或描述该对象所需的其他一些信息。

Win32 中,快捷方式的创建是通过使用 OLE COM IShellLink 接口来创建快捷方式,并使用 IPersistFile 接口将其保存到磁盘(持久化存储)。

3.1 使用代码示例

下载本文附带的源代码。它包含两个文件:一个头文件CreateShortCut.h和一个源文件“CreateShortCut.cpp”。将这两个文件添加到您的项目中,并在您的项目中#includeCreateShortCut.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 作为第一个参数调用,但结果对我来说不令人满意。快捷方式创建得很好,但是:

  1. 即使所有扩展名都设置为隐藏,Windows 资源管理器仍显示 .lnk 扩展名。
  2. 双击快捷方式时,它会正常打开指向的文件夹,但 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 日

  1. 引入了位置常量(CSC_*)。
  2. 引入了参数 `BOOL forceCreate`(默认为 FALSE)。

2013 年 7 月 11 日 

  1. 包含完整的 ANSI 和宽字符版本。
  2. 检测到在使用类的单个对象多次后,存在一些 DLL 提前卸载的情况。这会导致应用程序停止工作,并显示调试信息“已检测到无效的异常处理程序例程(参数:0x00000003)”。为了修复此问题,将 CoInitializeEx 移至类构造函数,将 CoUninitialize 移至析构函数。

2013 年 5 月 22 日

  1. 初始项目。
© . All rights reserved.