0 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Получения HWND оболочки Windows

Получения HWND оболочки Windows

Shell Extensions и как с ними бороться

Думаю, вы замечали, что некоторые программы добавляют собственные пункты в системное контекстное меню. Например, WinRAR добавляет «Сжать» и «Распаковать в…», ICQ — «Переслать пользователю» и пр. Механизм, с помощью которого ваш код «внедряется» в оболочку Windows, называется Shell Extensions — именно о нем пойдет речь…

Определения

В дальнейшем, говоря «программист среднего уровня», мы будем подразумевать человека, который уже написал свой первый текстовый редактор и знаком с практикой создания собственных компонент.

Shell Extensions — набор сервисных функций Windows API, призванный обеспечить расширение базовых функций оболочки Windows Explorer за счет наших надстроек. В числе основных функций Shell Extensions:

  • работа с системными контекстными меню;
  • работа с папками и объектами из пространства имен оболочки Windows (Мои Документы, Принтеры, Панель управления…);
  • использование механизма Drag&Drop;
  • создание и использование ярлыков.

Для реализации задуманного нам понадобятся интерфейсы IContextMenu и IShellFolder. Указатель на главный интерфейс IshellFolder, соответствующий «Рабочему столу» оболочки, можно получить, используя функцию SHGetDesktopFolder, объявление которой выглядит так:

Эта функция возвращает нам указатель на интерфейс IShellFolder, который возвращается в переменной ppshf. Далее допустим, что у нас в компоненте имеется поле под названием ShellObject типа String, в котором хранится путь к необходимому объекту (к примеру — C:WindowsNotePad.exe), и что нам нужно получить его контекстное меню (рис. 2). Для этого сначала используем метод из интерфейса IShellFolder:

  • hwndOwner — дескриптор родительского окна (0);
  • cidl — количество элементов, на которое указывает значение apidl (1);
  • apidl — параметр, представляющий собой уникальный идентификатор объекта;
  • riid — глобальный уникальный идентификатор системы Windows (IID_IContextMenu из файла ShlObj.pas);
  • prgfInOut — зарезервировано, должно быть nil;
  • ppvOut — переменная, которая получит указатель на «заказанный» интерфейс.

После использования этого оператора нам понадобится обратиться к функциям WinAPI — для работы с контекстными меню. Это, в первую очередь:

Синтаксис первой и последней функции, я думаю, понятен и без разъяснений. Функция TrackPopupMenu, собственно, и выводит на экран контекстное меню. Параметры этой функции принимают значения:

  • hMenu — дескриптор контекстного меню. Это тот самый Menu, который мы создали с помощью CreatePopupMenu;
  • uFlags — выравнивание относительно координат. Возможные значения: TPM_CENTERALIGN, TPM_LEFTALIGN, TPM_RIGHTALIGN, TPM_LEFTBUTTON, TPM_RIGHTBUTTON, TPM_RETURNCMD. Этот параметр используется для возврата команды (как будет показано ниже);
  • x, y — координаты, по которым будем «впрыгивать» наше меню;
  • nReserved — соответственно, приравниваем к нулю;
  • hWnd — дескриптор родительского окна;
  • prcRect — указатель на структуру TRect, которая задает «окно» в экранных координатах в пределах которого пользователь может щелкать без каких-либо исчезновений контекстного меню. Если = nil, то при нажатии мышкой за пределами контекстного меню оное исчезнет.

Возвращаемое значение показывает наличие команды или ее отсутствие. Если True — пользователь выбрал пункт; False — соответственно, не выбрал.

А теперь — самое главное

Ну что ж, сделали мы Menu — остается наполнить его содержимым, соответствующим нашему ShellObject. Для этого узнаем сначала его идентификатор (PItemIDList) — сделаем это при помощи метода ParseDisplayName из интерфейса IshellFolder. Этот метод объявлен следующим образом:

function ParseDisplayName (hwndOwner: HWND;pbcReserved: Pointer; lpszDisplayName: POLESTR; out pchEaten: ULONG;out ppidl: PItemIDList; var dwAttributes: ULONG): HResult; stdcall;

  • lpszDisplayName — имя объекта, для которого надо найти PItemIDList;
  • pchEaten — возвращает символы, которые были правильно разобраны;
  • ppidl — как раз то, что нам нужно (сохраняем в FItemIDList);
  • dwAttributes — атрибуты для только что найденного FItemIDList.

Но здесь следует проявлять осторожность. Как вы помните, нам нужно вывести контекстное меню для C:WindowsNotePad.exe. Но сделать это прямо нельзя. Поэтому найдем сначала PItemIDList для папки C:Windows — контейнера нашего NotePad.exe. Пишем:

  • ShellFolder — значение, которое мы получили из SHGetDesktopFolder;
  • FEaten,FAtt — как я уже говорил, мне они не пригодились — но, чем черт не шутит, лучше их все-таки придержать;
  • FItemIDList — сохраняем, он нам еще понадобится.

После удачного завершения надо бы перейти к классу родителя нашего NotePad.exe. Воспользуемся для этого функцией IShellFolder.BindToObject, объявленной следующим образом:

  • pidl — наш FItemIDList;
  • riid — в этом случае указывает на экземпляр IID_IshellFolder;
  • ppvOut — указывает, куда нам его запихнут (скажем, ShellFolder1).

И после очередной строчки кода:

мы получим в переменной ShellFolder0 указатель на интерфейс IShellFolder, соответствующий папке C:Windows. Теперь мы можем узнать PItemIDList нашего NotePad:

Для чего все это было написано?

Теперь мы без зазрений совести можем приступать к выводу нашего контекстного меню:

