Работа с ярлыками

Чтение и изменение свойств, создание новых ярлыков

Автор: Илья Зарецкий
АО "Промышленно-строительный банк"
Опубликовано: 31.03.2004
Версия текста: 1.0

Общие замечания
Интерфейс IShellLink
GetPath
SetPath
GetArguments
SetArguments
GetWorkingDirectory
SetWorkingDirectory
GetHotKey
SetHotKey
GetShowCmd
SetShowCmd
GetDescription
SetDescription
GetIconLocation
SetIconLocation
Resolve
Интерфейс IPersistFile
Load
Save
GetCurFile
IsDirty
Чтение/изменение существующего ярлыка
Пример
Создание нового ярлыка
Пример
Заключение

Демонстрационный проект

Общие замечания

Для управления ярлыками в Windows используется COM-класс ShellLink, имеющий идентификатор CLSID_ShellLink и реализующий два интерфейса: IShellLink, который позволяет получать и назначать свойства ярлыка, и IPersistFile, предназначенный (в данном случае) для чтения и записи файла ярлыка. Кроме этого, существует функция SHAddToRecentDocs, предназначенная для добавления в меню Start/Documents ярлыков на документы, которые пользователь открывал последними.

Интерфейс IShellLink

В действительности, существует две версии этого интерфейса: IShellLinkA и IShellLinkW, последний из которых доступен только под Windows NT/2000/XP; какая из версий будет использоваться зависит от того, в какой конфигурации компилируется приложение — ANSI или Unicode. Ниже перечислены методы данного интерфейса.

GetPath

HRESULT GetPath(
   LPTSTR pszDest,
   int cchMax,
   WIN32_FIND_DATA* pwfd,
   DWORD fdwFlags
);

Копирует в буфер по адресу pszDest размером cchMax символов полное имя объекта, на который ссылается данный ярлык, и записывает в структуру, адресуемую параметром pwfd, атрибуты этого объекта. В качестве параметра fdwFlags может быть указана комбинация следующих флагов:

SLGP_SHORTPATH вернуть имя в формате 8.3
SLGP_UNCPRIORITY вернуть имя в формате UNC (Universal Naming Convention)
SLGP_RAWPATH вернуть необработанное имя (оно, согласно MSDN, представляет собой „нечто, что может не существовать и может содержать имена переменных окружения, вместо которых необходимо подставить их значения“)

Метод может вернуть одно из следующих значений:

NOERROR успешное выполнение, в pszDest скопирован полный путь
S_FALSE успешное выполнение, но в pszDest записана пустая строка (это происходит в том случае, когда объекту, на который ссылается ярлык, не соответствует реальный файл на диске — Recycle Bin, Control Panel, Printers)
E_xxx код ошибки OLE

SetPath

HRESULT SetPath(
   LPCTSTR pszSrc
);

Назначает полное имя объекта, на который ссылается данный ярлык, в соответствии с параметром pszSrc. Метод возвращает значение NOERROR при успешном выполнении или код ошибки OLE — в противном случае.

GetArguments

HRESULT GetArguments(
   LPTSTR pszDest,
   int cchMax
);

Копирует в буфер по адресу pszDest размером cchMax символов аргументы, передаваемые объекту при его открытии (эти аргументы задаются в свойствах ярлыка на вкладке Shortcut в поле ввода Target вслед за именем объекта). Метод возвращает значение NOERROR при успешном выполнении или код ошибки OLE — в противном случае.

SetArguments

HRESULT SetArguments(
   LPCTSTR pszSrc
);

Назначает аргументы, передаваемые объекту при его открытии, в соответствии со строкой pszSrc. Метод возвращает значение NOERROR при успешном выполнении или код ошибки OLE — в противном случае.

GetWorkingDirectory

HRESULT GetWorkingDirectory(
   LPTSTR pszDest,
   int cchMax
);

Копирует в буфер по адресу pszDest размером cchMax символов имя папки, которая должна стать текущей для открытого объекта (это имя задается в свойствах ярлыка на вкладке Shortcut в поле ввода Start in). Метод возвращает значение NOERROR при успешном выполнении или код ошибки OLE — в противном случае.

SetWorkingDirectory

HRESULT SetWorkingDirectory(
   LPCTSTR pszSrc
);

Назначает имя папки, которая должна стать текущей для открытого объекта, в соответствии со строкой pszSrc. Метод возвращает значение NOERROR при успешном выполнении или код ошибки OLE — в противном случае.

GetHotKey

HRESULT GetHotKey(
   WORD* pwDest
);

Копирует в переменную по адресу pwDest „горячую клавишу“, используемую для открытия объекта (это сочетание клавиш задается в свойствах ярлыка на вкладке Shortcut в поле Shortcut key). Младший байт скопированного значения содержит виртуальный код основной клавиши (VK_xxx), а старший байт — комбинацию флагов, соответствующих клавишам-модификаторам:

HOTKEYF_CONTROL клавиша Ctrl
HOTKEYF_SHIFT клавиша Shift
HOTKEYF_ALT клавиша Alt

Метод возвращает значение NOERROR при успешном выполнении или код ошибки OLE — в противном случае.

SetHotKey

HRESULT SetHotKey(
   WORD wSrc
);

Назначает „горячую клавишу“, используемую для открытия объекта. Младший байт параметра wSrc должен содержать виртуальный код основной клавиши, а старший — комбинацию флагов HOTKEYF_xxx, соответствующих клавишам-модификаторам, возможные значения которых приведены выше. Метод возвращает значение NOERROR при успешном выполнении или код ошибки OLE — в противном случае.

GetShowCmd

HRESULT GetShowCmd(
   int* pnDest
);

Записывает в переменную по адресу pnDest команду показа главного окна объекта (вариант отображения главного окна выбирается в свойствах ярлыка на вкладке Shortcut из списка Run). Возможно одно из следующих значений:

SW_SHOWNORMAL соответствует варианту Normal window
SW_SHOWMINNOACTIVE соответствует варианту Minimized
SW_SHOWMAXIMIZED соответствует варианту Maximized

Метод возвращает значение NOERROR при успешном выполнении или код ошибки OLE — в противном случае.

SetShowCmd

HRESULT SetShowCmd(
   int nSrc
);

Назначает команду показа главного окна объекта в соответствии с параметром nSrc, возможные значения которого приведены выше. Метод возвращает значение NOERROR при успешном выполнении или код ошибки OLE — в противном случае.

GetDescription

HRESULT GetDescription(
   LPTSTR pszDest,
   int cchMax
);

Копирует в буфер по адресу pszDest размером cchMax символов описание ярлыка (под Windows 2000/XP оно задается в свойствах ярлыка на вкладке Shortcut в поле ввода Comment и отображается при наведении курсора мышки на ярлык). Метод возвращает значение NOERROR при успешном выполнении или код ошибки OLE — в противном случае.

SetDescription

HRESULT SetDescription(
   LPCTSTR pszSrc
);

Назначает описание ярлыка в соответствии со строкой pszSrc. Метод возвращает значение NOERROR при успешном выполнении или код ошибки OLE — в противном случае.

GetIconLocation

HRESULT GetIconLocation(
   LPTSTR pszDest,
   int cchMax,
   int* pnIndex
);

Копирует в буфер по адресу pszDest размером cchMax символов полное имя файла, содержащего иконку для данного ярлыка, а в переменную по адресу pnIndex — индекс иконки в этом файле, начиная с 0. Метод возвращает значение NOERROR при успешном выполнении или код ошибки OLE — в противном случае.

SetIconLocation

HRESULT SetIconLocation(
   LPCTSTR pszSrc,
   int nIndex
);

Назначает для данного ярлыка иконку, содержащуюся в файле с именем pszSrc и имеющую индекс nIndex. Метод возвращает значение NOERROR при успешном выполнении или код ошибки OLE — в противном случае.

Resolve

HRESULT Resolve(
   HWND hWnd,
   DWORD fdwFlags
);

Выполняет поиск объекта, на который ссылается данный ярлык. Параметр hWnd должен содержать дескриптор окна, поверх которого будут выводиться диалоговые окна для ввода дополнительной информации, если она понадобится системе. Через параметр fdwFlags передается набор флагов, определяющих параметры поиска:

SLR_NOSEARCH не использовать эвристический поиск
SLR_UPDATE если объект, на который ссылается ярлык был изменен, необходимо обновить его полное имя
SLR_NOUPDATE не обновлять ярлык в любом случае
SLR_INVOKE_MSI запустить Microsoft Windows Installer для восстановления объекта, на который ссылается ярлык, если это необходимо
SLR_NO_UI не запрашивать дополнительную информацию, если объект, на который ссылается ярлык, не может быть найден (если этот флаг установлен, старшее слово параметра fdwFlags может содержать максимальный интервал времени, в течение которого будет выполняться поиск; значение 0 соответствует интервалу по умолчанию, составляющему 3000 мс)

Метод возвращает значение NOERROR при успешном выполнении или код ошибки OLE — в противном случае.

Интерфейс IPersistFile

Этот интерфейс предоставляет методы, предназначенные для работы с дисковыми файлами.

Load

HRESULT Load(
   LPCOLESTR pstrFileName,
   DWORD fdwMode
);

Открывает файл с именем pstrFileName и инициализирует COM-объект данными из этого файла; параметр fdwMode определяет режим работы с файлом и может быть комбинацией следующих флагов:

STGM_READ чтение данных
STGM_WRITE запись данных
STGM_READWRITE чтение и запись данных
STGM_SHARE_EXCLUSIVE блокировать доступ других процессов к файлу
STGM_SHARE_DENY_READ блокировать чтение файла другими процессами
STGM_SHARE_DENY_WRITE блокировать запись в файл другими процессами
STGM_SHARE_DENY_NONE не блокировать другим процессам доступ к файлу

Метод может возвращать одно из следующих значений:

S_OK успешное выполнение
E_OUTOFMEMORY для выполнения требуемой операции недостаточно памяти
E_FAIL произошла ошибка

Save

HRESULT Save(
   LPCOLESTR pstrFileName,
   BOOL fSetCurrent
);

Записывает данные в файл с именем pstrFileName; если параметр fSetCurrent задан равным TRUE, то для записи данных в тот же самый файл при следующих вызовах метода параметр pstrFileName можно задавать равным NULL (значение параметра fSetCurrent при этом игнорируется). Рассмотрим простой пример:

IPersistFile* ppf;
...
// обработка команды "Сохранить как..."
ppf->Save(имя_файла, TRUE);
...
// обработка команды "Сохранить"
ppf->Save(NULL, FALSE);

Метод Save возвращает значение S_OK при успешном выполнении или E_FAIL — в случае ошибки.

GetCurFile

HRESULT GetCurFile(
   LPOLESTR* ppstrDest,
);

Выделяет с помощью интерфейса IMalloc блок памяти, копирует в него полное имя файла, который COM-объект использует для чтения/записи данных, и записывает адрес выделенного блока в переменную по адресу ppstrDest. Освобождение этого блока памяти должно выполняться приложением-клиентом. Пример использования:

IPersistFile* ppf;
IMalloc* pMalloc;

...   // получили указатель на IPersistFile
LPOLESTR pstrCurFile = NULL;
ppf->GetCurFile(&pstrCurFile);
::MessageBoxW(NULL, pstrCurFile, L"Current File", MB_OK);
// получаем указатель на IMalloc
::SHGetMalloc(&pMalloc);
// освобождаем память
pMalloc->Free(pstrCurFile);
// IMalloc больше не нужен
pMalloc->Release();

Метод GetCurFile возвращает одно из следующих значений:

S_OK полное имя файла успешно скопировано
S_FALSE файл не имеет имени и скопированная строка является шаблоном вида *.расширение
E_OUTOFMEMORY для выполнения требуемой операции недостаточно памяти
E_FAIL произошла ошибка

IsDirty

HRESULT IsDirty(void);

Метод возвращает значение S_OK, если данные были изменены с момента последнего сохранения файла, или S_FALSE — в противном случае.

Чтение/изменение существующего ярлыка

Для чтения (и возможно — изменения) свойств уже существующего ярлыка необходимо выполнить следующую последовательность действий:

  1. Инициализировать, если это еще не было сделано, COM-библиотеку вызовом функции CoInitialize (или CoInitializeEx).
  2. Создать с помощью функции CoCreateInstance экземпляр COM-класса ShellLink и получить указатель на его интерфейс IPersistFile.
  3. Загрузить вызовом метода Load требуемый ярлык, указав желаемый режим доступа к его свойствам — чтение (STGM_READ), запись (STGM_WRITE) или чтение/запись (STGM_READWRITE).
  4. Получить указатель на интерфейс IShellLink „нашего“ COM-объекта, вызвав метод QueryInterface через полученный ранее указатель на IPersistFile.
  5. Если существует вероятность того, что объект, на который ссылается ярлык, был перемещен — вызвать через полученный указатель метод Resolve для поиска объекта.
  6. Получить и (или) назначить требуемые свойства ярлыка, вызвав через указатель на IShellLink его методы GetXxxx и (или) SetXxxx соответственно.
  7. При необходимости — сохранить сделанные изменения с помощью метода Save интерфейса IPersistFile.
  8. „Отпустить“ полученные интерфейсы, вызвав для каждого из них метод Release.
  9. Завершить, если это необходимо, работу с COM-библиотекой вызовом функции CoUninitialize (перед этим можно вызвать функцию CoFreeUnusedLibraries для выгрузки из памяти всех неиспользуемых более COM-серверов).

Пример

В качестве примера перечисленных действий ниже приведен текст функции, которая принимает в качестве параметра имя ярлыка и выводит на консоль полное имя файла, на который этот ярлык ссылается.

        void print_shortcut_target(LPCTSTR pszShortcut)
{
   IPersistFile* ppf;
   IShellLink* pshl;
   WIN32_FIND_DATA wfd;

   // инициализируем COM-библиотеку
   ::CoInitialize(NULL);

   // создаем COM-объект и получаем указатель на IPersistFile
   ::CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
      IID_IPersistFile, reinterpret_cast<void**>(&ppf));

   // загружаем ярлык#if defined(_UNICODE)
   ppf->Load(pszShortcut, STGM_READ);
#else
   LPWSTR pwszTemp = new WCHAR[_MAX_PATH];
   mbstowcs(pwszTemp, pszShortcut, _MAX_PATH);
   ppf->Load(pwszTemp, STGM_READ);
   delete[] pwszTemp;
#endif// получаем указатель на IShellLink
   ppf->QueryInterface(IID_IShellLink, reinterpret_cast<void**>(&pshl));

   // ищем объект, на который ссылается ярлык
   pshl->Resolve(NULL, SLR_ANY_MATCH | SLR_NO_UI);

   // получаем имя объекта и выводим его на консоль
   LPTSTR pszTarget = new TCHAR[_MAX_PATH];
   pshl->GetPath(pszTarget, _MAX_PATH, &wfd, 0);
   _putts(pszTarget);
   delete[] pszTarget;

   // убираем за собой
   pshl->Release();
   ppf->Release();

   // завершаем работу с COM-библиотекой
   ::CoFreeUnusedLibraries();
   ::CoUninitialize();
}

Создание нового ярлыка

Для создания нового ярлыка необходимо выполнить следующие действия:

  1. Инициализировать, если это еще не было сделано, COM-библиотеку вызовом функции CoInitialize (или CoInitializeEx).
  2. Создать с помощью функции CoCreateInstance экземпляр COM-класса ShellLink и получить указатель на его интерфейс IShellLink.
  3. Назначить вызовами соответствующих методов SetXxxx полученного интерфейса желаемые свойства ярлыка.
  4. Получить указатель на интерфейс IPersistFile „нашего“ COM-объекта, вызвав метод QueryInterface через полученный ранее указатель на IShellLink.
  5. Сохранить созданный ярлык в файл с заданным именем вызовом метода Save.
  6. „Отпустить“ полученные интерфейсы, вызвав для каждого из них метод Release.
  7. Завершить, если это необходимо, работу с COM-библиотекой вызовом функции CoUninitialize (перед этим можно вызвать функцию CoFreeUnusedLibraries для выгрузки из памяти всех неиспользуемых более COM-серверов).

Пример

Возможный вариант реализации описанной последовательности действий содержится в методе OnButtonCreate класса CMainDialog из примера к данной статье.

        void CMainDialog::OnButtonCreate(void)
{
        USES_CONVERSION;

        HRESULT hr;
        IShellLink* pShLink;
        TCHAR szIconFile[_MAX_PATH];
        int iIcon;
        IPersistFile* ppf;
        CString strName;

        if (UpdateData()) {
                // создаем ShellLink и получаем указатель на IShellLink
                hr = ::CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink,
                        reinterpret_cast<void**>(&pShLink));
                if (SUCCEEDED(hr)) {
                        // назначаем иконку в соответствии с типом файла
                        GetFileIcon(m_strTarget, szIconFile, &iIcon);
                        if (::lstrlen(szIconFile) > 0) {
                                pShLink->SetIconLocation(szIconFile, iIcon);
                        }
                        // назначаем поле "Target"
                        pShLink->SetPath(m_strTarget);
                        // назначаем поле "Start in"
                        pShLink->SetWorkingDirectory(m_strStartIn);
                        // назначаем поле "Run"staticint anShowCmds[] = { SW_SHOWNORMAL, SW_SHOWMINNOACTIVE, SW_SHOWMAXIMIZED };
                        pShLink->SetShowCmd(anShowCmds[m_iRun]);
                        // получаем интерфейс IPersistFile
                        hr = pShLink->QueryInterface(IID_IPersistFile, reinterpret_cast<void**>(&ppf));
                        if (SUCCEEDED(hr)) {
                                // записываем файл ярлыка в заданную папку
                                strName.Format(IDS_NAME_FORMAT, ::PathFindFileName(m_strTarget));
                                ppf->Save(T2CW(m_strLocation + _T('\\') + strName), FALSE);
                                AfxMessageBox(IDS_SUCCEEDED, MB_ICONINFORMATION | MB_OK);
                                // отпускаем IPersistFile
                                ppf->Release();
                        }
                        else {
                                AfxMessageBox(IDS_SAVE_ERROR, MB_ICONSTOP | MB_OK);
                        }
                        // отпускаем IShellLink
                        pShLink->Release();
                }
                else {
                        AfxMessageBox(IDS_CREATE_ERROR, MB_ICONSTOP | MB_OK);
                }
        }
}

Заключение

Надеюсь, что теперь работа с ярлыками не будет вызывать у читателя сколько-нибудь серьезных трудностей. Но помните — «In Windows, there’s always a catch…»


Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.