程式安裝、首次運行引導、使用者自訂快捷方式等場景常需自動建立桌面捷徑。Windows 提供 IShellLink / IPersistFile COM 介面標準化此操作。本文詳細解析從基本建立到「精確放置於桌面目錄」的完整流程,提供 C++ Builder 可直接編譯的代碼。
CoInitialize - 初始化 COM 程式庫CoCreateInstance(CLSID_ShellLink) - 建立 Shell Link 物件IShellLink::SetPath / SetDescription / SetShowCmd / SetIconLocation... - 設定捷徑屬性QueryInterface(IID_IPersistFile) - 獲取持久化介面IPersistFile::Save - 將捷徑儲存為 .lnk 檔案Release - 釋放介面指標CoUninitialize - 反初始化 COM硬編碼 C:\Windows\Desktop 或 C:\Users\Public\Desktop 極不可靠。正確方式使用 SHGetSpecialFolderLocation + SHGetPathFromIDList:
#include // 需連結 shell32.lib
// 獲取當前使用者桌面目錄 (CSIDL_DESKTOP)
// 或所有使用者共用桌面 (CSIDL_COMMON_DESKTOPDIRECTORY)
LPITEMIDLIST pidl;
HRESULT hr = SHGetSpecialFolderLocation(NULL, CSIDL_DESKTOP, &pidl);
if (SUCCEEDED(hr)) {
TCHAR szPath[MAX_PATH];
if (SHGetPathFromIDList(pidl, szPath)) {
// szPath 即為桌面目錄完整路徑
// 例如:C:\Users\Username\Desktop
}
CoTaskMemFree(pidl); // 必須釋放 PIDL
}
#include
#include
#pragma hdrstop
class TShortcutCreator {
public:
struct Config {
String TargetPath; // 目標程式/檔案完整路徑
String Arguments; // 命令列參數
String Description; // 捷徑描述 (工具提示)
String WorkingDir; // 起始目錄
String IconPath; // 圖示來源檔案 (可為目標本身)
int IconIndex = 0; // 圖示資源索引
WORD Hotkey = 0; // 熱鍵 (如 MAKEWORD('N', HOTKEYF_CTRL | HOTKEYF_ALT))
int ShowCmd = SW_SHOWNORMAL; // 顯示狀態
String LinkName; // 捷徑檔名 (不含 .lnk),預設同目標檔名
};
static bool CreateOnDesktop(const Config& cfg) {
// 1. 初始化 COM
HRESULT hr = CoInitialize(NULL);
if (FAILED(hr)) return false;
bool bComInit = true;
// 2. 獲取桌面目錄
LPITEMIDLIST pidl;
hr = SHGetSpecialFolderLocation(NULL, CSIDL_DESKTOP, &pidl);
if (FAILED(hr)) { CoUninitialize(); return false; }
TCHAR szDesktop[MAX_PATH];
if (!SHGetPathFromIDList(pidl, szDesktop)) {
CoTaskMemFree(pidl); CoUninitialize(); return false;
}
CoTaskMemFree(pidl);
// 3. 組合 .lnk 完整路徑
String linkName = cfg.LinkName.IsEmpty() ?
ExtractFileName(cfg.TargetPath) : cfg.LinkName;
if (!linkName.EndsWith(".lnk")) linkName += ".lnk";
String linkPath = IncludeTrailingPathDelimiter(szDesktop) + linkName;
// 4. 建立 Shell Link
IShellLink* pLink = NULL;
hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
IID_IShellLink, (void**)&pLink);
if (FAILED(hr)) { CoUninitialize(); return false; }
// 5. 設定屬性
pLink->SetPath(cfg.TargetPath.c_str());
if (!cfg.Arguments.IsEmpty()) pLink->SetArguments(cfg.Arguments.c_str());
if (!cfg.Description.IsEmpty()) pLink->SetDescription(cfg.Description.c_str());
if (!cfg.WorkingDir.IsEmpty()) pLink->SetWorkingDirectory(cfg.WorkingDir.c_str());
if (!cfg.IconPath.IsEmpty() && FileExists(cfg.IconPath))
pLink->SetIconLocation(cfg.IconPath.c_str(), cfg.IconIndex);
if (cfg.Hotkey) pLink->SetHotkey(cfg.Hotkey);
pLink->SetShowCmd(cfg.ShowCmd);
// 6. 持久化儲存
IPersistFile* pPersist = NULL;
hr = pLink->QueryInterface(IID_IPersistFile, (void**)&pPersist);
bool bSuccess = false;
if (SUCCEEDED(hr)) {
WCHAR wszPath[MAX_PATH];
linkPath.WideChar(wszPath, MAX_PATH);
hr = pPersist->Save(wszPath, TRUE); // TRUE = 覆寫既有檔案
bSuccess = SUCCEEDED(hr);
pPersist->Release();
}
pLink->Release();
CoUninitialize();
return bSuccess;
}
};
void __fastcall TForm1::Button1Click(TObject *Sender) {
TShortcutCreator::Config cfg;
cfg.TargetPath = "C:\\Program Files\\MyApp\\MyApp.exe";
cfg.Arguments = "/auto";
cfg.Description = "我的超級應用程式";
cfg.WorkingDir = "C:\\Program Files\\MyApp";
cfg.IconPath = "C:\\Program Files\\MyApp\\MyApp.exe"; // 使用自身圖示
cfg.IconIndex = 0;
cfg.LinkName = "我的超級應用程式";
if (TShortcutCreator::CreateOnDesktop(cfg))
ShowMessage("桌面捷徑建立成功!");
else
ShowMessage("建立失敗,請檢查權限或路徑。");
}
使用 CSIDL_APPDATA 獲取 AppData\Roaming,拼接 Microsoft\Internet Explorer\Quick Launch:
SHGetSpecialFolderLocation(NULL, CSIDL_APPDATA, &pidl);
SHGetPathFromIDList(pidl, szPath); // -> C:\Users\Name\AppData\Roaming
String qlPath = IncludeTrailingPathDelimiter(szPath) +
"Microsoft\\Internet Explorer\\Quick Launch\\";
> **注意**:Windows 7+ 已無「快速啟動」區,改為任務列釘選 (ITaskbarList3 介面)。
CSIDL_COMMON_DESKTOPDIRECTORY (公用桌面) 需管理員權限requireAdministrator 或使用 ShellExecute("runas") 重啟自身
IShellLink* pLink; IPersistFile* pPersist;
CoCreateInstance(CLSID_ShellLink, ..., IID_IShellLink, (void**)&pLink);
pLink->QueryInterface(IID_IPersistFile, (void**)&pPersist);
pPersist->Load(L"C:\\Desktop\\MyApp.lnk", STGM_READ);
TCHAR szTarget[MAX_PATH]; pLink->GetPath(szTarget, MAX_PATH, NULL, 0);
TCHAR szArgs[MAX_PATH]; pLink->GetArguments(szArgs, MAX_PATH);
TCHAR szDesc[MAX_PATH]; pLink->GetDescription(szDesc, MAX_PATH);
// ...
| HRESULT | 常數 | 原因 |
|---|---|---|
| 0x80004001 | E_NOTIMPL | 介面方法未實現 (罕見) |
| 0x80070005 | E_ACCESSDENIED | 目錄寫入權限不足 |
| 0x80070003 | E_PATHNOTFOUND | 目標路徑/工作目錄不存在 |
| 0x800401F0 | CO_E_NOTINITIALIZED | 未呼叫 CoInitialize |
| 0x80070057 | E_INVALIDARG | 參數無效 (如路徑過長、含非法字元) |
SHGetSpecialFolderLocation 獲取特殊目錄,不要硬編碼路徑CoInitialize/CoUninitialize 必須成對呼叫,建議用 RAII 包裝Release,PIDL 用 CoTaskMemFreeFileExists(TargetPath),避免建立失效捷徑\\?\ 前綴或 Windows 10 1607+ 的長路徑支援Shortcut 表,而非自訂代碼