Что тут написано. Во-первых — вызов интерфейса IcontextMenu, сопряженного с объектом FItemIDList папки ShellFolder0. Во-вторых, создание дескриптора пустого контекстного меню; заполнение контекстного QueryContextMenu; использование команды TrackPopupMenu для вывода контекстного меню в точку (100, 100).

Обработка результата команды TrackPopupMenu:

  • переменная Command типа LongBool преобразуется в тип Longint;
  • CommandStr — переменная, в которую заносится название команды;
  • DoCommandEvent — процедура обработки события;
  • структура ICI типа _CMINVOKECOMMANDINFO задает параметры, необходимые для запуска на исполнение кода, приписанного выбранному пункту меню по умолчанию;
  • InvokeCommand (ICI) — запуск кода по умолчанию.

Недоработки…

…а где их нет? То есть, конечно, этот компонент работает, я его использую, но в нем (пока) отсутствуют некоторые полезные функции. К примеру, если вы заглянете в файл ShlObj.pas, то обнаружите, что там, помимо использованного нами интерфейса IcontextMenu, объявлены также интерфейсы IContextMenu2 и IContextMenu3, которые используются для расширения базовых функций интерфейса (к примеру, IContextMenu2 используется для работы с элементами подменю). Кроме того, небольшая доработка компонента даст возможность включать в него свои собственные пункты меню (сравните рис. 3 и рис. 4).

Так что не стоит рассматривать эту статью как исчерпывающее руководство по Shell Extensions — она призвана всего лишь пробудить в вас аппетит к дальнейшим исследованиям.

Получения HWND оболочки Windows

Shell Extensions и как с ними бороться

Думаю, вы замечали, что некоторые программы добавляют собственные пункты в системное контекстное меню. Например, WinRAR добавляет «Сжать» и «Распаковать в…», ICQ — «Переслать пользователю» и пр. Механизм, с помощью которого ваш код «внедряется» в оболочку Windows, называется Shell Extensions — именно о нем пойдет речь…

Определения

В дальнейшем, говоря «программист среднего уровня», мы будем подразумевать человека, который уже написал свой первый текстовый редактор и знаком с практикой создания собственных компонент.

Shell Extensions — набор сервисных функций Windows API, призванный обеспечить расширение базовых функций оболочки Windows Explorer за счет наших надстроек. В числе основных функций Shell Extensions:

  • работа с системными контекстными меню;
  • работа с папками и объектами из пространства имен оболочки Windows (Мои Документы, Принтеры, Панель управления…);
  • использование механизма Drag&Drop;
  • создание и использование ярлыков.

Для реализации задуманного нам понадобятся интерфейсы IContextMenu и IShellFolder. Указатель на главный интерфейс IshellFolder, соответствующий «Рабочему столу» оболочки, можно получить, используя функцию SHGetDesktopFolder, объявление которой выглядит так:

Эта функция возвращает нам указатель на интерфейс IShellFolder, который возвращается в переменной ppshf. Далее допустим, что у нас в компоненте имеется поле под названием ShellObject типа String, в котором хранится путь к необходимому объекту (к примеру — C:WindowsNotePad.exe), и что нам нужно получить его контекстное меню (рис. 2). Для этого сначала используем метод из интерфейса IShellFolder:

  • hwndOwner — дескриптор родительского окна (0);
  • cidl — количество элементов, на которое указывает значение apidl (1);
  • apidl — параметр, представляющий собой уникальный идентификатор объекта;
  • riid — глобальный уникальный идентификатор системы Windows (IID_IContextMenu из файла ShlObj.pas);
  • prgfInOut — зарезервировано, должно быть nil;
  • ppvOut — переменная, которая получит указатель на «заказанный» интерфейс.

После использования этого оператора нам понадобится обратиться к функциям WinAPI — для работы с контекстными меню. Это, в первую очередь:

Синтаксис первой и последней функции, я думаю, понятен и без разъяснений. Функция TrackPopupMenu, собственно, и выводит на экран контекстное меню. Параметры этой функции принимают значения:

  • hMenu — дескриптор контекстного меню. Это тот самый Menu, который мы создали с помощью CreatePopupMenu;
  • uFlags — выравнивание относительно координат. Возможные значения: TPM_CENTERALIGN, TPM_LEFTALIGN, TPM_RIGHTALIGN, TPM_LEFTBUTTON, TPM_RIGHTBUTTON, TPM_RETURNCMD. Этот параметр используется для возврата команды (как будет показано ниже);
  • x, y — координаты, по которым будем «впрыгивать» наше меню;
  • nReserved — соответственно, приравниваем к нулю;
  • hWnd — дескриптор родительского окна;
  • prcRect — указатель на структуру TRect, которая задает «окно» в экранных координатах в пределах которого пользователь может щелкать без каких-либо исчезновений контекстного меню. Если = nil, то при нажатии мышкой за пределами контекстного меню оное исчезнет.

Возвращаемое значение показывает наличие команды или ее отсутствие. Если True — пользователь выбрал пункт; False — соответственно, не выбрал.

А теперь — самое главное

Ну что ж, сделали мы Menu — остается наполнить его содержимым, соответствующим нашему ShellObject. Для этого узнаем сначала его идентификатор (PItemIDList) — сделаем это при помощи метода ParseDisplayName из интерфейса IshellFolder. Этот метод объявлен следующим образом:

function ParseDisplayName (hwndOwner: HWND;pbcReserved: Pointer; lpszDisplayName: POLESTR; out pchEaten: ULONG;out ppidl: PItemIDList; var dwAttributes: ULONG): HResult; stdcall;

  • lpszDisplayName — имя объекта, для которого надо найти PItemIDList;
  • pchEaten — возвращает символы, которые были правильно разобраны;
  • ppidl — как раз то, что нам нужно (сохраняем в FItemIDList);
  • dwAttributes — атрибуты для только что найденного FItemIDList.

Но здесь следует проявлять осторожность. Как вы помните, нам нужно вывести контекстное меню для C:WindowsNotePad.exe. Но сделать это прямо нельзя. Поэтому найдем сначала PItemIDList для папки C:Windows — контейнера нашего NotePad.exe. Пишем:

  • ShellFolder — значение, которое мы получили из SHGetDesktopFolder;
  • FEaten,FAtt — как я уже говорил, мне они не пригодились — но, чем черт не шутит, лучше их все-таки придержать;
  • FItemIDList — сохраняем, он нам еще понадобится.
Читать еще:  Компьютер месяца — декабрь 2019 года

После удачного завершения надо бы перейти к классу родителя нашего NotePad.exe. Воспользуемся для этого функцией IShellFolder.BindToObject, объявленной следующим образом:

  • pidl — наш FItemIDList;
  • riid — в этом случае указывает на экземпляр IID_IshellFolder;
  • ppvOut — указывает, куда нам его запихнут (скажем, ShellFolder1).

И после очередной строчки кода:

мы получим в переменной ShellFolder0 указатель на интерфейс IShellFolder, соответствующий папке C:Windows. Теперь мы можем узнать PItemIDList нашего NotePad:

Для чего все это было написано?

Теперь мы без зазрений совести можем приступать к выводу нашего контекстного меню:

Что тут написано. Во-первых — вызов интерфейса IcontextMenu, сопряженного с объектом FItemIDList папки ShellFolder0. Во-вторых, создание дескриптора пустого контекстного меню; заполнение контекстного QueryContextMenu; использование команды TrackPopupMenu для вывода контекстного меню в точку (100, 100).

Обработка результата команды TrackPopupMenu:

  • переменная Command типа LongBool преобразуется в тип Longint;
  • CommandStr — переменная, в которую заносится название команды;
  • DoCommandEvent — процедура обработки события;
  • структура ICI типа _CMINVOKECOMMANDINFO задает параметры, необходимые для запуска на исполнение кода, приписанного выбранному пункту меню по умолчанию;
  • InvokeCommand (ICI) — запуск кода по умолчанию.

Недоработки…

…а где их нет? То есть, конечно, этот компонент работает, я его использую, но в нем (пока) отсутствуют некоторые полезные функции. К примеру, если вы заглянете в файл ShlObj.pas, то обнаружите, что там, помимо использованного нами интерфейса IcontextMenu, объявлены также интерфейсы IContextMenu2 и IContextMenu3, которые используются для расширения базовых функций интерфейса (к примеру, IContextMenu2 используется для работы с элементами подменю). Кроме того, небольшая доработка компонента даст возможность включать в него свои собственные пункты меню (сравните рис. 3 и рис. 4).

Так что не стоит рассматривать эту статью как исчерпывающее руководство по Shell Extensions — она призвана всего лишь пробудить в вас аппетит к дальнейшим исследованиям.

Получения HWND оболочки Windows

Перевод А. И. Легалова

Англоязычный оригинал находится на сервере компании Reliable Software

Программировать с использованием COM настолько трудно, что Вы не должны даже пробовать это без MFC. Правильно или неправильно? Абсолютная чушь! Рекламируемые OLE и его преемник COM имеют элегантность гиппопотама, занимающегося фигурным катанием. Но размещение MFC на вершине COM подобно одеванию гиппопотама в клоунский костюм еще больших размеров.

Итак, что делать программисту, когда он столкнется с потребностью использовать возможности оболочки Windows, которые являются доступными только через интерфейсы COM? Читайте .

Для начала, всякий раз, когда Вы планируете использовать COM, Вы должны сообщить система, чтобы она инициализировала COM подсистему. Точно так же всякий раз, когда вы заканчиваете работу, Вы должны сообщить системе, чтобы она выгрузила COM. Самая простой способ это сделать заключается в определении объекта, конструктор которого инициализирует COM, а деструктор выгрожает ее. Самое лучшее место, для недрения данного механизма — это объект Controller (см. Windows программу Generic), подобный этому.

Этот способ гарантируют, что COM подсистема будет проинициализирована прежде, чем к ней будут сделаны любые обращения и что она будет освобождена после того, как программа осуществит свои разрушения (то есть, после того, как «Вид» и «Модель» будут разрушены).

Класс UseCom очень прост.

Пока не было слишком трудно, не так ли? Дело в том, что мы не коснулись главной мерзости COM программирования — подсчета ссылок. Вам должно быть известно, что каждый раз, когда что Вы получаете интерфейс, его счетчик ссылок увеличивается. И Вам необходимо явно уменьшать его. И это становится более чем ужастным тогда, когда Вы начинаете запрашивать интерфейсы, копировать их, передавать другим и т.д. Но ловите момент: мы знаем, как управляться с такими проблемами! Это называется управлением ресурсами. Мы никогда не должны касаться интерфейсов COM без инкапсуляции их в интеллектуальных указателях на интерфейсы. Ниже показано, как это работает.

Примечание. В настоящий момент данная тема достаточно широко рассмотрена в литературе, переведенной на русский язык. В частности, можно отметить книги:

  • Джефф Элджер. «C++: библиотека программиста»;
  • Скотт Мейерс. «Эффективное программирование на С++».

Не волнуйте, что этот класс выглядит непригодным (потому что имеет защищенный конструктор). Мы никогда не будем использовать его непосредственно. Мы наследуем от него. Между прочим, это удобный прием: создайте класс с защищенным пустым конструктором, и осуществите наследование от него. Все наследующие классы должны обеспечивать их внутреннюю реализацию своими открытыми конструкторами. Поскольку Вы можете иметь различные классы, наследующие от SIfacePtr, они будут отличаться по способу получения, по их конструкторам, рассматриваемым интерфейсам.

Закрытые фиктивный копирующий конструктор и оператор «=» не всегда необходимы, но они сохранят Вам время, потраченное на отладку, если по ошибке вы передадите интеллектуальный интерфейс по значению вместо передачи по ссылке. Это больше невозможно. Вы можете освободить один и тот же интерфейс, дважды и это будет круто отражаться на подсчете ссылок COM. Верьте мне, у меня это случалось. Как это часто бывает, компилятор откажется передавать по значению объект, который имеет закрытую копию конструктора. Он также выдаст ошибку, когда Вы пробуете присвоить объект, который имеет закрытый оператор присваивания.

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

Обратите внимание на использование ранее показанного приема: класс SShellPtr непосредственно не пригоден для использования. Вы должны наследовать от него подкласс и реализовать в нем соответствующий конструктор.

Обратите также внимание, что я не уверен, может ли _shellMalloc быть статическим элементом SShellPtr. Проблема состоит в том, что статические элементы инициализируются перед WinMain. Из-за этого вся COM система может оказаться неустойчивой. С другой стороны, документация говорит, что Вы можете безопасно вызывать из другой API функции CoGetMalloc перед обращением к CoInitialize. Это не говорит о том, может ли SHGetMalloc, который делает почти то же самое, также вызываться в любое время в вашей программе. Подобно многим других случаям, когда система ужасно разработана или задокументирована, только эксперимент может ответить на такие вопросы. Добро пожаловать к нам с такими ответами.

Между прочим, если Вы нуждаетесь в интеллектуальном указателе, который использует специфическое распределение памяти для COM, то получите его, вызывая CoGetMalloc. Вы можете без опаски сделать этот _malloc статическим элементом и инициализировать его только один раз в вашей программе (ниже SComMalloc:: GetMalloc тоже статический):

Это — все, что надо знать, чтобы начать использовать оболочку Windows и ее COM интерфейсы. Ниже приводится пример. Оболочка Windows имеет понятие Рабочего стола, являющегося корнем «файловой» системы. Вы обращали внимание, как Windows приложения допускают пользователя, просматривают файловую систему, начинающуюся на рабочем столе? Этим способом Вы можете, например, создавать файлы непосредственно на вашем рабочем столе, двигаться между дисководами, просматривать сетевой дисковод, и т.д. Это, в действительности, Распределенная Файловая система (PMDFS — poor man’s Distributed File System) ограниченного человека (?). Как ваше приложение может получить доступ к PMDFS? Просто. В качестве примера напишем код, который позволит пользователю, выбирать папку, просматривая PMDFS. Все, что мы должны сделать — это овладеть рабочим столом, позиционироваться относительно его, запустить встроенное окно просмотра и сформировать путь, который выбрал пользователь.

Давайте, запустим объект desktop . Он использует интерфейс по имени IShellFolder. Обратите внимание, как мы приходим к Первому Правилу Захвата. Мы распределяем ресурсы в конструкторе, вызывая функцию API SHGetDesktopFolder. Интеллектуальный указатель интерфейса будет заботиться об управлении ресурсами (подсчет ссылок).

Как только мы получили рабочий стол, мы должны создать специальный вид пути, который используется PMDFS. Класс ShPath инкапсулирует этот «путь». Он создан из правильного Unicode пути (используйте mbstowcs, чтобы преобразовать путь ASCII в Unicode: int mbstowcs(wchar_t *wchar, const char *mbchar, size_t count)). Результат преобразования — обобщенный путь относительно рабочего стола. Обратите внимание, что память для нового пути распределена оболочкой — мы инкапсулируем это в SShellPtr, чтобы быть уверенными в правильном освобождении.

Этот путь оболочки станет корнем, из которого окно просмотра начнет его взаимодействие с пользователем.

С точки зрения клиентского кода, окно просмотра — путь, выбранный пользователем. Именно поэтому он наследуется от SShellPtr .

Между прочим, ITEMIDLIST — официальное имя для этого обобщенного пути. Это — список, и я не назвал его, SHITEMID’ом. Впредь я буду воздерживаться от любых дальнейших комментариев или шуток относительно соглашений, связанных с именами, используемыми в оболочке.

Первая программа на WinAPI

WinAPI или Windows API (Application Programming Interface) — это библиотека для создания классических приложений Windows. Сама библиотека WinAPI написана на языке C и представляет собой коллекцию функций, структур и констант. Она объявлена в определённых заголовочных файлах и реализована в статических (.lib) и динамических (.dll) библиотеках.

В данном уроке мы создадим нашу первую программу на WinAPI. Для начала создайте проект. Выберите меню File -> New -> Project:

В открытом окне в левой панели выберите Other, затем Empty Project (пустой проект). Там же доступен шаблон Windows Desktop Application, но мы напишем программу с нуля, так как шаблон по умолчанию пока слишком сложен для нас. В нижней части выберите имя проекта, его местоположение и хотите ли вы создать решение для него. Местоположение может быть любым, для меня это C:progcpp

В обозревателе решений щёлкните правой кнопкой мышки и выберите Add -> New Item.

