понедельник, 20 октября 2014 г.

Как в Windows открыть файл в ассоциированной с ним программе средствами C++?

Для программистов со "стажем" - это, конечно, не проблема. Но вот для новичков составляет определенную сложность, т.к. это напрямую не связано с языком программирования C++, а является функциональностью операционной системы. 
Как вы уже могли догадаться, речь пойдет о применении соответствующей функции WinAPI. Представляю сегодняшнего героя - ShellExecute!

Как благосклонно с нами делится портал MSDN, ShellExecute "Performs an operation on a specified file", что на "великом и могучем" можно расценить как "выполнение операции над определенным файлом". Что же в принципе можно сделать с помощью данной функции? Это открыть файл (отдельно для редактирования) или директорию, напечатать файл, инициировать поиск. Можно так же открыть интернет-адрес в браузере по-умолчанию. Вообщем все то, что так нам не хватает "чистом C++" на Windows :-)))). 

Немного теории.
Определение функции незатейливо и имеет следующий вид:
HINSTANCE ShellExecute(
  _In_opt_  HWND hwnd,
  _In_opt_  LPCTSTR lpOperation,
  _In_      LPCTSTR lpFile,
  _In_opt_  LPCTSTR lpParameters,
  _In_opt_  LPCTSTR lpDirectory,
  _In_      INT nShowCmd
);

где:
hwnd - Дескриптор родительского окна используемый для отображения пользовательского интерфейса или сообщения об ошибках. Это значение может быть равно NULL, если операция не связана с окном (удобно, если приложение консольное).

lpOperation - Указатель на строку с нулевым символом, упоминаемой в данном случае в качестве "глагола", который определяет выполняемое действие. Набор доступных "глаголов" зависит от конкретного файла или папки. Как правило такими "глаголами" являются действия, доступные из контекстного меню объекта. Обычно используются следующие "глаголы":

  • edit - запускает редактор и открывает документ для редактирования. Если lpFile не файл, функция не выполняется.
  • explore - открывает в "Проводнике" папку, которая указана в lpFile.
  • find - инициирует поиск, начиная с каталога, заданного lpDirectory.
  • open - открывает объект, указанный в параметре lpFile. Объектом может быть файл или папка. Сегодня нас будет интересовать именно эта команда - "глагол". 
  • print - печатает файл, указанный в lpFile. Если lpFile не файл, функция не выполняется.
  • NULL - используется "глагол" по умолчанию, если таковой имеется у объекта. Если нет, то  используется глагол "open". Если никакой "глагол" не доступен, система использует первый "глагол", указанный в реестре.

lpFile - Указатель на строку с нулевым символом, который определяет файл или объект, для которого следует выполнить указанный "глагол". Для указания объекта из пространства имен Shell, необходимо указание его полного имени. Обратите внимание, что не все "глаголы" поддерживаются на всех объектах. Например, не все типы документов поддерживает "Печать". Если используется относительный путь для параметра lpDirectory,  то не следует использовать относительный путь для lpFile.

lpParameters - Если lpFile определяет исполняемый файл, этот параметр является указателем на строку с нулевым символом, который определяет параметры, которые передаются в приложение. Формат этой строки определяется вызываемым действием. Если lpFile определяет файл, lpParameters должно быть NULL.

lpDirectory - Указатель на строку с нулевым символом, которая определяет рабочий каталог по умолчанию (в котором будут осуществляться действия с объектом). Если это значение равно NULL, то используется текущий рабочий каталог. Если для lpFile используется относительный путь, то не следует использовать относительный путь для lpDirectory.

nShowCmd - Флаги, определяющие, как будет отображаться приложение  при открытии. Если lpFile определяет файл, флаг просто передается в соответствующее приложении. Флаг передается в приложение, чтобы оно само решило как его обработать. Эти значения определены в winuser.h.
Возможных значений nShowCmd достаточно много и они обладают "говорящими" именами, поэтому  вы можете сами пройти на страницу с описанием функции и посмотреть. В большинстве же случаев достаточным является использование SW_SHOWNORMAL (1) - активация и демонстрация окна приложения как при "первом запуске".

