Winapi не запутайтесь в типах


Содержание

Основы программирования для Win32 API

Материал рассматривается на примере пакета Borland C++ 5.5 command line tools

Стартовая функция WinMain

Программа на Си для Windows, как и для любой другой платформы, должна обязательно содержать некоторую «стартовую» функцию, которой передается управление при запуске программы. Вообще говоря, имя такой «стартовой» функции может различаться в различных компиляторах, но исторически сложилось так (а, кроме того, имеются еще и стандарты ANSI и ISO, к которым, правда, производители коммерческих компиляторов типа Microsoft и Borland/Inprise относятся без особого трепета), что такой функцией является: У этой функции может быть до трех параметров:

  • argc — количество параметров в командной строке (включая имя программы),
  • argv — массив строк-параметров (argv[0] — имя программы),
  • env — массив строк-переменных окружения.

Многие компиляторы для Windows «понимают» такую стартовую функцию. Однако при этом они создают хотя и 32-битное, но консольное приложение. Пример 1 (example1.cpp): Компилируем: Запускаем:

При использовании стандартных библиотек (stdio, stdlib и т. п.) вам не потребуется никаких лишних телодвижений по сравнению с обычными методами написания программ на Си. Если же ваша цель — 32-битное приложение с графическим интерфейсом, то черное консольное окошко будет вас раздражать. Пример (example2.cpp):

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

  • hInst — дескриптор для данного экземпляра программы,
  • hpi — в Win32 не используется (всегда NULL),
  • cmdline — командная строка,
  • ss — код состояния главного окна.

Пример (example3.cpp):

Кроме того, компилятору и компоновщику нужно сообщить о том, что вы делаете графическое приложение. Для bcc32 это опция -tW:

Если же вы соскучитесь по черному консольному окошку, его можно в любой момент создать при помощи вызова Win32 API

О типах, о функциях

Как известно, в Си есть лишь три базовых типа ( char , int , float/double ) и еще несколько их вариаций с модификаторами signed/unsigned , short/long . Однако фирме Microsoft зачем-то понадобилось описывать функции Win32 API с помощью переопределенных типов:

Кроме перечисленных простых типов, практически ни один вызов Win32 API не обходится без «штучек» с «ручками» — переменных типа handle («ручка»), которые идентифицируют некоторый объект («штучку»). Такие «ручки» принято называть дескрипторами. Реально такая переменная представляет собой всего лишь указатель на некоторую системную структуру или индекс в некоторой системной таблице.

При заполнении различных структур часто требуется указать такую «ручку» от какой-нибудь «штучки». Очень часто вместо конкретного дескриптора допустимо передавать значение NULL , означающее, что вы еще не обзавелись такой «штучкой» или собираетесь использовать «штучку» по умолчанию.

В стандартных версиях Си для функций используются два варианта соглашения о передаче параметров: соглашение языка Си (параметры функции помещаются в стек в порядке обратном их описанию, очистку стека производит вызывающая процедура) и соглашение языка Паскаль (параметры функции помещаются в стек в (прямом) порядке их описания, очистку стека производит вызываемая процедура). Для этих соглашений использовались, соответственно, модификаторы cdecl и pascal . При описании функций Win32 API используется модификатор WINAPI , а для описания пользовательских функций обратного вызова — модификатор CALLBACK . Оба этих модификатора являются переопределением специального модификатора _stdcall , соответствующего соглашению о передаче параметров, использующегося исключительно в Win32 API, — Standard Calling Convention (параметры функции помещаются в стек в порядке обратном их описанию, очистку стека производит вызываемая процедура).

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

Окно приложения может содержать строку заголовка title bar (1), строку меню menu bar (2), системное меню system menu (3), кнопку сворачивания окна minimize box (4), кнопку разворачивания окна maximize box (5), рамку изменения размеров sizing border (6), клиентскую область client area (7), горизонтальную и вертикальную полосы прокрутки scroll bars (8):

Меню, строка заголовка с системными кнопками, системное меню, рамка изменения размеров и полосы прокрутки относятся к области окна, называемой неклиентской областью (non-client area). С неклиентской областью Windows справляется сама, а вот за содержимое и обслуживание клиентской области отвечает приложение.

Кроме главного окна, приложение может использовать еще и другие типы окон: управляющие элементы (controls), диалоговые окна (dialog boxes), окна-сообщения (message boxes). Управляющий элемент — окно, непосредственно обеспечивающее тот или иной способ ввода информации пользователем. К управляющим элементам относятся: кнопки, поля ввода, списки, полосы прокрутки и т.п. Управляющие элементы обычно не болтаются сами по себе, а проживают в каком-либо диалоговом окне. Диалоговое окно — это временное окно, напичканное управляющими элементами, обычно использующееся для получения дополнительной информации от пользователя. Диалоговые окна бывают модальные (modal) и немодальные (modeless). Модальное диалоговое окно требует, чтобы пользователь обязательно ввел обозначенную в окне информацию и закрыл окно прежде, чем приложение продолжит работу. Немодальное диалоговое окно позволяет пользователю, не закрывая диалогового окна, переключаться на другие окна этого приложения. Окно-сообщение — это диалоговое окно предопределенного системой формата, предназначенное для вывода небольшого текстового сообщения с одной или несколькими кнопками. Пример такого окна показан в Примере 3.

В отличие от традиционного программирования на основе линейных алгоритмов, программы для Windows строятся по принципам событийно-управляемого программирования (event-driven programming) — стиля программирования, при котором поведение компонента системы определяется набором возможных внешних событий и ответных реакций компонента на них. Такими компонентами в Windows являются окна. С каждым окном в Windows связана определенная функция обработки событий. События для окон называются сообщениями. Сообщение относится к тому или иному типу, идентифицируемому определенным кодом (32-битным целым числом), и сопровождается парой 32-битных параметров ( WPARAM и LPARAM ), интерпретация которых зависит от типа сообщения. В заголовочном файле windows.h для кодов сообщений определены константы с интуитивно понятными именами:

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

Для стандартных управляющих элементов (библиотека Common Controls Library — COMCTL32.DLL) в Windows имеются предопределенные обработчики событий, которые при наступлении интересных событий сообщают всяческую полезную информацию окну, содержащему этот управляющий элемент. Стандартная библиотека Common Dialog Box Library (COMDLG32.DLL) содержит несколько готовых весьма полезных диалоговых окон с обработчиками: диалоги выбора файла, настроек печати, выбора шрифта, выбора цвета и др. Кроме того, любая среда разработки (VisualBasic, Delphi, VisualC++ и т.п.) навязывает разработчику дополнительный набор готовых управляющих элементов и диалогов — иногда достаточно удобных, иногда не очень.

Структура программы

Программа для Win32 обычно состоит из следующих блоков:

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

  • UINT style — стиль (поведение) класса окон,
  • WNDPROC lpfnWndProc — процедура обработки событий окна,
  • int cbClsExtra — размер дополнительной памяти в системной структуре класса для данных пользователя,
  • int cbWndExtra — размер дополнительной памяти в системной структуре окна для данных пользователя,
  • HINSTANCE hInstance — дескриптор модуля (экземпляра программы), в котором реализована процедура обработки,
  • HICON hIcon — дескриптор иконки окна,
  • HCURSOR hCursor — дескриптор курсора мыши для окна,
  • HBRUSH hbrBackground — дескриптор «кисточки» для закрашивания фона окна,
  • LPCSTR lpszMenuName — имя ресурса, содержащего меню окна,
  • LPCSTR lpszClassName — имя класса.

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

Для создания окна вызывается функция:

Вместо параметров x, y, nWindth, nHeight допустимо передавать константу CW_USEDEFAULT , позволяющую операционной системе задать эти числа по ее усмотрению.

Интерпретация кода стиля определяется классом окна. Стиль определяет не только оформление окна, но и его поведение. Общие для всех классов константы стилей (при необходимости объединяются операцией побитовое ИЛИ):

  • WS_DISABLED — при создании окно заблокировано (не может получать реакцию от пользователя);
  • WS_VISIBLE — при создании окно сразу же отображается (не надо вызывать ShowWindow );
  • WS_CAPTION — у окна есть строка заголовка;
  • WS_SYSMENU — у окна есть системное меню;
  • WS_MAXIMIZEBOX — у окна есть кнопка разворачивания;
  • WS_MINIMIZEBOX — у окна есть кнопка сворачивания;
  • WS_SIZEBOX или WS_THICKFRAME — у окна есть рамка изменения размеров;
  • WS_BORDER — у окна есть рамка (не подразумевает изменение размеров);
  • WS_HSCROLL или WS_VSCROLL — у окна есть горизонтальная или вертикальная прокрутка;
  • WS_OVERLAPPED или WS_TILED — «перекрываемое» окно — обычное окно с рамкой и строкой заголовка;
  • WS_POPUP — «всплывающее» окно;
  • WS_OVERLAPPEDWINDOW — «перекрываемое» окно с системным меню, кнопками сворачивания/разворачивания, рамкой изменения размеров, короче, типичный стиль для главного окна приложения.

Во время выполнения функции CreateWindow процедуре обработки событий окна посылается сообщение WM_CREATE . При успешном выполнении функции возвращается дескриптор созданного окна, при неудаче — NULL.

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

  • SW_SHOW — отобразить и активировать окно;
  • SW_HIDE — скрыть окно;
  • SW_MAXIMIZE — развернуть окно на весь экран;
  • SW_RESTORE — активировать окно и отобразить его в размерах по умолчанию;
  • SW_MINIMIZE — свернуть окно.

Если перед вызовом этой функции окно было видимым, функция возвращает TRUE , если же окно было скрыто — FALSE .

Если клиентская область главного окна приложения содержит объекты, прорисовываемые по сообщению WM_PAINT , имеет смысл прорисовать эти объекты сразу после отображения главного окна на экране. Функция UpdateWindow непосредственно вызывает процедуру обработки событий указанного окна с сообщением WM_PAINT (минуя очередь сообщений приложения):

Windows использует два способа доставки сообщений процедуре обработки событий окна:

  • непосредственный вызов процедуры обработки событий (внеочередные или неоткладываемые сообщенияnonqueued messages);
  • помещение сообщения в связанный с данным приложением буфер типа FIFO, называемый очередью сообщенийmessage queue (откладываемые сообщенияqueued messages).

К внеочередным сообщениям относятся те сообщения, которые непосредственно влияют на окно, например, сообщение активации окна WM_ACTIVATE и т.п. Кроме того, вне очереди сообщений обрабатываются сообщения, сгенерированные различными вызовами Win32 API, такими как SetWindowPos , UpdateWindow , SendMessage , SendDlgItemMessage .

К откладываемым сообщениям относятся сообщения, связанные с реакцией пользователя: нажатие клавиш на клавиатуре, движение мышки и клики. Чтобы извлечь сообщение из очереди, программа вызывает функцию Эта функция возвращает FALSE , если получено сообщение WM_QUIT , и TRUE в противном случае. Очевидно, что условием продолжения цикла обработки событий является результат этой функции. Если приложение хочет завершить свою работу, оно посылает само себе сообщение WM_QUIT при помощи функции Ее параметр — статус выхода приложения. Обычно эта функция вызывается в ответ на сообщение об уничтожении окна WM_DESTROY .

После извлечения сообщения из очереди следует вызвать функцию TranslateMessage , переводящую сообщения от нажатых клавиш в удобоваримый вид, а затем DispatchMessage , которая определяет предназначенное этому сообщению окно и вызывает соответствующую процедуру обработки событий. Результат возврата соответствует значению, которое вернула процедура обработки событий (обычно никому не нужен).

Процедура обработки сообщений окна должна быть объявлена по следующему прототипу: Значения параметров: hw — дескриптор окна, которому предназначено сообщение, msg — код сообщения, wp и lp — 32-битные параметры сообщения, интерпретация которых зависит от кода сообщения. Зачастую старший/младший байт или старшее/младшее слово параметров сообщения несут независимый смысл, тогда удобно использовать определенные в windows.h макросы:

Например, сообщение WM_COMMAND посылается окну в трех случаях:

  1. пользователь выбрал какую-либо команду меню;
  2. пользователь нажал «горячую» клавишу (accelerator);
  3. в дочернем окне произошло определенное событие.

При этом параметры сообщения интерпретируются следующим образом. Старшее слово параметра WPARAM содержит: 0 в первом случае, 1 во втором случае и код события в третьем случае. Младшее слово WPARAM содержит целочисленный идентификатор пункта меню, «горячей» клавиши или дочернего управляющего элемента. Параметр LPARAM в первых двух случаях содержит NULL , а в третьем случае — дескриптор окна управляющего элемента.

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

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

Все описанное в данном параграфе суммируется в примере 4 (example4.cpp):

Приведенный пример создает окно с кнопкой «My button», при нажатии на которую вылезает окно-сообщение:

Ресурсы

Ресурсы — это бинарные данные, добавляемые в исполняемый файл при компоновке программы. К стандартным ресурсам относятся: иконки, курсоры, меню, диалоги, растровые изображения (BMP), векторные изображения (EMF), шрифты, таблицы горячих клавиш, таблицы строк, информация о версии программы или модуля. В процессе разработки программы ресурсы описывают в отдельном текстовом файле — файле описания ресурсов (расширение .rc), — а затем при помощи компилятора ресурсов переводят в бинарный вид и добавляют в исполняемый файл на этапе компоновки исполняемого файла. Использование ресурсов значительно облегчает работу программиста по визуализации графических примитивов интерфейса программы.