В открывшемся окне выберите C++ File (.cpp) и введите имя файла в нижней части — main.cpp.

Перед тем как мы начнём рассматривать код, давайте поговорим о типах данных в WinAPI и соглашениях вызова (calling conventions).

Типы данных в WinAPI

WinAPI переопределяет множество стандартных типов языка C. Некоторые переопределения зависят от платформы для которой создаётся программа. Например, тип LRESULT, если его скомпилировать для x86, будет типом long. Но если скомпилировать программу для x64, то LRESULT будет типом __int64. Вот так LRESULT определяется на самом деле (он зависит от LONG_PTR, а LONG_PTR может уже быть или __int64, или long):

Читать еще:  Универсальный накопитель Minds@Work MindStor

Соглашения по вызову (Calling Conventions) — __stdcall

В коде ниже перед именами функций вы встретите __stdcall. Это одно из соглашений по вызову функций. Соглашение по вызову функций определяет каким образом аргументы будут добавляться в стек. Для __stdcall аргументы помещаются в стек в обратном порядке — справа налево. Также, __stdcall говорит, что после того как функция завершится, она сама (а не вызывающая функция) удалит свои аргументы из стека. Все функции WinAPI используют __stdcall соглашение.

WinAPI переопределяет __stdcall в WINAPI, CALLBACK или APIENTRY, которые используются в разных ситуациях. Поэтому в примерах из MSDN вы не увидите __stdcall, но нужно помнить что именно оно будет использоваться.

Типы WinAPI пишутся в верхнем регистре.

Описатели/дескрипторы (Handles) в WinAPI

Handle на русский язык сложно перевести однозначно. Наверное, наиболее частое употребление в русском имеет слово дескриптор. По сути это ссылка на ресурс в памяти. Например, вы создаёте окно. Это окно хранится в памяти и оно имеет запись в таблице, которая хранит указатели на все созданные системные ресурсы: окна, шрифты, файлы, картинки. Указатель на ваше окно в данной таблице называется дескриптором окна (handle of the window).

Любой указатель это просто переопределение типа void*. Примеры дескрипторных типов в WinAPI: HWND, HINSTANCE, HBITMAP, HCURSOR, HFILE, HMENU.

Подытожим: дескрипторы используются для получения доступа к каким-либо системным ресурсам.

WinAPI окна

Давайте посмотрим на код самой простой WinAPI программы:

Вначале нужно добавить WinAPI: статичную библиотеку, которая содержит определения различных функций и включить заголовочный файл с объявлениями этих функций, структур и констант. user32.lib содержит основные возможности Windows — всё, что касается окон и обработки событий.

На следующей строке мы объявляем функцию обратного вызова (callback), которая будет вызываться, когда наше приложение получает какое-либо сообщение от операционной системы. Мы вернёмся к этому ниже.

Функция WinMain

WinMain — точка входа в программу, а как мы помним такая функция вызывается операционной системой.

Главная функция приложений под Windows отличается от консольной версии. Она возвращает целое число и это всегда ноль. __sdtcall говорит, что аргументы добавляются в стек в обратном порядке и WinMain сама удаляет их из стека по завершении. WinMain принимает 4 аргумента:

hInstance — дескриптор экземпляра приложения. Можете думать о нём, как о представлении вашего приложения в памяти. Он используется для создания окон.

Второй аргумент — наследие шестнадцатибитных версий Windows. Уже давно не используется.

Третий аргумент представляет аргументы командной строки. Пока мы не будем им пользоваться.

nCmdShow — специальный флаг, который можно использовать при создании окон. Он говорит о состоянии окна: должно ли оно показываться нормально, на полный экран или быть свёрнутым.

Теперь давайте посмотрим как создаются окна.

Классы окон (Window Classes)

Для создания окна нужно определить и зарегистрировать его класс. Windows создаёт свои классы таким же образом. Все стандартные элементы, которые вы видите в Windows являются классами: кнопки, поля редактирования, полосы прокрутки. Windows хранит список всех зарегистрированных классов. Обязательно нужно заполнить только три поля класса: имя класса, дескриптор экземпляра приложения (передаётся в WinAPI в виде параметра) и оконная процедура (адрес функции).

Сначала нужно заполнить структуру WNDCLASS. Пусть вас не смущает название WNDCLASS — это не C++ класс. В данном случае, класс — всего лишь термин используемый в WinAPI:

Здесь мы инициализируем структуру WNDCLASS нулями, определяем обязательные поля и регистрируем класс.

lpfnWndProc имеет тип WNDPROC. Как говорилось выше, это указатель на функцию WindowProc, которую мы объявили в самом начале. У каждого оконного класса должна быть своя оконная процедура.

hInstance — дескриптор экземпляра приложения. Все оконные классы должны сообщать, какое приложение их зарегистрировало. Мы используем первый параметр функции WinMain.

lpszClassName — имя класса, задаётся пользователем. В Windows все классы называются в верхнем регистре (примеры: BUTTON, EDIT, LISTBOX), мы будем делать также в наших уроках.

WNDCLASS содержит больше полей: стиль, иконка, имя меню, но мы можем пропустить их. Некоторые из них мы рассмотрим в следующих уроках. Вы можете посмотреть полный список в документации к WinAPI на MSDN (официальном сайте Microsoft с документацией).

В конце мы регистрируем наш класс с помощью функции RegisterClass. Мы передаём адрес структуры WNDCLASS. Теперь мы можем создать окно.

Первая WinAPI программа — Пустое окно

В WinAPI есть функция для создания окон — CreateWindow:

Первый параметр — имя класса. В данном случае он совпадает с именем класса, который мы зарегистрировали. Второй — имя окна, это та строка, которую пользователи программы будут видеть в заголовке. Следующий — стиль. WS_OVERLAPPEDWINDOW говорит, что WinAPI окно имеет заголовок (caption), кнопки сворачивания и разворачивания, системное меню и рамку.

Четыре числа определяют позицию левого верхнего угла окна и ширину/высоту.

Затем идут два указателя nullptr. Первый — дескриптор родительского окна, второй — меню. У нашего окна нет ни того, ни другого.

hInstance — дескриптор на экземпляр приложения, с которым связано окно.

В последний аргумент мы передаём nullptr. Он используется для специальных случаев — MDI (Multiple Document Interface ) — окно в окне.

CreateWindow возвращает дескриптор окна. Мы можем использовать его для обращения к окну в коде. Теперь мы можем показать и обновить окно.:

ShowWindow (показать окно) использует параметр nCmdShow функции WinMain для контроля начального состояния (развёрнуто на весь экран, минимизировано, обычный размер). UpdateWindow (обновить окно) мы обсудим в следующих уроках.

Главный цикл WinAPI

Далее идёт бесконечный цикл. В этом цикле мы будем реагировать на разные события, возникающие при взаимодействии пользователя с нашей программой.

Перед циклом мы объявляем переменную типа MSG (от message — сообщение) — в этой структуре Windows кодирует события. Если произошло событие WM_QUIT, то мы завершаем цикл.

Операционная система генерирует множество различных сообщений. Они генерируются, когда происходит какое-то событие: изменился размер окна, нажата клавиша мышки или клавиатуры.

Очередь сообщений (Message Queue) в WinAPI

Все оконные приложения управляются событиями (event-driven). В операционной системе существует очередь сообщений. Когда происходит какое-либо событие в любой программе, в эту очередь посылается сообщение. Также, любая программа имеет свою очередь сообщений. Windows проверяет каждое сообщение в системной очереди и посылает их в программные очереди. В действительности всё немного сложнее, так как очереди сообщений связаны с потоками, но мы обсудим это позже. На данный момент запомните, что каждое приложение имеет свою собственную очередь сообщений.

Сообщение это просто структурная переменная MSG. Давайте посмотрим на определение этой структуры:

hwnd — дескриптор окна, которому принадлежит сообщение.

message — идентификатор сообщения (UINT — unsigned integer).

wParam и lParam содержат дополнительную информацию и зависят от идентификатора сообщения.

time — понятно из названия — время, когда было создано сообщение.

pt — позиция курсора на экране в момент генерации сообщения. POINT — тип WinAPI для описания точек с координатами (x,y).

Приложение в бесконечном цикле проверяет свою очередь сообщений, смотрит на свойство message и решает, что делать с данным сообщением (как его обработать).

Функция PeekMessage проверяет очередь и берёт последнее сообщение. Затем, она берёт информацию о сообщении и помещает её в переменную msg. Последний аргумент заставляет удалить сообщение из очереди.Итак, в условии мы проверяем, содержит ли очередь сообщений приложения какое-либо сообщение. Если содержит, мы заполняем переменную msg и удаляем сообщение из очереди. Затем вызываем две функции.

TranslateMessage — генерирует дополнительное сообщение если произошёл ввод с клавиатуры (клавиша с символом была нажата или отпущена). По умолчанию, клавиатура генерирует так называемые сообщения виртуального ключа (virtual key). TranslateMessage генерирует ещё одно сообщение, которое сообщает информацию о символе. Мы поговорим об этом позже. Пока что: вызов TranslationMessage нужен, чтобы обработать ввод символов с клавиатуры.

Функция DispatchMessage посылает сообщение в функцию WindowProc.

Оконная процедура (Window Procedure) WindowProc

Оконная процедура — это специальная функция в которой мы обрабатываем сообщения. В данный момент нам нужно обработать только одно важное сообщение, а для всех остальных выполнить стандартное действие. Давайте посмотрим на наш вариант WindowProc:

Обратите внимание, что мы сами нигде не вызываем WindowProc. Оконная процедура привязана к классу окна. И когда мы вызываем DispatchMessage, система сама вызывает оконную процедуру, связанную с окном. Такие функции называются функциями обратного вызова (callback functions) — они не вызываются напрямую. Также обратите внимание, что данная функция получает только часть свойств MSG структуры.

Внутри WindowProc мы проверяем поле message структуры MSG. Оно содержит идентификатор сообщения. В Windows определено много констант для разных сообщений. В данной программе мы проверяем WM_DESTROY. Это сообщение посылается, когда окно уничтожается, в момент, когда оно уже удалено с экрана. В ответ на это сообщение мы вызываем PostQuitMessage — она говорит системе, что приложение будет закрыто и посылает сообщение WM_QUIT в очередь сообщений программы (не в системную).

Если пришло сообщение, которое мы не хотим обрабатывать сами, мы передаём его в DefWindowProc — действие по умолчанию.

Заключение

В данном уроке мы создали пустое но полностью функциональное стандартное окно операционной системы Windows. В следующих уроках мы обсудим разные части WinAPI, а шаблон из данного урока станет основой для наших программ на DirectX и OpenGL.

Пространство имён оболочки Windows

Введение

В операционных системах компании Microsoft с 1995 года используется новая оболочка, построенная на основе компонентной объектной модели. Одним из нововведений оболочки операционной системы стало понятие пространства имён оболочки. Пространство имён оболочки являет собой иерархически упорядоченный мир объектов, известных операционной системе, с их свойствами и предоставляемыми действиями. Оно во многом сходно со структурой файловой системы, но включает в себя не только файлы и каталоги. Такие понятия файловой системы, как имя файла и путь, заменены более универсальными.

Читать еще:  «Яндекс.Диск»: синхронизация — это модно!

Основное пространство имён начинается с корневого объекта «Рабочий стол», и его легко исследовать, запустив приложение «Проводник». Параллельно основному пространству имён могут сосуществовать множество дополнительных пространств имён, о которых подробнее будет рассказано позднее.

Основные понятия

Пространство имён (Shell namespace) является древовидной структурой, состоящей из COM-объектов. Объекты, владеющие дочерними объектами, именуются папками (Shell folder), причём среди таковых могут оказаться и другие папки (Subfolders). Объекты, не владеющие дочерними объектами, именуются файловыми объектами (file objects), причём файловым объектом может представлять собой не только файл файловой системы, но и принтер, компонент «Панели Управления» или объект другого типа. Каждый объект имеет идентификатор элемента (Item identifier), однозначно определяющий его расположение в папке. Таким образом, чтобы указать на некий объект в данной папке, нам потребуется лишь передать его идентификатор. Если же мы хотим указать на некий объект в известном пространстве имён, тогда нам придётся указать идентификаторы всех папок, начиная с корня, и до самого объекта включительно. В качестве примера приведём аналогию из файловой системы:

«C:Мои документыДоклад о возможных способах реализации интерфейса к корпоративной БД.doc» уникально представит файл относительно файловой системы известного (моего домашнего) компьютера.

То, что в файловой системе именуется путём к файлу, в пространстве имён именуется списком идентификаторов (Identifier List).

Объекты-папки знают о тех обьектах, которыми они владеют, и о тех операциях, которые с ними возможны. Папки предоставляют нам механизм для перечисления всех объектов, которыми данный объект-папка владеет — интерфейс IShellFolder. Получение от объекта указателя на данный интерфейс называется привязкой (Binding).

Большая часть объектов основного пространства имён оболочки являются объектами, представляющими часть файловой системы. Те же объекты, что не представлены в файловой системе, называются виртуальными. Такие виртуальные папки, как папки рабочего стола (Desktop), «Мой Компьютер» (My Computer) и «Сетевое окружение» (Network Neighborhood), позволяют реализовать унифицированное пространство имён.

Каталоги файловой системы, используемые оболочкой в особых целях, называются специальными. Одной из таких папок, например, является папка «Программы» (Programs). Местонахождение специальных папок файловой системы указывается в подразделе ветви HKEY_CURRENT_USER/Software/Microsoft/Windows/CurrentVersion/Explorer/Shell Folders/ .

Идентификаторы элементов

Идентификатор элемента является уникальным для той папки, в которой данный элемент (объект пространства имён оболочки) находится, и является двоичной структурой переменного размера, чей формат определяется тем программным обеспечением, которое поддерживает существование папки, владеющей определяемым данным идентификатором объектом. Идентификатор элемента имеет смысл только в контексте той папки, которая его сконструировала.

Идентификатор элемента описывается структурой SHITEMID, для которой определено лишь значение первого поля — размер данной структуры.

Список идентификаторов, уникально идентифицирующих объект в определённом пространстве имён, эквивалентен понятию пути для файловой системы, и определяется как список из последовательно расположенных идентификаторов, за которыми следует завершающее список 16-битное значение 0x0000 (ITEMIDLIST). Список идентификаторов может быть как абсолютным, то есть определяющим положение объекта относительно корневой папки, так и относительным, то есть определяющим положение элемента относительно какой-либо конкретной папки.

Приложение оперирует понятием указателя на список идентификаторов (Pointer to an Identifier List), который кратко именуют как PIDL-указатель. Все глобальные методы (утилиты) оболочки, принимающие в качестве одного из параметров PIDL-указатель, ожидают его в абсолютном формате. В то же время все методы интерфейса IShellFolder, принимающие в качестве одного из параметров PIDL-указатель, ожидают его в относительном формате (если только в описании метода не указано иначе).

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

За размещение списков идентификаторов отвечает распределитель памяти оболочки (Shell’s allocator), предоставляющий интерфейс IMalloc. Указатель на данный интерфейс распределителя памяти оболочки можно получить через метод SHGetMalloc.

Таким образом, если Ваше приложение получает от оболочки PIDL-указатель, то оно становится ответственным за обязательное в дальнейшем освобождение этого списка с помощью распределителя памяти оболочки.

Ниже представлен пример копирования списка идентификаторов:

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

Интерфейс IShellFolder предоставляет метод CompareIDs для определения расположения двух идентификаторов относительно друг друга (выше, ниже или равны) в данной папке. При этом параметр lParam определяет критерий упорядочивания, но заранее определённым для всех объектов-папок является только сортировка по имени (значение 0 ). Если вызов этого метода завершён успешно, то поле CODE возвращаемого значения содержит ноль при равенстве объектов, отрицательно, если первое меньше второго, и положительно в обратном случае.

Местонахождение объектов-папок

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

Концепция альтернативной оболочки Windows. Часть 1.

Предположим, Вы приобрели новый компьютер с предустановленной ОС Windows. После загрузки системы и входа в профиль пользователя Вас приветствует Рабочий стол с несколькими иконками и полоска Панели Задач в нижней части экрана — там тоже несколько иконок. После настройки Интернета Вы начинаете скачивать программки — большие и маленькие. После запуска инсталляторов для каждой из них на Рабочем столе (а иногда и на Панели Быстрого запуска) появляются новые ярлычки. Вы начинаете работать с программами, и, возможно, часть созданных документов сохраняете на Рабочий стол.

При сильной захламленности Рабочего стола Вас подстерегает ещё одна неприятность. Для создания нового документа нужно активировать ярлык приложения, а для изменения документа нужно дважды кликнуть по иконке документа. Но ярлыки приложений и иконки документов на Рабочем столе перемешаны, и поиск нужного объекта требует некоторого напряжения. Можно, конечно, перетащить все ярлыки приложений в левую (правую, верхнюю или нижнюю) часть Рабочего стола, но не факт, что при очередной перезагрузке этот порядок сохранится.

реклама

Рассмотрим типовые решения проблемы нехватки места на Рабочем столе.

реклама

Можно поступить более радикально — удалить часть объектов в корзину.

реклама

Как временное решение, с помощью контекстного меню Рабочего стола можно изменить размер значков с обычных на маленькие.

реклама

Это, пожалуй, все варианты, которые предлагает ОС.

Что могло бы облегчить работу на компьютере в данной ситуации?

Если бы на Рабочем столе была полоса прокрутки, то часть иконок можно было бы поместить в невидимую область экрана.

Или, скажем, если бы ОС предоставляла возможность иметь в своём распоряжении несколько полноценных Рабочих столов, то можно было бы перераспределить иконки между ними.

Кнопочки на Панели Быстрого Запуска надо тоже как-то поделить. Скажем, иметь несколько Панелей Быстрого Запуска — по одной на каждый Рабочий стол.

Итак, обозначим список наших пожеланий.

  1. Мы хотим, чтобы было много Рабочих столов.
  2. Мы хотим, чтобы на каждом Рабочем столе при необходимости была прокрутка.
  3. Мы хотим, чтобы ярлыки приложений располагались отдельно от иконок документов и папок с документами.
  4. Мы хотим, чтобы для каждого Рабочего стола была своя Панель Быстрого Запуска.
  5. Мы хотим, чтобы текст подписи под значками не усекался, а отображался полностью, в удобоваримом виде.

Как эти требования выполняются в Программном комплексе «Пользовательская оболочка» (в дальнейшем — ПКПО)?

В ПКПО есть аналог Рабочего стола, называемый Составным проектом. Пользователь может создать практически неограниченное количество Составных проектов. Все Составные проекты образуют древовидную структуру и, естественно, у каждого Составного проекта есть имя. Кроме того, Вы можете сразу переходить из одного Составного проекта в другой сразу, вне зависимости от места в Дереве Проектов, если создадите объект «Родственный проект».

Составной проект, с которым в данное время работает пользователь, отображается в специальной панели — Окне Просмотра. Слева от Окна просмотра располагается Панель Кэша Приложений, куда пользователь помещает ярлыки приложений, которые могут понадобиться в текущем Составном проекте. Если в любой из панелей содержится много объектов, автоматически появляется прокрутка. Граница между панелью Кэша Приложений и Окном просмотра изменяемая. Объекты в Окне Просмотра сортируются по одному из выбранных критериев, но сначала отображаются папки, т. е. вложенные Составные проекты (см. Рисунок 1).

В каждой из панелей «Пользовательской оболочки» есть несколько вариантов отображения иконок и подписей к ним, в том числе, без усечения текста.

Рисунок 1. Окно Просмотра и Кэш Приложений

А как насчёт Панели Быстрого Запуска?

Этот элемент интерфейса формируется с помощью другого приложения ПКПО — Пользовательской панели, аналога Панели Задач Windows (см. Рисунок 2). Панель Кэша Приложений (см. Рисунок 2) — это и есть аналог Панели Быстрого Запуска Windows.

Рисунок 2: Пользовательская панель

Таким образом, во время работы над текущим Составным проектом мы можем запустить нужное приложение, нажав кнопку на Панели Кэша Приложений.

Если с помощью средств навигации Окна Просмотра мы переходим в другой Составной проект, содержимое панелей Кэша Приложений обоих приложений полностью обновляется.

Поскольку Кэш Приложений отображается сразу в двух приложениях, Вы можете скрыть панель Кэша Приложений в «Пользовательской оболочке», тем самым увеличивая размер Окна Просмотра (см. Рисунок 1).

У некоторых пользователей может возникнуть возражение — некоторые приложения нужны в нескольких, или даже во всех Составных проектах, например, веб-браузер или пакет офисных программ. Что делать?

Эта проблема так же легко решается с помощью ПКПО.

Для ярлыков приложений, которые нужны во всех Составных проектах, выделяется специальная папка — Список Приложений, формируемая пользователем вручную, по собственному усмотрению. Ярлыки можно группировать в папки. В приложении «Пользовательская оболочка» Список Приложений при необходимости отображается в Правой панели (см. Рисунок 3).

Рисунок 3. Список Приложений в Правой панели

В приложении «Пользовательская панель» Список Приложений отображается в виде панели с кнопками — по одной кнопке на каждый ярлык приложения и каждую папку в корне Списка Приложений. Вложенные в папки Списка Приложений объекты отображаются в виде меню (см. Рисунок 2).

  1. Если иконки не помещаются на какой-либо панели приложения «Пользовательская панель», появляются кнопки для прокрутки (см. кнопку прокрутки Списка Приложений на Рисунке 2).
  2. Панель Кэша Приложений «Пользовательской оболочки» может работать и в режиме отображения Дерева папок (режим по умолчанию), подобно окну Проводника:

Рисунок 4: Панель Кэша Приложений в виде дерева

Дополнительную информацию по ПКПО можно найти на странице программы.

Ссылка на основную публикацию
Статьи c упоминанием слов:
Adblock
detector