И прежде чем перейти к самому "сладкому", необходимо отметить один очень важный момент - это тип передаваемых символьных параметров - LPCTSTR. Как говорится на сайте MSDNLPCTSTR - это LPCWSTR если определен UNICODE, иначе LPCSTR. Для получения дополнительной информации см Типы данных Windows для Strings

Что реально это означает для нас? А то,что если проект настроен (что в большинстве справедливо для Visual Studio) как UNICODE, то функция ShellExecute будет так же ожидать переменные в UNICODE (const wchar_t*). В противном случае в ANSI (const char*). При этом сама функция ShellExecute является прекомпиленной оберткой для двух других функций:
ShellExecuteA - для работы с параметрами в ANSI,
ShellExecuteW - для работы с параметрами в UNICODE.

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

В настоящей заметке я сознательно не рассматриваю вопрос разработки ANSI|UNICODE приложения, поскольку это тема отдельного обсуждения. Но не могу не отметить один важный момент - применение макроса _T или _TEXT при передачи символьных переменных в функцию. Например:
ShellExecute(NULL,_TEXT("open"),_TEXT("test.txt"),NULL,NULL,1);

Для тех, кто не знает - поясню: если в проекте определен _UNICODE (прошу обратить внимание, что именно со знаком подчеркивания), то _T транслирует строковые литералы в представление с префиксом "L" (обозначая таким образом строку символов UNICODE). В противном случае _T транслирует строку без префикса "L" (т.е. строку символов ANSI).

Т.о., если вы собираетесь разрабатывать свое приложение с поддержкой и ANSI и UNICODE, не забывайте использовать макрос _T или _TEXT. Указанные макросы определены в заголовочном файле "tchar.h". В противном случае вы можете столкнуться с ошибками несовпадения типов при переходе от одной кодировки к другой.

Перейдем же непосредственно к теме заметки - открытию файла в ассоциированной с ним программе. В общем случае, вызов функции будет иметь следующий вид:
ShellExecute(NULL,_TEXT("open"),_TEXT("test.txt"),NULL,NULL,1);
Вызов функции с такими параметрами откроет файл text.txt в ассоциированном с текстовыми файлами редакторе. Поскольку при передачи файла используется относительный путь, функция будет искать файл в рабочей директории программы. Если же необходимо, чтобы программа работала с файлами в какой-то конкретной директории, то вы можете указать ее в качестве параметра lpDirectory:
ShellExecute(NULL,_TEXT("open"),_TEXT("text.txt"),NULL,_TEXT("C:\\temp"),1 );
В данном примере функция будет искать файл text.txt в директории "C:\temp". Либо вы можете сделать тоже самое указав полный путь для имени файла:
ShellExecute(NULL,_TEXT("open"),_TEXT("C:\\temp\\text.txt"),NULL,NULL,1 );
Главное помнить, что потребности определяют средства ;-). Аналогичным образом можно открыть и адрес интернет-страницы задав его в качестве имени файла (с обязательным указанием www):
ShellExecute(NULL,_TEXT("open"),_TEXT("www.cppstock.blogspot.com"),NULL,NULL,1 );
Или директорию на компьютере:
ShellExecute(NULL,_TEXT("open"),_TEXT("C:\\temp"),NULL,NULL,1 );
Или ту же директорию, но с раскрытием дерева каталогов:
ShellExecute(NULL,_TEXT("explore"),_TEXT("C:\\temp"),NULL,NULL,1 );
Надеюсь принцип вызова функции понятен.

Как и любая подобная функция, ShellExecute возвращает результат попытки работы с файлом - если возвращаемое число менее или равно 32, значит произошла ошибка. Перечень таких ошибок указан на странице описания функции. Полагаю, рассмотрение вопроса обработки ошибок излишне :-).

И в заключении - для использования функции ShellExecute необходимо подключить заголовочный файл shellapi.h. При использовании компиляторов отличных от Microsoft VC++, так же следует подключить и файл windows.h.  При этом включение windows.h должно предшествовать shellapi.h:
#include <windows.h>
#include <shellapi.h>

Но для проектов на Microsoft VC++ может потребоваться подключение библиотеки Shell32.lib - будьте внимательны.

Все! Всем кодить! :D

Комментариев нет:

Отправить комментарий