Файл описания ресурсов состоит из операторов, объединяемых в блоки. Один оператор занимает одну строку файла. Допускается использовать комментарии, определяемые так же, как в программе на языке Си. Файл описания ресурсов перед компиляцией так же обрабатывается препроцессором, поэтому в нем можно использовать директивы препроцессора ( #include , #define , . ) и макроопределения. В сложных «блочных» описаниях ресурсов вместо ключевых слов BEGIN и END можно использовать < и >, соответственно.

Иконки, картинки и курсоры мыши можно описать двумя способами (квадратные скобки не являются частью синтаксиса оператора и означают необязательный элемент): Здесь nameID — численный или строковой идентификатор; RESOURCETYPE — ключевое слово, обозначающее тип ресурса: ICON , BITMAP или CURSOR ; load-option и mem-option — всякие неинтересные в данный момент опции, которые можно спокойно пропустить; filename — имя файла, содержащее соответствующий ресурс.

Эти ресурсы можно внедрить в виде шестнадцатеричных кодов непосредственно в файл ресурсов:

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

Меню описывается следующим образом: Здесь item-definitions — один из трех операторов: Параметры операторов имеют следующий смысл: text — текст пункта меню или подменю (может содержать комбинации \t — табуляция, \a — выравнивание по правому краю, & — следующий символ подчеркивается, обозначает «горячую» клавишу для указанного пункта меню); result — целочисленный идентификатор пункта меню, посылаемый окну-владельцу через сообщение WM_COMMAND при выборе этого пункта меню; optionlist — необязательный список опций, разделенных запятой или пробелом:

  • CHECKED — рядом с пунктом меню отображается галочка,
  • GRAYED — пункт меню неактивен (не может быть выбран) и отображается серым цветом и др.


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

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

Для этого создаем файл ресурсов (example4a.rc):

Для перевода файла описания ресурсов в бинарный вид используется компилятор ресурсов Borland Resource Compiler: В результате получается файл example4a.res, который потребуется в процессе компоновки.

В примере 4 надо изменить строки на

Чтобы программа не была такой скучной, изменим обработчик сообщения WM_COMMAND : В результате при выборе того или иного пункта меню выводится окно-сообщение с кодом команды.

Обратите внимание: среди команд меню не используется код 1, который отведен кнопке «My button». Это типичная практика назначать всем дочерним элементам окна и командам меню разные численные идентификаторы, что облегчает обработку сообщения WM_COMMAND .

Компиляция и компоновка сложных проектов

Теперь компоновка программы будет более сложной, поэтому bcc32 с этой задачей не справится. В этом примере компилятор будет использоваться только для компилирования (запускаем с ключом ): В результате получаем объектный файл example4a.obj.

Чтобы собрать все части программы вместе, придется запускать компоновщик вручную (ilink32 или tlink32). В командной строке компоновщика указываются следующие параметры:

  • options — о допустимых опциях можно узнать, запустив компоновщик без параметров. Нам потребуются:
    • -aa — тип приложения «графическое для Win32» (другие варианты: -ap — консольное приложение, -ad — драйвер);
    • -Tpe — формат выходного файла «.EXE» (другой вариант: -Tpd — «.DLL»);
    • -L путь — путь для поиска библиотек и объектных файлов (обычно: -Lc:\bcc55\lib).
  • objfiles — список объектных файлов, из которых составляется программа, разделенных пробелом или знаком «+». Этот список должен начинаться с борландовского инициализационного объектного файла: c0w32.obj — для графического приложения под Win32 или c0x32.obj — для консольного приложения.
  • exefile — имя исполняемого файла, который получится в результате компоновки.
  • mapfile — имя файла, который после компиляции будет содержать карту сегментов вашей программы (оно вам надо? если нет, здесь делаем «пусто», а в опциях указываем -x, чтобы компоновщик не замусоривал рабочий каталог этой фигней).
  • libfiles — список библиотек, в которых надо искать не определенные в программе функции, разделенных пробелом или знаком «+». Как минимум, надо указать import32.lib, которая содержит код подключения к стандартным библиотекам Win32 API: kernel32.dll, user32.dll, gdi32.dll, advapi32.dll и др. Если вы используете какие-либо функции стандартных библиотек языка Си (stdlib, stdio, . ), надо указать еще cw32.lib.
  • deffile — файл параметров модуля (module definition file). Это текстовый файл, в котором определяются различные настройки компилируемой программы типа: размеры сегментов стека, кода, данных, «заглушка» (что будет происходить при попытке запуска программы в DOS) и проч. Если не указывать этот файл, компоновщик выберет вполне приличные для большинства случаев параметры по умолчанию.
  • resfiles — файлы ресурсов (разделяются пробелом или знаком «+»).

Компоновщик ilink32 умеет работать в пошаговом (инкрементирующем) режиме incremental linking, при этом он создает несколько файлов состояний (*.IL?). При последующих попытках компиляции он использует их, так что процесс компиляции занимает меньше времени. Чтобы отключить пошаговую компиляцию и не замусоривать рабочий каталог этими файлами, следует указать опцию -Gn. Например, если при отключенной пошаговой компиляции программа компилируется 8 секунд, то первая компиляция в пошаговом режиме займет 25 секунд, а все последующие — не более 2 секунд.

Итак, компонуем модифицированный пример 4:

Если все сделано правильно, первое, что становится сразу заметным — у исполняемого файла появилась иконка:

Эта же иконка отображается в строке заголовка главного окна программы. Под строкой заголовка отображается созданное нами меню. При выборе любой команды меню появляется окно-сообщение с кодом команды. При выборе команды «Exit» программа завершается.

Если ваш проект состоит из множества файлов, то компилировать и компоновать их вручную становится затруднительно. В таких случаях используются сценарии компиляции, которые обрабатываются программой make. Сценарий компиляции — текстовый файл с именем Makefile , описывающий зависимости между различными файлами проекта, следующего формата:

Сценарий компиляции должен содержать как минимум одно правило. Строка с командами обязательно должна начинаться с отступа табуляцией. В качестве имени правила обычно выступает имя файла, который получится в результате выполнения команд в теле правила. Зависимости — необязательный список имен файлов, разделенных пробелами, от которых зависит данное правило. Если при вызове make окажется, что хотя бы один файл из этого списка новее, чем файл-результат правила, то выполняются все команды из этого правила. В качестве зависимостей могут указываться имена файлов-названия других правил. Тогда make будет выполнять рекурсивную проверку зависимостей. Make не выполняет команды из правила, если все файлы-зависимости старее файла-результата.

Если не указывать в качестве файлов-зависимостей example4a.rc и example4a.cpp, то make не станет ничего делать, когда файл example4a.exe уже существует. Тем не менее, приведенный пример — не совсем удачный сценарий компиляции. Если мы изменим только файл ресурсов, make все равно будет перекомпилировать исходный текст. Если мы изменим только исходный текст, make будет перекомпилировать еще и ресурсы. С учетом этого замечания, более удачным будет следующий сценарий:

Если в командной строке make не указано иное, то make пытается выполнить первое правило из сценария. Именно поэтому первым правилом стоит example4a.exe — результат, который мы хотим получить после компиляции всего проекта.

Если написать подходящий сценарий компиляции, то для компиляции вашего проекта придется набирать лишь команду:

Диалоговые окна

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

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

Илон Маск рекомендует:  Пример вывода произвольного текста на изображении

Для создания модального диалога используется функция DialogBox , а для создания немодального диалога — CreateDialog :

Параметры: hInst — дескриптор экземпляра программы (модуля, в котором находится шаблон); template — имя ресурса, описывающего диалог; parent — дескриптор родительского окна; DlgFunc — диалоговая функция следующего формата: Параметры диалоговой функции такие же, как у обычной функции обработки событий. Отличие этой функции — она вызывается из предопределенной функции обработки событий для диалоговых окон. Она должна вернуть значение TRUE , если обработала переданное ей сообщение, или FALSE в противном случае. Она ни в коем случае не должна сама вызывать DefWindowProc .

При создании диалогового окна диалоговая процедура получает сообщение WM_INITDIALOG . Если в ответ на это сообщение процедура возвращает FALSE , диалог не будет создан: функция DialogBox вернет значение -1 , а CreateDialog — NULL .

Модальное диалоговое окно блокирует указанное в качестве родительского окно и появляется поверх него (вне зависимости от стиля WS_VISIBLE ). Приложение закрывает модальное диалоговое окно при помощи функции Приложение должно вызвать эту функцию из диалоговой процедуры в ответ на сообщение от кнопок «OK», «Cancel» или команды «Close» из системного меню диалога. Параметр result передается программе как результат возврата из функции DialogBox .

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

Если эта функция вернула TRUE , то сообщение обработано и его не следует передавать функциям TranslateMessage и DispatchMessage .

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

Шаблон диалогового окна в ресурсах задается следующим образом:

В начале блока описания диалога задается: nameID — целочисленный или строковой идентификатор ресурса, x , y — координаты диалога на экране (или относительно родительского окна), width , height — размер диалога. Координаты и размеры диалога и всех элементов внутри него задаются в диалоговых единицах (dialog units). Одна горизонтальная диалоговая единица соответствует 1/4 средней ширины символа в системном шрифте. Одна вертикальная диалоговая единица соответствует 1/8 средней высоты символа в системном шрифте.

После заголовка блока идет ряд необязательных операторов-параметров диалога (property-statements) в любом порядке:

В качестве стиля диалога можно применять все перечисленные для обычных окон стили. Обычно выбирают: WS_POPUP , WS_SYSMENU , WS_CAPTION , а также WS_BORDER для немодального диалога и DS_MODALFRAME — для модального. Кроме того, можно использовать DS_SETFOREGROUND , чтобы при отображении диалога перевести его на передний план, даже если его родительское окно неактивно.

В теле шаблона (control-statements) перечисляются составляющие его управляющие элементы. Один из возможных вариантов оператора: Здесь text — текст управляющего элемента (заголовок), id — целочисленный идентификатор элемента 0. 65535 (внутри одного диалога идентификаторы всех элементов должны различаться), class — имя класса, к которому принадлежит управляющий элемент, style — стиль управляющего элемента, x , y , width , height — положение и размер диалогового элемента относительно клиентской области диалога в диалоговых единицах.

Вот пример диалога, содержащего простое статическое текстовое поле и кнопку «OK»:

Добавим этот диалог к ресурсам примера 4. В текст программы добавим две глобальных переменных:

Присвоим переменной h значение дескриптора экземпляра программы в самом начале функции WinMain . Это значение нам потребуется для вызова функции DialogBox .

Изменим обработчик сообщения WM_COMMAND следующим образом:

Теперь в текст программы необходимо добавить диалоговую процедуру:

При создании диалога вызывается процедура SetDlgItemText , меняющая содержание текстового поля в диалоге (элемент с , генерирующая сообщение WM_COMMAND с >Функция DlgProc должна быть определена или описана до ссылки на нее в вызове DialogBox .

Управляющие элементы

Управляющие элементы, как и другие окна, принадлежат тому или иному классу окон. Windows предоставляет несколько предопределенных классов управляющих элементов. Программа может создавать управляющие элементы поштучно при помощи функции CreateWindow или оптом, загружая их вместе с шаблоном диалога из своих ресурсов. Управляющие элементы — это всегда дочерние окна. Управляющие элементы при возникновении некоторых событий, связанных с реакцией пользователя, посылают своему родительскому окну сообщения-оповещения (notification messages) WM_COMMAND или WM_NOTIFY .

Как и любое другое окно, управляющий элемент может быть скрыт или отображен при помощи функции ShowWindow . Аналогично, управляющий элемент может быть блокирован или разблокирован при помощи функции: В качестве второго параметра передается флаг TRUE (разблокировать) или FALSE (блокировать). Функция возвращает значение TRUE , если перед ее вызовом окно было заблокировано. Узнать текущий статус блокирования окна можно при помощи функции: которая возвращает значение TRUE , если окно разблокировано.

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

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

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

Для управляющих элементов внутри диалогов специальный смысл имеют стили WS_TABSTOP и WS_GROUP . Если в диалоге имеются управляющие элементы со стилем WS_TABSTOP , то при нажатии пользователем на клавишу [Tab] (или [Shift]+[Tab]), текущий активный элемент диалога будет терять фокус и передавать его следующему за ним (или предыдущему) ближайшему элементу со стилем WS_TABSTOP . С помощью стиля WS_GROUP элементы диалога можно объединять в группы. Группа элементов начинается с элемента со стилем WS_GROUP и заканчивается элементом, после которого идет элемент со стилем WS_GROUP , или последним элементом в диалоге. Внутри группы только первый элемент должен иметь стиль WS_GROUP . Windows допускает перемещение внутри группы при помощи клавиш-стрелок.

Классы предопределенных управляющих элементов: «STATIC» Статический управляющий элемент представляет собой текстовую метку или прямоугольник. Не предоставляет пользователю ни средств ввода, ни вывода. Примеры:

Соответствующее описание ресурсов: Для текстовых статиков со стилями SS_LEFT , SS_RIGHT или SS_CENTER существуют более простые операторы объявления ресурсов: Чтобы поменять текст статика ему можно послать сообщение WM_SETTEXT ( wp=0 ; lp=(LPARAM)(LPCSTR)lpsz — адрес строки) или использовать функции: Чтобы сменить иконку или картинку нетекстового статика, надо послать ему сообщение STM_SETIMAGE ( wp=(WPARAM)fImageType — тип изображения: IMAGE_BITMAP или IMAGE_ICON ; lp=(LPARAM)(HANDLE)hImage — дескриптор иконки или картинки). «BUTTON» Кнопка — это небольшое прямоугольное дочернее окно, обычно имеющее два состояния: нажато/отпущено или включено/выключено. Пользователь меняет состояние этого элемента щелчком мыши. К этому классу относятся: кнопки-«давилки» (push buttons), кнопки-«галочки» (check boxes), «радио»-кнопки (radio buttons) и специальный тип групповых рамочек (group boxes). Примеры:

Соответствующее описание ресурсов: Для кнопок существуют более простые операторы объявления ресурсов: Разница между стилями BS_xxx и BS_AUTOxxx заключается в том, что при щелчке по AUTO -кнопкам Windows сама автоматически переключает их состояние. Для не AUTO -кнопок это надо делать вручную в диалоговой процедуре, послав сообщение BM_SETCHECK ( wp=(WPARAM)fCheck — флаг: BST_UNCHECKED , BST_CHECKED или BST_INDETERMINATE (для BS_3STATE -кнопок); lp=0 ) или при помощи функций: Автоматические радио-кнопки должны быть объединены в группу при помощи стиля WS_GROUP , чтобы Windows корректно их обрабатывала.
Проверить состояние кнопки можно, послав ей сообщение BM_GETCHECK ( wp=0; lp=0 ) или вызовом функции: При щелчке мыши по кнопке она присылает родительскому диалогу сообщение-оповещение WM_COMMAND ( HIWORD(wp)=BN_CLICKED; LOWORD(wp)=(int) >). «EDIT» Поле редактирования предназначено для ввода пользователем текста с клавиатуры. Щелчком мыши внутри элемента пользователь передает этому элементу фокус ввода (input focus). При этом внутри элемента появляется текстовый курсор — мигающая каретка. Пользователь может использовать мышь для перемещения каретки по полю редактирования и выделению текста в этом поле. Примеры:


Соответствующее описание ресурсов: Стиль ES_WANTRETURN означает, что кнопка [Enter] будет обрабатываться самим элементом, а не передаваться диалогу. Благодаря этому стилю оказался возможен переход на новую строчку для предложения «Она съела кусок. » (на картинке).
По умолчанию текстовые поля позволяют вводить столько текста, сколько может отобразиться в рамках поля. Чтобы предоставить пользователю возможность ввести больше текста, надо использовать стиль ES_AUTOHSCROLL (и ES_AUTOVSCROLL для многострочных полей).
Для текстовых полей существует более простой оператор объявления ресурсов: Чтобы поменять содержимое текстового поля, программа вызывает функцию SetDlgItemText . Чтобы получить текущее содержимое текстового поля, используется функция: Чтобы узнать размер строки в текстовом поле, надо послать элементу сообщение WM_GETTEXTLENGTH ( wp=0; lp=0 ).
Текстовое поле посылает родительскому диалогу следующие сообщения-оповещения WM_COMMAND ( LOWORD(wp)=(int) >):

  • HIWORD(wp)=EN_KILLFOCUS — текстовое поле потеряло фокус (фокус передан другому элементу диалога);
  • HIWORD(wp)=EN_SETFOCUS — текстовое поле получило фокус;
  • HIWORD(wp)=EN_CHANGE — пользователь изменил текст в поле;
  • HIWORD(wp)=EN_ERRSPACE — закончилось место, отведенное под текстовый буфер управляющего элемента.

«LISTBOX» Окно-список используется для отображения списка имен (например, имен файлов). Пользователь может, просматривая список, выделить один или несколько элементов щелчком мыши. При выделении того или иного элемента списка, он подсвечивается, а родительскому окну посылается сообщение-оповещение. Для очень больших списков могут использоваться полосы прокрутки. Примеры:

Соответствующее описание ресурсов: или в короткой форме: Для добавления элемента к списку следует послать ему сообщение LB_ADDSTRING ( wp=0; lp=(LPARAM)(LPCSTR)lpsz — строка для добавления). Для того, чтобы заполнить один из списков, показанных на рисунке, в обработчик сообщения WM_INITDIALOG в диалоговую процедуру был вставлен такой фрагмент: Кроме этого, список «понимает» следующие сообщения:

  • LB_DELETESTRING ( wp=(WPARAM)index; lp=0 ) — удалить элемент с указанным номером;
  • LB_INSERTSTRING ( wp=(WPARAM)index; lp=(LPARAM)(LPCSTR)lpsz ) — вставить указанную строку в список как элемент с индексом index;
  • LB_FINDSTRING ( wp=(WPARAM)indexStart; lp=(LPARAM)(LPTSTR)lpszFind ) — найти элемент, содержащий указанную строку (поиск ведется, начиная с элемента indexStart), результат сообщения — номер элемента, удовлетворяющего критерию, или LB_ERR ;
  • LB_GETCOUNT ( wp=0; lp=0 ) — количество элементов в списке;
  • LB_GETCURSEL ( wp=0; lp=0 ) — выделенный элемент в списке;
  • LB_RESETCONTENT ( wp=0; lp=0 ) — удалить все элементы из списка.

Окно-список посылает родительскому диалогу следующие сообщения-оповещения WM_COMMAND ( LOWORD(wp)=(int) >):

  • HIWORD(wp)=LBN_DBLCLK — пользователь дважды щелкнул мышью по списку;
  • HIWORD(wp)=LBN_SELCHANGE — пользователь выделил другой элемент в списке (или отменил выделение).

«COMBOBOX» Комбобокс — это помесь поля редактирования с окном-списком. Этот элемент содержит поле редактирование и список, который может отображаться все время либо «выпадать» при нажатии на кнопку рядом с полем редактирования. Есть три основных типа комбобоксов:

  • «выпадающий» комбобокс ( CBS_DROPDOWN ) содержит поле редактирования и «выпадающий» список;
  • «выпадающий» список ( CBS_DROPDOWNLIST ) не содержит поля для изменения текста;
  • простой комбобокс ( CBS_SIMPLE ) содержит поле редактирования и обычный список.

Обратите внимание, что в ресурсах значение высоты элемента определяет размер поля редактирования вместе с «выпавшим» списком по вертикали. Короткий вариант этого же объявления ресурсов: Для работы с комбобоксами существуют сообщения, аналогичные списковым: CB_ADDSTRING , CB_DELETESTRING , CB_INSERTSTRING , CB_FINDSTRING , CB_GETCOUNT , CB_GETCURSEL , CB_RESETCONTENT .
Комбобокс посылает родительскому диалогу сообщение оповещение WM_COMMAND со следующими кодами оповещения:

  • CBN_SELCHANGE , когда пользователь выделяет другую строку в комбобоксе (бывает полезным для простых комбобоксов);
  • CBN_SELENDOK , когда пользователь выбрал элемент в выпадающем списке и щелкнул мышкой по выделению (подтвердил выделение), для простых комбобоксов посылается перед каждым CBN_SELCHANGE ;
  • CBN_SELENDCANCEL , когда пользователь закрыл выпадающий список, так и не выбрав никакой элемент;
  • CBN_DROPDOWN , когда открывается выпадающий список;
  • CBN_CLOSEUP , когда выпадающий список был закрыт по той или иной причине.

Кроме предопределенных управляющих элементов, Windows предоставляет еще набор стандартных управляющих элементов посредством библиотеки Common Controls Library (COMCTL32.DLL). Чтобы воспользоваться ей, в тест программы надо включить заголовочный файл commctrl.h и добавить в блок инициализации программы вызов функции:

Управляющие элементы из этой библиотеки, как правило, посылают сообщения-оповещения родительскому диалогу через сообщение WM_NOTIFY ( wp=(int) > — указатель на структуру со специльными параметрами сообщения-оповещения).

Классы управляющих элементов из Common Controls Library: List View Controls ( WC_LISTVIEW ) Элемент просмотра списков — это окно отображающее совокупность элементов. Каждый элемент может быть представлен текстовой меткой и (необязательно) иконкой. Типичный пример использования этого элемента — программа «Проводник». Содержимое того или иного каталога представляется в виде элемента просмотра списков. Есть четыре основных стиля для этого элемента:

  • крупные иконки — стиль LVS_ICON ;
  • мелкие иконки — стиль LVS_SMALLICON ;
  • список — стиль LVS_LIST ;
  • таблица — стиль LVS_REPORT .

Приведенные в примере списки заполнялись в диалоговой процедуре при инициализации диалога ( WM_INITDIALOG ): Status Windows ( STATUSCLASSNAME ) Поле статуса — это горизонтальное окно в нижней части родительского окна, которое программа обычно использует для отображения каких-либо характеристик, параметров или небольших текстовых сообщений.

В примере 4б можно заменить статическое текстовое поле на поле статуса: При создании поля статуса не требуется указывать ни размер, ни позицию окна, Windows сама выберет эти параметры подходящим образом. Для создания поля статуса можно использовать специальную функцию: С помощью сообщения SB_SETPARTS поле статуса можно разбить на части ( wp=(int)nParts — количество частей; lp=(LPARAM)(int*)widths — указатель на массив размеров частей). В таком случае текст для каждой части поля статуса задается сообщением SB_SETTEXT ( wp=(int)iPart — номер части; lp=(LPARAM)(LPSTR)lpszText — текстовая строка). Пример: Up-Down Controls ( UPDOWN_CLASS ) Управляющий элемент «up-down» представляет собой пару небольших кнопок-стрелок, нажимая которые, пользователь увеличивает или уменьшает значение. Этот элемент, как правило, связывается с элементом-компаньоном (buddy window), обычно реализованным в виде поля редактирования. Для пользователя элемент «up-down» и его компаньон представляются единым управляющим элементом.
Если при создании элемента «up-down» указать стиль UDS_AUTOBUDDY , то компаньоном будет назначен предыдущий управляющий элемент диалога. Программа может также передать дескриптор окна-компаньона при помощи сообщения UDM_SETBUDDY ( wp=(WPARAM)(HWND)hwndBuddy — дескриптор окна-компаньона; lp=0 ). Если элементу «up-down» назначить стиль UDS_SETBUDDYINT , то он будет автоматически менять текст окна-компаньона, представляющий числовое значение.
Другой способ создать элемент «up-down» — использовать функцию Progress Bars ( PROGRESS_CLASS ) Полоса прогресса — это окно, которое программа может использовать для индикации состояния выполнения какой-либо длительной операции. Окно представляет собой прямоугольник, заполняемый системным цветом слева направо.
Каждый раз, когда приложение посылает этому окну сообщение PBM_STEPIT ( wp=0; lp=0 ), заполнение полосы прогресса продвигается дальше вправо на некоторое значение. Tooltip Controls ( TOOLTIPS_CLASS ) Окно-подсказка — всплывающее окно, содержащее строку описательной информации о том или ином элементе интерфейса программы. Таким элементом интерфейса может быть конкретное окно (управляющий элемент) или прямоугольный участок клиентской области какого-либо окна. Большую часть времени подсказка скрыта. Она появляется, когда пользователь задерживат курсор мыши над тем или иным элементом интерфейса программы более, чем на полсекунды. Подсказка скрывается, когда пользователь кликает мышью или уводит курсор с этого элемента. Одно окно-подсказка может обслуживать любое количество элементов интерфейса. Чтобы назначить тому или иному элементу интерфейса программы подсказку, надо окну-подсказке послать сообщение TTM_ADDTOOL ( wp=0; lp=(LPARAM)(TOOLINFO*)lpti — указатель на структуру, содержащую информацию об элементе). Пример:
Property Sheets & Tab Controls Элементы вкладки свойств и переключатели вкладок обычно используются совместно. Пример использования вкладок:

Каждая вкладка содержит свой набор управляющих элементов и может задаваться в ресурсах так же, как и отдельный диалог: Для создания диалога с вкладками используется функция PropertySheet , перед вызовом которой надо заполнить соответствующие системные структуры: Для каждой вкладки может быть своя диалоговая процедура, а может быть общая для всех вкладок (как в этом примере). Trackbars ( TRACKBAR_CLASS ) Ползунок (бегунок) используется, если от пользователя требуется получить дискретное значение из определенного диапазона. Маркер ползунка пермещается на заданное программой значение. Пример ползунка показан на первой вкладке предыдущего примера. Ползунки бывают горизонтальные ( TBS_HORZ ) или вертикальные ( TBS_VERT ). Диапазон значений ползунка задается сообщением TBM_SETRANGE ( wp=(BOOL)fRedraw — перерисовать маркер после изменения диапазона; lp=MAKELONG(lMinimum,lMaximum) — диапазон значений: младшее слово — минимальное значение, старшее слово — максимальное значение). Переместить ползунок можно при помощи сообщения TBM_SETPOS ( wp=TRUE ; lp=(LONG)position — новая позиция ползунка). Чтобы получить текущее значение ползунка, следует послать ему сообщение TBM_GETPOS ( wp=0; lp=0 ). Ползунок оповещает родительское окно о событиях через сообщение WM_HSCROLL ( LOWORD(wp)=ScrollCode — код события; HIWORD(wp)=posistion — позиция маркера ползунка; lp=(HWND)hwndTrackBar — дескриптор элемента). Toolbars ( TOOLBARCLASSNAME ) Панель инструментов — это окно, содержащее набор кнопок, посылающих командное сообщение родительскому окну, когда пользователь щелкает по ним. Как правило, кнопки на панели инструментов соответствуют часто используемым командам меню приложения. Панель инструментов располагается ниже строки меню.

Информация о кнопках передается панели инструментов через структуру TBBUTTON , а для создания окна панели инструментов удобно использовать функцию CreateToolbarEx . Rich Edit Controls ( RICHEDIT_CLASS ) Продвинутое поле редактирования является развитием класса «EDIT» стандартных управляющих элементов. Элементы управления этого класса поддерживают форматирование текста (по отдельным символам и по отдельным абзацам) и позволяют внедрять OLE-объекты. Tree View Controls ( WC_TREEVIEW ) Элемент просмотра дерева позволяет представлять информацию об иерархии некоторых объектов (содержание документа, дерево каталогов файловой системы и т.п.) Каждый объект может быть представлен текстовой меткой и иконкой. Объект может иметь иерархию дочерних объектов, которая раскрывается по щелчку на этом элементе.

Стандартные диалоги

Windows предоставляет набор готовых стандартных диалогов посредством библиотеки Common Dialog Boxes Library (COMDLG32.DLL): диалог открытия и сохранения файла, диалог печати документа, диалог выбора цвета, шрифта и т.п. Чтобы создать один из перечисленных диалогов, надо заполнить определенную структуру и вызвать соответствующую функцию из этой библиотеки:

  • BOOL WINAPI ChooseColor(CHOOSECOLOR* lpcc) — создает диалог, отображающий палитру цветов и позволяющий пользователю выбрать тот или иной цвет или создать свой.
  • BOOL WINAPI ChooseFont(CHOOSEFONT* lpcf) — создает диалог, отображающий имена установленных в системе шрифтов, их кегль, стиль начертания и т.п.
  • BOOL WINAPI GetOpenFileName(OPENFILENAME* lpofn) и BOOL WINAPI GetSaveFileNAme(OPENFILENAME* lpofn) — создают диалог, отображающий содержимое того или иного каталога, и позвояющий пользователю выбрать уникальное имя файла для открытия или сохранения.
  • BOOL WINAPI PrintDlg(PRINTDLG* lppd) — создает диалог, позволяющий пользователю установить различные опции печати, например, диапазон страниц, количество копий и др.
  • BOOL WINAPI PageStupDlg(PAGESETUPDLG* lppsd) — создает диалог, позволяющий пользователю выбрать различные параметры страницы: ориентацию, поля, размер бумаги и т.п.
  • HWND WINAPI FindText(FINDREPLACE* lpfr) — создает диалог, позволяющий пользователю ввести строку для поиска и такие опции, как направление поиска.
  • HWND WINAPI ReplaceText(FINDREPLACE* lpfr) — создает диалог, позволяющий пользователю ввести строку для поиска, строку для замены и опции замены (направление поиска, область поиска).

Все перечисленные диалоги, кроме последних двух, — модальные, т.е. указанная функция не вернет значение, пока пользователь тем или иным способом не закроет диалог. Значение TRUE означает, что пользователь закрыл диалог, нажав на «ОК», а соответствующая структура заполнена новыми значениями. Значение FALSE означает, что пользователь нажал на [Esc], выбрал команду системного меню «Закрыть» или нажал кнопку «Отмена», а соответствующая структура осталась неизменной. Пример использования диалога открытия файла:

Немодальный диалог в качестве главного окна

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

Пример 5 демонстрирует использование немодального диалога в приложении типа «блокнот».

Файл example5.h содержит константы-идентификаторы команд меню и элементов диалога.

Файл example5.rc описывает ресурсы программы: иконку, меню и шаблон диалога.

Файл example5.cpp — текст программы.

Win32 API. Программирование: Учебное пособие

Для того, чтобы оценить ресурс, необходимо авторизоваться.

Рассмотрены основные принципы программирования в среде Microsoft Windows на языке C++ с применением Win32 API. Пособие предназначено для студентов, обучающихся по специальностям 210202.65 «Проектирование и технология вычислительных средств» и 0900104.65 «Комплексная защита объектов информации», а также для студентов других специальностей изучающих дисциплину «Программирование на языках высокого уровня».

Winapi: не запутайтесь в типах

Одним из важнейших понятий в Windows-программировании является понятие объектного дескриптора (handle). В Windows все объекты — кисти, перья, растры, указатели мыши, контексты устройств, окна, экземпляры программ— идентифицируются 32-разрядным (в Winl6 — 16-разрядным) целым числом, которое называется дескриптором (иногда манипулятором). С каждым дескриптором связывается идентификатор типа, начинающийся с буквы «Н» (в языке Си — с «h» нижнего регистра. В отличие от Pascal, регистр в Си имеет значение). Например, hwnd— это дескриптор окна. Дескрипторы ссылаются на объекты, находящиеся под управлением системы Windows. Работающая операционная система отслеживает все дескрипторы которые служат связующими звеньями между загруженными объектами вызвавшими их приложениями.

Когда мы создаем некоторый объект в Windows, ему присваивается уникальный 32-разрядный дескриптор, который в последующем передается каждой функции, при работе с этим объектом. Это главное различие между функциями Widows API и стандартными методами класса Object Pascal. Вторые связаны с тем экземпляром класса, через который они вызываются, и поэтому не требуют явного указания на объект. Первым необходимо такое указание (что и делается с помощью дескриптора), т. к. они сами по себе никак не связаны ни с одним объектом.

В Delphi для хранения дескриптора объектов определен тип THandle:

type THandle : LongInt;

Кроме типа THandle в Delphi поддерживаются также имена типов, пришедшие из С++, а именно: hwnd, hmenu, hkey и другие имена, начинающиеся с префикса. Н (Handle).

При использовании функций Windows API к объектам Object Pascal следует понимать, что данные функции «не знают» о внутренних механизмах работы Delphi.

Поясним на примере: если скрыть окно не с помощью метода Delphi Hide, а с помощью функции WinAPI showWindow(Handle, sw_Hide), то в Delphi не возникнет событие onHide, потому что оно генерируется упомянутыми внутренними механизмами. Следует об этом помнить.

Но случается такое только когда функциями Windows API дублируется то, что можно сделать с помощью Delphi. Для вызова функций Windows API для объекта, созданного с помощью Delphi, используйте свойство объекта Handle, — в нем хранится дескриптор.

Читайте также

Сообщения операционной системы Windows очень напоминают событийные сообщения Delphi.

Каждое сообщение Windows имеет свой уникальный номер (идентификатор).

(ниже представлена обработка сообщения wm_move.

При обработке сообщений часто приходится сталкиваться с ситуациями, когда один 32-разрядный…

Большая часть функций API уже импортирована в среду Delphi и описана в ее модулях

Подпишись на нашу группу в контакте и будь в курсе обновлений:

Тайный WinAPI. Как обфусцировать вызовы WinAPI в своем приложении

Итак, есть несколько способов скрытия вызовов WinAPI.

  1. Виртуализация. Важный код скрывается внутри самодельной виртуальной машины.
  2. Прыжок в тело функции WinAPI после ее пролога. Для этого нужен дизассемблер длин инструкций.
  3. Вызов функций по их хеш-значениям.

Все остальные техники — это разные вариации или развитие трех этих атак. Первые две встречаются нечасто — слишком громоздкие. Как минимум приходится всюду таскать с собой дизассемблер длин и прологи функций, рассчитанные на две разные архитектуры. Вызов функций по хеш-именам прост и часто используется в более-менее видной малвари (даже кибершпионской).

Наша задача — написать легко масштабируемый мотор для реализации скрытия вызовов WinAPI. Они не должны читаться в таблице импорта и не должны бросаться в глаза в дизассемблере. Давай напишем короткую программу для экспериментов и откомпилируем ее для x64.

Как видишь, здесь используются две функции WinAPI — CreateFileA и Sleep . Функцию CreateFileA я решил привести в качестве примера не случайно — по ее аргументу «C:\\test\\text.txt» мы ее легко и найдем в уже обфусцированном виде.

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

  • предпочитать краткость кода ( /Os ) ,
  • отключить проверку безопасности ( /Gs- ) ,
  • отключить отладочную информацию,
  • в настройках компоновщика отключить внесение случайности в базовый адрес ( /DYNAMICBASE:NO ) ,
  • включить фиксированный базовый адрес ( /FIXED ) ,
  • обозначить самостоятельно точку входа (в нашем случае это main),
  • игнорировать все стандартные библиотеки ( /NODEFAULTLIB ) ,
  • отказаться от манифеста ( /MANIFEST:NO ) .
Илон Маск рекомендует:  Как сделать, чтобы фон не повторялся

Эти действия помогут уменьшить размер программы и избавить ее от вставок неявного кода. В моем случае получилось, что программа занимает 3 Кбайт. Ниже — ее полный листинг.

Как видишь, функции WinAPI явно читаются в коде и видны в таблице импорта приложения.

Приложение в программе PE-bear

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

Как хешировать

В статье нет смысла приводить алгоритм хеширования — их десятки, и они доступны в Сети, даже в Википедии. Могу посоветовать алгоритмы, с возможностью выставления вектора начальной инициализации (seed), чтобы хеши функций были уникальными. Например, подойдет алгоритм MurmurHash.


Давай условимся, что у нас макрос хеширования будет иметь прототип HASH_API(name, name_len, seed) , где name — имя функции, name_len — длина имени, seed — вектор начальной инициализации. Так что все значения хеш-функций у тебя будут другими, не как в статье!

Поскольку мы договорились писать легко масштабируемый модуль, определимся, что функция получения WinAPI у нас будет вида

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

По ходу написания этой функции я буду пояснять, что к чему, потому что путешествие по заголовку PE-файла — дело непростое (у динамической библиотеки будет именно такой заголовок). Сначала мы объявили используемые переменные, с этим не должно было возникнуть проблем. �� Далее, в первой строчке кода, мы получаем из переданного в нашу функцию модуля DLL ее IMAGE_DOS_HEADER . Вот его структура:

Продолжение доступно только участникам

Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», увеличит личную накопительную скидку и позволит накапливать профессиональный рейтинг Xakep Score! Подробнее

C++ — Использование STL-строк на границах Win32 API

Продукты и технологии:

C++, Standard Template Library, Visual Studio, Win32 API

В статье рассматриваются:

• Win32 API;

• передача стандартных STL-строк в качестве входных и выходных параметров;

• обработка потенциально возможного состояния гонок (race condition).

Win32 API предоставляет несколько средств, используя чистый C-интерфейс. Это означает, что при обмене текстом на границах Win32 API никаких строковых классов C++ нет. Вместо этого используются указатели на символы в стиле C. Например, Win32-функция SetWindowText имеет следующий прототип (из соответствующей документации MSDN на bit.ly/1Fkb5lw):

Строковый параметр выражается в форме LPCTSTR, что эквивалентно const TCHAR*. В Unicode-сборках (которые используются по умолчанию со времен Visual Studio 2005 и должны применяться в современных Windows-приложениях на C++) TCHAR typedef соответствует wchar_t, поэтому прототип SetWindowText читается как:

А значит, входная строка фактически передается как указатель-константа (т. е. только для чтения) на символ wchar_t с тем допущением, что указываемая строка заканчивается нулевым символом в классическом стиле чистого C. Это типичный шаблон для входных строковых параметров, передаваемых на границах Win32 API.

Конечно, применение C++ вместо чистого C — чрезвычайно продуктивный вариант разработки Windows-кода пользовательского режима и, в частности, Windows-приложений.

С другой стороны, выходные строки на границах Win32 API обычно представляются с использованием двух частей информации: указателя на буфер-получатель, выделенный вызвавшим кодом, и параметра размера, выражающего общую длину этого буфера. Примером является функция GetWindowText (bit.ly/1bAMkpA):

В этом случае информация, относящаяся к строковому буферу-получателю (выходной строковый параметр), хранится в последних двух параметрах: lpString и nMaxCount. Первый из них является указателем на буфер-приемник (представленный с использованием LPTSTR Win32 typedef, который транслируется в TCHAR* или wchar_t* в Unicode-сборках). Последний параметр, nMaxCount, представляет размер строкового буфера-получателя в wchar_ts; заметьте, что это значение включается завершающий нулевой символ (не забудьте, что строки в стиле C являются массивами символов, завершаемых нулевым символом).

В целом использование C++ поднимает семантический уровень кода и увеличивает эффективность труда программиста без негативного влияния на производительность приложения.

Конечно, применение C++ вместо чистого C — чрезвычайно продуктивный вариант разработки Windows-кода пользовательского режима и, в частности, Windows-приложений. В целом использование C++ поднимает семантический уровень кода и увеличивает эффективность труда программиста без негативного влияния на производительность приложения. В частности, работать со строковыми классами C++ гораздо лучше (легче, продуктивнее, менее чревато ошибками), чем иметь дело с массивами символов в стиле C, завершаемыми нулевыми символами.

Теперь возникает вопрос: какой вид строковых классов C++ можно использовать для взаимодействия с уровнем Win32 API, который изначально предоставляет интерфейсы для чистого C?

Класс CString из Active Template Library (ATL)/Microsoft Foundation Class Library (MFC) Что ж, этот класс — неплохой вариант. CString очень хорошо интегрируется с Windows-инфраструктурами C++ вроде ATL, MFC и Windows Template Library (WTL), которые упрощают Win32-программирование на C++. Так что, если вы используете эти инфраструктуры, имеет смысл применять CString для представления строк на специфичном для платформы уровне Win32 API приложений Windows на C++. Более того, CString предлагает удобные и специфичные для платформы Windows средства вроде возможности загружать строки из ресурсов и т. д.; это средства, зависимые от платформы, которые кросс-платформенная стандартная библиотека наподобие Standard Template Library (STL) просто по определению не в состоянии предложить. Поэтому, например, если вам нужно спроектировать и реализовать новый C++-класс, производный от какого-либо существующего ATL- или MFC-класса, подумайте о применении CString для представления строк.

Стандартные STL-строки Однако бывают случаи, где лучше использовать стандартный класс string в интерфейсе пользовательских C++-классов, образующих Windows-приложения. Например, вам может понадобиться абстрагировать уровень Win32 API в своем коде на C++ и предпочтительнее применять STL-класс строки вместо специфичных для Windows классов вроде CString в открытом интерфейсе пользовательских C++-классов. Давайте рассмотрим ситуацию, где текст хранится в строковых классах STL. В этом случае вам нужно передавать STL-строки через границы Win32 API (который предоставляет чистый C-интерфейс, как говорилось в начале статьи). При использовании ATL, WTL и MFC инфраструктура реализует «склеивающий» код между Win32 C-интерфейсом и CString, скрывая его «под капотом», но такое удобство недоступно в случае STL-строк.

Для целей этой статьи предположим, что строки хранятся в Unicode-формате UTF-16, который является кодировкой Unicode по умолчанию для Windows API. По сути, если бы эти строки использовали другой формат (скажем, Unicode UTF-8), их можно было бы преобразовывать в UTF-16 на границе Win32 API, чтобы удовлетворить ранее упомянутое требование этой статьи. Для таких преобразований применимы Win32-функции MultiByteToWideChar и WideCharToMultiByte: первую можно вызывать для преобразования из Unicode UTF-8 («мультибайтовой») строки в Unicode UTF-16 («широкосимвольную») строку, а последнюю — использовать для обратного преобразования.

В Visual C++ тип std::wstring хорошо подходит для представления строки Unicode UTF-16, поскольку его нижележащий тип символов — wchar_t, который имеет размер 16 битов в Visual C++, что совпадает с размерностью символа UTF-16. Заметьте, что на других платформах, таких как GCC Linux, wchar_t состоит из 32 битов, поэтому std::wstring на этих платформах хорошо подошел бы для представления текста, кодированного в формате Unicode UTF-32. Чтобы убрать эту неоднозначность, в C++11 ввели новый стандартный тип строк: std::u16string. Это специализация класса std::basic_string с элементами типа char16_t, т. е. с 16-битными символами.

Случай с входными строками

Если какая-то Win32-функция ожидает PCWSTR (или LPCWSTR в более старой терминологии), т. е. входной строковый параметр const wchar_t* с завершающим нулевым символом в стиле C, может сработать простой вызов метода std::wstring::c_str. По сути, этот метод возвращает указатель на строку в стиле C только для чтения.

CString очень хорошо интегрируется с Windows-инфраструктурами C++ вроде ATL, MFC и Windows Template Library (WTL), которые упрощают Win32-программирование на C++.

Например, чтобы задать текст заголовка окна или текст элемента управления, используя содержимое, хранящееся в std::wstring, Win32-функцию SetWindowText можно вызвать так:

Заметьте: хотя ATL/MFC CString обеспечивает неявное преобразование в «голый» const-указатель на символ (const TCHAR*, эквивалентный const wchar_t* в современных Unicode-сборках), STL-строки не поддерживают такое неявное преобразование. Вместо этого вы должны явно вызвать метод c_str STL-строки. В современном C++ сложилось общее понимание, что неявные преобразования — штука нехорошая, поэтому разработчики строковых STL-классов перешли на явно вызываемый метод c_str. (Соответствующее обсуждение отсутствия неявного преобразования в современных смарт-указателях STL вы найдете в публикации в блоге по ссылке bit.ly/1d9AGT4.)

Случай с выходными строками

В этом случае все немного усложняется. Обычный шаблон заключается в вызове какой-то Win32-функции, чтобы получить размер буфера-получателя для выходной строки. Размер может включать (а может и не включать) завершающий нулевой символ — вы должны прочитать документацию на эту Win32-функцию.

Затем вызывающий код динамически создает буфер должного размера. Размер этого буфера был определен на предыдущем этапе.

И наконец, вызовом другой Win32-функции содержимое строки считывается в буфер, созданный вызвавшим кодом.

Например, чтобы извлечь текст из элемента управления, можно вызвать API-функцию GetWindowTextLength и получить длину текстовой строки в wchar_ts. (Заметьте, что в этом случае возвращаемая длина не включает завершающий нулевой символ.)

Затем, используя эту длину, можно создать строковый буфер. Здесь одним из вариантов могло бы быть использование std::vector для управления строковым буфером:

Это проще, чем чистый вызов new wchar_t[bufferLength], потому что тогда потребовалось бы освобождать буфер вызовом delete[] (а если бы вы забыли об этом, появилась бы утечка памяти). Применение std::vector проще, даже если использование vector влечет за собой небольшие издержки по сравнению с чистым вызовом new[]. По сути, в этом случае деструктор std::vector автоматически удаляет выделенный буфер.

Это также помогает в написании C++-кода, безопасного при исключениях: если где-то в коде возникает исключение, автоматически вызывается деструктор std::vector. Тогда как буфер, динамически создаваемый new[], чей указатель хранится в исходном указателе-владельце (raw owning pointer), вызвал бы утечку памяти в такой ситуации.

Другой вариант, считающийся альтернативой std::vector, — использование std::unique_ptr, в частности std::unique_ptr . В этом варианте обеспечиваются автоматическая деструкция std::vector (и безопасность при исключениях) благодаря деструктору std::unique_ptr, а также меньшие издержки, чем в случае std::vector, поскольку std::unique_ptr — совсем крошечная C++-оболочка исходного указателя-владельца. В основном unique_ptr является указателем-владельцем, защищенным в безопасных границах RAII. RAII (bit.ly/1AbSa6k) — это очень распространенная идиома программирования на C++. Если вы не знакомы с ней, просто рассматривайте RAII как метод реализации, при котором автоматически вызывается delete[] обернутого указателя (например, в деструкторе unique_ptr), освобождая связанные ресурсы и предотвращая утечку памяти (и вообще утечку ресурсов).

С unique_ptr код мог бы выглядеть так:

Или следующим образом — при использовании std::make_unique (доступна в C++14 и реализована в Visual Studio 2013):

После создания буфера должного размера можно вызвать GetWindowText, передав указатель на этот буфер. Чтобы получить указатель на начало исходного буфера, управляемого std::vector, можно использовать метод std::vector::data (bit.ly/1I3ytEA), примерно так:

В случае unique_ptr вызывался бы его метод get:

И наконец, текст из элемента управления можно полностью скопировать (deep copied) из временного буфера в экземпляр std::wstring:

В предыдущем фрагменте кода я использовал перегрузку конструктора wstring, которая принимает константный исходный указатель wchar_t на входную строку с завершающим нулевым символом. Это работает нормально, поскольку вызываемый Win32 API вставит завершающий нулевой символ в строковый буфер-получатель, предоставленный вызвавшим кодом.

Для небольшой оптимизации, если длина строки (в wchar_ts) известна, вместо этого можно было бы использовать перегрузку конструктора wstring, которая принимает указатель и счетчик символов в строке. В этом случае длина строки предоставляется по месту вызова, и конструктору wstring не нужно находить ее (обычно с помощью операции O(N) подобно вызову wcslen в реализации Visual C++).

Сокращение для случая выходной строки: работа с std::wstring по месту

В отношении метода создания временного строкового буфера с использованием std::vector (или std::unique_ptr) и последующего его полного копирования and в std::wstring, вы могли бы пойти по кратчайшему пути. Экземпляр std::wstring можно было бы применять непосредственно как буфер-получатель, передаваемый в Win32-функции.

По сути, std::wstring имеет метод resize, который применим для формирования строки должного размера. Заметьте, что в этом случае вас не волнует начальное содержимое строки измененного размера, поскольку оно будет перезаписано вызванной Win32-функцией. На рис. 1 приведен фрагмент примера кода, показывающий, как считывать строки по месту, используя std::wstring..

Рис. 1. Чтение строк по месту, используя std::wstring

Я хотел бы прояснить несколько моментов, относящихся к коду на рис. 1.

Получаем доступ для записи к внутреннему строковому буферу Сначала рассмотрим вызов GetWindowText:

У программиста на C++ может возникнуть соблазн использовать метод std::wstring::data для доступа к внутреннему содержимому строки через указатель, передаваемый в вызове GetWindowText. Но wstring::data возвращает константный указатель, который не позволит модифицировать содержимое внутреннего строкового буфера. А поскольку GetWindowText ожидает доступа для записи к содержимому wstring, этот вызов не пройдет компиляцию. Поэтому альтернативой является применение синтаксиса &text[0] для получения адреса начала внутреннего строкового буфера, который должен быть передан как выходная строка (т. е. изменяемая) нужной Win32-функции.

По сравнению с предыдущим подходом этот метод более эффективен, так как нет временного std::vector с созданием буфера, последующим полным копированием в std::wstring и, наконец, его удалением. По сути, в этом случае код работает просто по месту — в экземпляре std::wstring.

Избегаем строк с двойными завершающими нулевыми символами Обратите внимание на последнюю строку кода на рис. 1:

При начальном вызове wstring::resize [text.resize(bufferLength); без коррекции «–1»] во внутреннем буфере wstring выделяется достаточно места, чтобы позволить Win32-функции GetWindowText влепить свой завершающий нулевой символ. Однако в дополнение к этому std::wstring неявно предоставляет другой завершающий нулевой символ. Таким образом, полученная строка завершается двумя нулевыми символами: один записывается GetWindowText, а другой автоматически добавляется wstring.

Чтобы исправить эту неправильную строку с двумя завершающими нулевыми символами, можно уменьшить размер экземпляра wstring, чтобы отбросить нулевой символ, записанный Win32-функцией, и оставить только нулевой символ, добавленный wstring. В этом заключается цель вызова text.resize(bufferLength–1).

Обработка состояния гонок

Прежде чем завершить эту статью, стоит обсудить, как обрабатывать потенциально возможное состояние гонок, которые могут возникнуть при работе с некоторыми API-функциями. Например, у вас есть код, который читает некое строковое значение из реестра Windows. Следуя шаблону, показанному в предыдущем разделе, программист на C++ сначала вызвал бы функцию RegQueryValueEx, чтобы получить длину строкового значения. Затем был бы создан буфер для этой строки, и, наконец, RegQueryValueEx была бы вызвана во второй раз, чтобы считать строковое значение в буфер, созданный на предыдущем шаге.


Состояние гонок, которое могло бы возникнуть в этом случае, — другой процесс, модифицирующий это строковое значение между двумя вызовами RegQueryValueEx. Длина строки, возвращенная первый вызовом, могла бы оказаться бессмысленным значением, не относящимся к новому строковому значению, записанному в реестр другим процессом. Таким образом, второй вызов RegQueryValueEx считал бы новую строку в буфер с неправильным размером.

Чтобы устранить эту ошибку, вы можете использовать шаблон кодирования наподобие представленного на рис. 2.

Рис. 2. Пример шаблона кодирования для обработки потенциально возможного состояния гонок в получении строк

Использование цикла while на рис. 2 гарантирует, что строка читается в буфер должного размера, так как каждый раз, когда возвращается ERROR_MORE_DATA, создается новый буфер с правильным значением bufferLength, и так до тех пор, пока API-вызов не закончится успехом (вернув ERROR_SUCCESS) или провалом по причине, отличной от неправильного размера буфера.

По сути, std::wstring имеет метод resize, который применим для формирования строки должного размера.

Заметьте, что фрагмент кода на рис. 2 — это не более чем пример скелетного кода; другие Win32-функции могут использовать другие коды ошибок, относящиеся к недостаточному размеру буфера, который предоставлен вызвавшим кодом, например код ERROR_INSUFFICIENT_BUFFER.

Заключение

Хотя использование CString на границе Win32 API (с помощью таких инфраструктур, как ATL/WTL и MFC) скрывает механику взаимодействия с уровнем чистых C-интерфейсов Win32, при работе с STL-строками программист на C++ должен обращать внимание на определенные детали. В этой статье я обсудил некоторые шаблоны кодирования для взаимодействия STL-класса wstring и Win32-функций с чистым C-интерфейсом. В случае ввода вызов метода c_str из wstring прекрасно подходит для передачи входной строки на границу Win32 C-интерфейса в форме простого константного (только для чтения) указателя на символ строки, завершаемой нулевым символом. В случае выходных строк вызывающий код должен создавать временный строковый буфер. Этого можно добиться, используя либо STL-класс std::vector, либо (с чуть меньшими издержками) STL-класс шаблона смарт-указателя std::unique_ptr. Другой вариант — задействовать метод wstring::resize, чтобы выделять некое пространство внутри экземпляра строки под буфер для Win32-функций. В этом случае важно указывать достаточное пространство, чтобы вызванная Win32-функция могла вписать свой завершающий нулевой символ, а затем уменьшать его размер, чтобы отбросить этот символ и оставить только завершающий нулевой символ от wstring. Наконец, я кратко рассмотрел потенциально возможное состояние гонок и представил пример шаблона кодирования для решения этой проблемы.

Джованни Диканио (Giovanni Dicanio) — программист, специализирующийся на C++ и операционной системе Windows, автор на Pluralsight. Является обладателем звания Visual C++ MVP. Помимо программирования и создания учебных курсов, любит помогать другим людям на форумах и в сообществах, преданных C++. С ним можно связаться по адресу giovanni.dicanio@gmail.com.

Выражаю благодарность за рецензирование статьи экспертам Дэвиду Крейви (David Cravey) (из GlobalSCAPE) и Стивену Т. Лававею (Stephan T. Lavavej) (из Microsoft).

Записки программиста

Пример простейшей многопоточной программы на WinAPI

25 декабря 2013

Продолжаем вспоминать Windows API. До сих пор мы писали грустные и унылые однопоточные программки из серии «вызови правильную процедуру с нужными аргументами и посмотри на ее код возврата». Сегодня же мы наконец-то напишем хоть и простенькую, но все же самую что ни на есть многопоточную программу, с теми самыми тредами и мьютексами, которые все постоянно ругают.

Из новых процедур нам понадобятся следующие.

Как нетрудно догадаться, CreateThread создает новую нитку. Аргументы слева направо — (1) нечто, о чем сейчас знать не нужно, (2) размер стека в байтах, округляется до размера страницы, если ноль, то берется размер по умолчанию, (3) указатель на процедуру, с которой следует начать выполнение потока, (4) аргумент процедуры, переданной предыдущим аргументом, обычно здесь передается указатель на некую структуру, (5) флаги, например, можно создать приостановленный поток (CREATE_SUSPENDED), а затем запустить его с помощью ResumeThread, (6) куда записать ThreadId созданного потока. В случае успеха процедура возвращает хэндл созданного потока. В случае ошибки возвращается NULL, а подробности можно узнать через GetLastError.

Процедура CreateMutex создает новый мьютекс. О первом аргументе сейчас знать не нужно. Если второй аргумент равен TRUE, созданный мьютекс будет сразу залочен текущим потоком, если же второй аргумент равен FALSE, создается разлоченный мьютекс. Третий аргумент задает имя мьютекса, если мы хотим создать именованный мьютекс. Именованные мьютексы нам пока что не понадобятся, поэтому передаем NULL. Возвращаемые значения в точности такие же, как и у CreateThread.

WaitForSingleObject ждет, когда объект, хэндл которого был передан первым аргументом, перейдет в сигнальное состояние (signaled state). Кроме того, в зависимости от типа объекта, процедура может менять его состояние. WaitForSingleObject может быть применен к мьютексам, семафорам, потокам, процессам и не только. Если hHandle представляет собой хэндл мьютекса, процедура ждет, когда мьютекс освободится, а затем лочит его. Если же hHandle является хэндлом потока, то процедура просто ждет его завершения. Второй аргумент задает время ожидания в миллисекундах. Можно ждать вечно, передав специальное значение INFINITE. Если указать ноль, процедура не переходит в режим ожидания, а возвращает управление немедленно.

В случае ошибки процедура возвращает WAIT_FAILED, а подробности поможет узнать GetLastError. В случае успеха возвращается WAIT_OBJECT_0, если мы дождались перехода объекта в сигнальное состояние, и WAIT_TIMEOUT, если отвалились по таймауту. Также мы можем получить WAIT_ABANDONED. Это происходит в случае, если нить, державшая мьютекс, завершилась, не освободив его. В этом случае мьютекс становится залочен текущей нитью, но целостность данных, доступ к которым ограничивался мьютексом, по понятным причинам находится под вопросом.

Процедура WaitForMultipleObjects работает аналогично WaitForSingleObject, но для массива объектов. Первый аргумент задает размер массива, должен быть строго больше нуля и не превышать MAXIMUM_WAIT_OBJECTS (который равняется в точности 64). Вторым аргументом задается указатель на массив хэндлов. В массиве могут содержаться хэндлы на объекты разных типов, но многократное включение одного и того же хэндла считается ошибкой. Если третий аргумент равен TRUE, процедура ждет перехода в сигнальное состояние всех объектов, иначе — любого из указанных объектов. Семантика последнего аргумента точно такая же, как и в случае с WaitForSingleObject.

Процедура возвращает WAIT_FAILED в случае ошибки и WAIT_TIMEOUT в случае отваливания по таймауту. Если процедура вернула значение от WAIT_OBJECT_0 до WAIT_OBJECT_0 + dwCount — 1, то в случае bWaitAll == TRUE все объекты из массива перешли в сигнальное состояние, а в случае bWaitAll == FALSE в сигнальное состояние перешел объект с индексом код возврата минус WAIT_OBJECT_0. Если процедура вернула значение от WAIT_ABANDONED_0 до WAIT_ABANDONED_0 + dwCount — 1, то в случае bWaitAll == TRUE все ожидаемые объекты перешли в сигнальное состояние и по крайней мере один из объектов оказался брошенным мьютексом, а в случае bWaitAll == FALSE брошенным оказался мьютекс с индексом код возврата минус WAIT_ABANDONED_0.

Если bWaitAll == TRUE, процедура не меняет состояния объектов до тех пор, пока все они не перейдут в сигнальное состояние. Таким образом, пока вы ждете, мьютексы могут продолжать лочиться и анлочиться другими потоками. Если bWaitAll == FALSE и сразу несколько объектов перешли в сигнальное состояние, процедура всегда работает с объектом в массиве, имеющим минимальный индекс.

ReleaseMutex анлочит ранее залоченный мьютекс. В случае успеха процедура возвращает значение, отличное от нуля. В случае ошибки возвращается ноль, а подробности доступны через GetLastError. Что характерно, для предотвращения дэдлоков Windows позволяет потокам лочить один и тот же мьютекс несколько раз, но и количество вызовов ReleaseMutex должно быть соответствующим.

Процедура Sleep блокирует текущий поток на заданное количество миллисекунд. Если в качестве параметра передать INFINITE, поток будет заблокирован навсегда. Если передать ноль, поток уступает оставшуюся часть своей доли процессорного времени любой другой нити с таким же приоритетом. У процедуры нет возвращаемого значения (VOID).

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

А теперь посмотрим на все это хозяйство в действии:

#define THREADS_NUMBER 10
#define ITERATIONS_NUMBER 100
#define PAUSE 10 /* ms */

DWORD dwCounter = 0 ;

DWORD WINAPI ThreadProc ( CONST LPVO >) <
CONST HANDLE hMutex = ( CONST HANDLE ) lpParam ;
DWORD i ;
for ( i = 0 ; i ITERATIONS_NUMBER ; i ++ ) <
WaitForSingleObject ( hMutex , INFINITE ) ;
dwCounter ++;
ReleaseMutex ( hMutex ) ;
Sleep ( PAUSE ) ;
>
ExitThread ( 0 ) ;
>

VO >( CONST HANDLE hStdOut , CONST LPCWSTR szMessage ) <
DWORD dwTemp ;
TCHAR szError [ 256 ] ;
WriteConsole ( hStdOut , szMessage , lstrlen ( szMessage ) , & dwTemp , NULL ) ;
wsprintf ( szError , TEXT ( «LastError = %d \r \n » ) , GetLastError ( ) ) ;
WriteConsole ( hStdOut , szError , lstrlen ( szError ) , & dwTemp , NULL ) ;
ExitProcess ( 0 ) ;
>

INT main ( ) <
TCHAR szMessage [ 256 ] ;
DWORD dwTemp , i ;
HANDLE hThreads [ THREADS_NUMBER ] ;
CONST HANDLE hStdOut = GetStdHandle ( STD_OUTPUT_HANDLE ) ;
CONST HANDLE hMutex = CreateMutex ( NULL , FALSE , NULL ) ;
if ( NULL == hMutex ) <
Error ( hStdOut , TEXT ( «Failed to create mutex. \r \n » ) ) ;
>

Илон Маск рекомендует:  Что такое код fbsql_fetch_field

for ( i = 0 ; i THREADS_NUMBER ; i ++ ) <
hThreads [ i ] = CreateThread ( NULL , 0 , & ThreadProc , hMutex , 0 , NULL ) ;
if ( NULL == hThreads [ i ] ) <
Error ( hStdOut , TEXT ( «Failed to create thread. \r \n » ) ) ;
>
>

WaitForMultipleObjects ( THREADS_NUMBER , hThreads , TRUE , INFINITE ) ;
wsprintf ( szMessage , TEXT ( «Counter = %d \r \n » ) , dwCounter ) ;
WriteConsole ( hStdOut , szMessage , lstrlen ( szMessage ) , & dwTemp , NULL ) ;

for ( i = 0 ; i THREADS_NUMBER ; i ++ ) <
CloseHandle ( hThreads [ i ] ) ;
>
CloseHandle ( hMutex ) ;
ExitProcess ( 0 ) ;
>

Здесь мы создаем десять потоков, каждый из которых увеличивает глобальный счетчик на единицу сто раз. Чтобы в счетчике не оказалась записана фигня, доступ к нему ограничивается с помощью мьютекса. Перед вызовом ExitProcess закрываются хэндлы мьютекса и всех созданных потоков. Строго говоря, в данном конкретном случае это не требуется, но в более общем случае все хэндлы за собой, разумеется, нужно вовремя закрывать. Также вы могли обратить внимание, что я прислушался к совету, который мне дали в комментариях к предыдущей заметке, и стал оборачивать строки в макрос TEXT.

Как обычно, программа может быть скомпилирована как в Visual Studio и запущена под Windows, так и с помощью MinGW и запущена под Wine. Ну и по традиции напоминаю, что ничто не делает блогера более грустным котиком, чем отсутствие каких-либо комментариев к его постам :)

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

Потоки (threads) в WinAPI

Когда приложение начинает свою работу, для него создаётся процесс (process). Обычно, каждой программе соответствует один процесс.

При создании процесса для него выделяется память — виртуальное адресное пространство (virtual address space). Когда в отладчике мы смотрим на адреса переменных — мы видим адреса из этого пространства.

Потоки (Threads)

Каждый процесс имеет как минимум один поток (thread). До сих пор наши программы состояли из одного процесса и одного потока. В этом уроке мы научимся создавать дополнительные потоки в процессе.

Самый главный вопрос о потоках: для чего нужно несколько потоков? Разные потоки могут одновременно выполняться разными ядрами процессора. Например, в нашей однопоточной программе одна функция просчитывает искусственный интеллект, а другая — физику взаимодействия объектов. В этом случае сначала будет выполнена одна функция, а потом вторая. Если же мы разделим программу на два процесса и запустим программу на двухъядерном процессоре, то искусственный интеллект и физика будут просчитываться одновременно.

Все потоки имеют доступ к адресному пространству процесса. И это может стать серьёзной проблемой. Например, один поток обрабатывает данные и, одновременно, второй пытается вывести эти же данные на экран. Что произойдёт? Успеет ли первый поток обработать все данные, до того как до них доберётся второй поток? Или же второй поток обгонит первый, и часть данных пользователь увидит обработанными, а часть — нет? Эти вопросы мы обсудим в следующих уроках.

С точки зрения C++ поток — это обычная функция имеющая определённый прототип. Для создания потока используется функция CreateThread.

Создание потоков — CreateThread

Функция CreateThread возвращает описатель потока:

!1?HANDLE WINAPI CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId );?1!

1. lpThreadAttributes — данный аргумент определяет, может ли создаваемый поток быть унаследован дочерним процессом. Мы не будем создавать дочерние процессы, поэтому ставим NULL.

2. dwStackSize — размер стека в байтах. Если передать 0, то будет использоваться значение по-умолчанию (1 мегабайт).

3. lpStartAddress — адрес функции, которая будет выполняться потоком. Т.е. можно сказать, что функция, адрес которой передаётся в этот аргумент, является создаваемым потоком. Данная функция должна соответствовать определённому прототипу — рассмотрим ниже. Имя функции может быть любым — вы сами его выбираете.

4. lpParameter — указатель на переменную, которая будет передана в поток.

5. dwCreationFlags — флаги создания. Здесь можно отложить запуск выполнения потока. Мы будем запускать поток сразу же, передаём 0.

6. lpThreadId — указатель на переменную, куда будет сохранён идентификатор потока. Нам идентификатор не нужен, передаём NULL.

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

!1?HANDLE thread = CreateThread(NULL,0,thread2,NULL, 0, NULL);?1!

Здесь мы сохраняем описатель потока в переменной thread. Обратите внимание на третий аргумент — адрес функции потока. thread2 — имя функции, которая и будет являться вторым потоком. Вот её код:

Функция потока должна соответствовать следующему прототипу:

!1?DWORD WINAPI ThreadProc(LPVOID lpParameter)?1!

Аргумент, который может принимать данная функция передаётся четвёртым параметров функции CreateThread. Если отбросить все переопределения типов, то данный прототип выглядит так:

!1?unsigned long __stdcall ThreadProc(void* lpParameter)?1!

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


Создание программы с двумя потоками

Код программы можно скачать в начале урока. Это простая консольная программа. Для работы с потоками необходимо включить файл windows.h. Рассмотрим основной код:

!1?DWORD WINAPI thread2(LPVO ; return 0; >?1!

У меня программа выводит в консоль следующее:

Сначала выводится текст First thread. Далее создаётся второй поток. А вот что выведется дальше однозначно утверждать нельзя: More data from first thread или Second thread. Зависит от того какой поток первым успеет напечатать сообщение. В большинстве случаев первый поток успеет быстрее, всё-таки второму потоку ещё предстоит выделить ресурсы и начать выполнять свою функцию.

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

Заключение

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

Надеюсь, основная идея работы с потоками вам понятна.

Графический интерфейс GDI в Microsoft Windows

2.1. Получение и освобождение контекста отображения

Способы получения (и, соответственно, освобождения) контекста отображения разные для контекстов разного типа. Можно выделить следующие типы контекста отображения:

  • общий контекст отображения (common display context);
  • контекст отображения для класса окна (class display context);
  • личный контекст отображения (private display context);
  • родительский контекст отображения (parent display context);
  • контекст отображения для окна (window display context);
  • контекст физического устройства (device context);
  • информационный контекст (information context);
  • контекст для памяти (memory device context);
  • контекст для метафайла (metafile context).

Каждый из перечисленных выше контекстов имеет свои особенности и свое назначение.

Общий контекст отображения

Этот контекст используется чаще всего и поэтому для ускорения доступа к нему Windows использует кеширование (как мы уже говорили, размер кеша достаточен для хранения только пяти контекстов отображения).

Для получения общего контекста отображения приложение должно вызвать функцию BeginPaint (при обработке сообщения WM_PAINT ) или GetDC (при обработке других сообщений). При этом перед регистрацией класса окна в поле стиля класса окна в структуре WNDCLASS не должны использоваться значения CS_OWNDC , CS_PARENTDC или CS_CLASSDC :

Функция BeginPaint возвращает контекст отображения для окна hwnd:

Перед этим она подготавливает указанное окно для рисования, заполняя структуру типа PAINTSTRUCT (адрес которой передается через параметр lpps) информацией, которую можно использовать в процессе рисования.

Структура PAINTSTRUCT и указатели на нее (различных типов) описаны в файле windows.h:

Рассмотрим назначение отдельных полей структуры PAINTSTRUCT.

Поле hdc после возврата из функции будет содержать идентификатор полученного контекста отображения, который должен передаваться в качестве параметра функциям интерфейса GDI, выполняющим рисование. Можно использовать также значение идентификатора контекста, возвращенное функцией BeginPaint, так как эти значения одинаковые.

Анализируя содержимое поля fErase, приложение может определить, нужно ли перерисовывать фон окна. Если в этом поле находится значение TRUE, фон окна должен быть перерисован. Такая необходимость может возникнуть в том случае, если в классе, на базе которого создано окно, при регистрации не была выбрана кисть для закрашивания фона (поле hbrBackground структуры WNDCLASS).

Поле rcPaint, которое представляет собой структуру типа RECT, содержит координаты верхнего левого и правого нижнего угла прямоугольника, внутри которого нужно рисовать. Напомним вам формат структуры RECT , описанной в файле windows.h:

Мы уже говорили вам в 11 томе «Библиотеки системного программиста», что при обработке сообщения WM_PAINT приложение должно суметь перерисовать все окно или любую его часть. Сообщение WM_PAINT может попасть в функцию окна в том случае, если все окно или его часть требуют перерисовки. Поле rcPaint в структуре PAINTSTRUCT содержит координаты прямоугольной области, расположенной в окне и требующей перерисовки.

Остальные поля зарезервированы для Windows и не используются приложениями.

Контекст отображения, полученный при помощи функции BeginPaint, необходимо освободить перед завершением обработки сообщения WM_PAINT, вызвав функцию EndPaint :

Функции EndPaint передаются те же параметры, что и функции BeginPaint.

Обычно обработчик сообщения WM_PAINT выглядит следующим образом:

Подобный фрагмент кода вы можете найти в приложениях, описанных в одном из предыдущих томов «Библиотеки системного программиста».

Функции BeginPaint и EndPaint можно использовать только внутри обработчика сообщения WM_PAINT. Если же приложению требуется рисовать во время обработки других сообщений, оно должно получить контекст отображения с помощью функции GetDC . После завершения процедуры рисования перед выходом из обработчика сообщения следует освободить полученный контекст отображения, вызвав функцию ReleaseDC .

Функция GetDC возвращает контекст отображения для окна с идентификатором hwnd:

Полученный таким образом контекст отображения можно использовать для рисования во внутренней области окна (window client region).

Функция ReleaseDC освобождает контекст отображения hdc, полученный для окна hwnd:

Мы еще раз обращаем ваше внимание на необходимость своевременного освобождения общего контекста отображения.

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

Контекст отображения для класса окна

Общий контекст отображения , описанный нами в предыдущем разделе, кешируется операционной системой Windows для ускорения доступа к нему. Однако вы можете создать такой контекст отображения, который хранится отдельно в единственном экземпляре и используется всеми окнами, созданными на базе класса окна. При регистрации такого класса окна вы должны указать стиль CS_CLASSDC :

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

В отличие от общего контекста отображения, приложения, однажды получив контекст отображения для класса окна, могут не освобождать его. То есть для контекста этого типа после функций BeginPaint и GetDC можно не вызывать функции EndPaint и ReleaseDC. Если же приложение вызовет функцию EndPaint или ReleaseDC, они не будут ничего делать и сразу вернут управление. Для уменьшения вероятности ошибки мы рекомендуем вам всегда освобождать контекст отображения, даже если это и не требуется для данного типа контекста.

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

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

Зачем используется контекст отображения класса окна?

Вы можете использовать его в тех случаях, когда по соображениям повышения производительности нежелательно выполнять настройку многочисленных атрибутов контекста отображения после каждого вызова функции BeginPaint или EndPaint. Эту настройку можно выполнить только один раз. Каждый раз, когда функция окна получает контекст отображения класса окна, в нем выполняется настройка только двух атрибутов — области ограничения и начала системы физических координат устройства вывода. Остальные атрибуты остаются без изменений и, следовательно, не требуют повторной настройки.

Личный контекст отображения

Указав в стиле класса окна значение CS_OWNDC , можно добиться того, что для каждого окна, созданного на базе такого класса, Windows создаст отдельную структуру контекста отображения:

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

Однако не будет ошибкой, если приложение будет использовать личный контекст отображения как общий, то есть каждый раз при обработке сообщения WM_PAINT (или другого сообщения, обработчик которого занимается рисованием) оно будет получать контекст и затем отдавать его. Соответствующие функции (BeginPaint, EndPaint, GetDC, ReleaseDC) будут возвращать управление, не выполняя никаких действий.

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

Родительский контекст отображения

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

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

Контекст отображения для окна

Все описанные выше контексты отображения позволяют рисовать только во внутренней области окна (client region). С помощью функции GetWindowDC приложение может получить контекст отображения для окна , позволяющий рисовать в любом месте окна (в области заголовка окна, в области системного меню, рамки окна, кнопок изменения размера и т. п.).

Контекст отображения, полученный для окна, используется аналогично общему контексту отображения. После получения контекста его атрибуты принимают значения по умолчанию. Приложение обязано освобождать этот контекст сразу после использования, вызывая функцию ReleaseDC.

Особенностью данного контекста является то, что в нем выбрана система координат, начало которой находится в левом верхнем углу окна (всего окна, а не его внутренней области).

Такой контекст может использоваться при необходимости заменить стандартные элементы окна (заголовок, полосы просмотра и т. п.) на собственные, или если приложение желает нарисовать что-нибудь в области заголовка или рамки.

Контекст физического устройства

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

В отличие от контекста отображения, контекст физического устройства не получается, а создается, для чего используется функция CreateDC :

Параметр lpszDriver является указателем на строку символов, содержащую имя драйвера, обслуживающего физическое устройство. Имя драйвера совпадает с именем файла *.drv, содержащего сам драйвер и расположенного в системном каталоге Windows.

Имя устройства lpszDevice — это название самого устройства, описанное, например, в файле win.ini в разделе [devices].


Параметр lpszOutput указывает на структуру данных типа DEVMODE, используемую при инициализации устройства вывода. Если при работе с устройством нужно использовать параметры, установленные при помощи приложения Control Panel, параметр lpszOutput следует указать как NULL.

Более подробно вопросы работы с принтером будут рассмотрены в отдельной главе этого тома.

В приведенном ниже примере создается контекст устройства для лазерного принтера HP Laserjet III, подключенного к порту LPT1:, причем в системном каталоге Windows для этого принтера установлен драйвер hppcl5a.drv:

Аналогично, для принтера Epson FX-850, подключенного к порту LPT2:, и работающему через драйвер epson9.drv:

Созданный при помощи функции CreateDC контекст устройства следует удалить (но не освободить), вызвав функцию DeleteDC:

Эта функция возвращает TRUE при нормальном завершении и FALSE при возникновении ошибки.

Контекст для устройства DISPLAY

В некоторых случаях требуется получить контекст отображения, позволяющий приложению рисовать в любом месте экрана дисплея. Такой контекст можно создать при помощи функции CreateDC, указав в качестве имени драйвера строку «DISPLAY «, а в качестве остальных параметров — значение NULL:

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

После использования созданный таким образом контекст отображения следует удалить, вызвав функцию DeleteDC.

Есть еще два способа получения контекста для экрана.

Приложение может получить контекст отображения для всего экрана при помощи функции GetDC, указав в качестве параметра значение NULL:

Полученный таким образом контекст следует освободить после использования при помощи функции ReleaseDC, передав ей вместо идентификатора окна значение NULL:

Еще один способ связан с использованием функции GetDCEx, описание которой будет приведено ниже.

Информационный контекст

Если приложению необходимо получить информацию об устройстве вывода (например, с помощью функции GetDeviceCaps, рассмотренной нами в 11 томе «Библиотеки системного программиста»), оно может создать вместо обычного информационный контекст . Информационный контекст нельзя использовать для рисования, однако он занимает меньше места в памяти.

Информационный контекст создается при помощи функции CreateIC , аналогичной по своим параметрам функции CreateDC:

После использования информационный контекст следует удалить, вызвав функцию DeleteDC.

Контекст для памяти

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

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

Созданный таким образом контекст памяти удаляется при помощи функции DeleteDC.

Использование контекста памяти будет подробно описано в главе, посвященной битовым изображениям bitmap.

Контекст для метафайла

Контекст для метафайла позволяет записывать команды GDI в файл и затем проигрывать такой файл на физическом устройстве вывода. Файл может находиться в памяти или на диске, в последнем случае его можно использовать для переноса графического изображения в другое приложение.

Для создания контекста метефайла используется функция CreateMetaFile :

Параметр lpszFileName должен указывать на строку, содержащую путь к имени файла, в который будут записаны команды GDI, или NULL. В последнем случае создается метафайл в оперативной памяти.

После выполнения рисования в контексте метафайла следует закрыть метафайл, вызвав функцию CloseMetaFile :

Эта функция закрывает метафайл для контекста hdc и возвращает идентификатор метафайла. Идентификатор закрытого метафайла использовать нельзя, так как он не содержит никакой полезной информации.

Что можно сделать с полученным идентификатором метафайла?

Можно скопировать метафайл в обычный дисковый файл, вызвав функцию CopyMetaFile :

Параметр hmf определяет метафайл, параметр lpszFileName содержит путь к имени файла, в который будет записан метафайл .

Можно проиграть метафайл в контексте отображения или контексте устройства, вызвав функцию PlayMetaFile :

Наконец, при помощи функции DeleteMetaFile можно удалить метафайл:

Удаление метафайла с помощью функции DeleteMetaFile делает недействительным идентификатор метафайла hmf и освобождает оперативную память, занятую метафайлом. Если метафайл был создан как обычный дисковый файл, функция DeleteMetaFile не удаляет его с диска.

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

Функция GetDCEx

В программном интерфейсе операционной системы Windows версии 3.1 появилась функция GetDCEx , предоставляющая расширенные возможности для получения контекста отображения:

Функция возвращает идентификатор полученного контекста отображения или NULL при ошибке.

Параметр hwnd задает идентификатор окна, для которого необходимо получить контекст отображения.

С помощью параметра hrgnClip можно определить область ограничения вывода. Эта область может иметь произвольную форму и являться комбинацией нескольких областей ограничения. Использование областей ограничения будет подробно описано дальше в этой главе.

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

Константа Описание
DCX_WINDOW Функция возвращает контекст отображения, позволяющий рисовать во всем окне, а не только в его внутренней области
DCX_CACHE Функция получает общий контекст отображения из кеша Windows, даже если окно создано на базе класса стиля CS_OWNDC или CS_CLASSDC
DCX_CLIPCHILDREN Видимые области всех дочерних окон, расположенных ниже окна hwnd, исключаются из области отображения
DCX_CLIPSIBLINGS Видимые области всех окон-братьев (окон, имеющих общих родителей), расположенных выше окна hwnd, исключаются из области отображения
DCX_PARENTCLIP Для отображения используется вся видимая область родительского окна, даже если родительское окно создано с использованием стилей WS_CLIPCHILDREN и WS_PARENTDC. Начало координат устанавливается в левый верхний угол окна hwnd
DCX_EXCLUDERGN Если указан этот флаг, при выводе будет использована область ограничения, заданная параметром hrgnClip
DCX_INTERSECTRGN Используется пересечение области ограничения, заданной параметром hrgnClip, и видимой области полученного контекста отображения
DCX_LOCKWINDOWUPDATE Этот флаг разрешает рисование в окне, заблокированном для рисования функцией LockWindowUpdate . Флаг можно использовать при необходимости рисовать, например, рамку, выделяющую произвольную область экрана

Контекст отображения, полученный функцией GetDCEx, следует освободить после использования при помощи функции ReleaseDC.

Добавление к типу WINAPI. int WINAPI, HANDLE WINAPI

пишут такие типы как HANDLE WINAPI или int WINAPI и другие, а что добавление WINAPI меняет? И сам по себе WINAPI что за тип ?

2 ответа 2

Это макрос, определённый в одном из заголовочных файлов

Что именно он делает в текущей конфигурации — нужно посмотреть в макросе (а макрос может по-разному развернуться в зависимости от других определений)

Обычно определяет соглашение о вызове __stdcall

Символ WINAPI никакого отношения к типу не имеет. В Windows API этот макрос используется с функциями и определяет соглашение о вызове (calling convention). Поскольку WinAPI использует паскалевское соглашение о вызове, то этот символ определяется как __stdcall .

Поскольку вероятность внезапного изменения соглашения о вызове в WinAPI чуть ниже абсолютного нуля, то этот символ можно было бы вообще не определять, а писать сразу __stdcall . Но во-первых, это красиво смысл в этом макросе все-таки есть. Наличие символа WINAPI в объявлении функции сразу указывает что это именно функция WinAPI — в отличие, к примеру, от функций обратного вызова, у которых вместо WINAPI стоит CALLBACK , и который тоже транслируется в __stdcall . Благодаря этим макросам сразу видно назначение функции.

Всё ещё ищете ответ? Посмотрите другие вопросы с метками winapi или задайте свой вопрос.

Похожие

Подписаться на ленту

Для подписки на ленту скопируйте и вставьте эту ссылку в вашу программу для чтения RSS.

дизайн сайта / логотип © 2020 Stack Exchange Inc; пользовательское содержимое попадает под действие лицензии cc by-sa 4.0 с указанием ссылки на источник. rev 2020.11.12.35403

В чем суть WinApi?

Windows API — это самый низкоуровневый интерфейс Windows, доступный прикладному программисту — в том плане, что он на долгосрочной поддержке и не изменится с Windows 11.

Поверх Windows API работают все BOOST и STL.

Пример: читать файл в 130 мегабайт по одному байту. Добавив асинхронного чтения через OVERLAPPED, я сумел это сделать менее чем за 2 секунды (это был поток общего назначения с виртуальными read(), write() и seek(); специализированный прикладной буфер даст ещё выигрыша, но и это хорошо). То же самое через FILE* — не дождался.

Пример второй, всё те же файлы. Дело в том, что Excel захватывает свои файлы на всё время, пока он открыт. Закрывать? — плохой выбор. Добавив один флажок в CreateFile, документы всё-таки стало возможным открывать при работающем Excel.

Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL