Letysite.ru

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

Dword winapi c

Почему в C++ мы используем DWORD, а не unsigned int?

Я не боюсь признаться, что я немного новичок в C++, так что это может показаться глупым вопросом, но.

Я вижу, что DWORD используется повсюду в примерах кода. Когда я смотрю, что на самом деле означает DWORD, это, по-видимому, просто неподписанный int (от 0 до 4,294,967,295). Итак, мой вопрос заключается в следующем: почему у нас есть DWORD? Что же это дает нам, чего не дает интегральный тип ‘unsigned int’? Это как-то связано с мобильностью и различиями в машинах?

4 Ответов

DWORD не является типом C++, он определен в .

Причина в том, что DWORD имеет определенный диапазон и формат, на который полагаются функции Windows, поэтому, если вам требуется этот конкретный диапазон, используйте этот тип. (Или как они говорят: «когда ты в Риме, делай так, как делают римляне.») Для вас это соответствует unsigned int , но это может быть не всегда так. Чтобы быть в безопасности, используйте DWORD , когда ожидается DWORD , независимо от того, что это может быть на самом деле.

Например, если бы они когда-либо изменили диапазон или формат unsigned int , они могли бы использовать другой тип, чтобы подчинить DWORD тем же требованиям, и весь код, использующий DWORD , был бы none-the-wiser. (Аналогично, они могут решить, что DWORD должен быть unsigned long long , изменить его, и весь код, использующий DWORD , будет none-the-wiser.)

Также обратите внимание, что unsigned int не обязательно имеет диапазон от 0 до 4,294,967,295. Видеть здесь .

Когда MS-DOS и Windows 3.1 работали в 16-разрядном режиме, слово Intel 8086 составляло 16 бит, Microsoft WORD-16 бит, Microsoft DWORD-32 бита, а типичный неподписанный int компилятора составлял 16 бит.

Когда Windows NT работал в 32-разрядном режиме, слово Intel 80386 составляло 32 бита, Microsoft WORD-16 бит, Microsoft DWORD-32 бита, а типичный неподписанный int компилятора-32 бита. Имена WORD и DWORD больше не были самоописательными, но они сохранили функциональность программ Microsoft.

Когда Windows работает в 64-битном режиме, процессор Intel составляет 64 бит, Майкрософт WORD-16 бит, Майкрософт DWORD-это 32 бита, и типичного компилятора неподписанных int-это 32 бита. Имена WORD и DWORD больше не являются самоописательными, AND unsigned int больше не соответствует принципу наименьших сюрпризов, но они сохраняют функциональность множества программ.

Я не думаю, что WORD или DWORD когда-нибудь изменятся.

SDK разработчики предпочитают определять свои собственные типы с помощью typedef. Это позволяет изменять базовые типы только в одном месте, без изменения всего клиентского кода. Важно следовать этой конвенции. DWORD вряд ли будет изменен, но типы, такие как DWORD_PTR, отличаются на разных платформах, таких как Win32 и x64. Итак, если какая-то функция имеет параметр DWORD, используйте DWORD, а не unsigned int, и ваш код будет скомпилирован во всех будущих версиях заголовков windows.

Для себя я бы предположил, что unsigned int является специфичной платформой. Целое число может быть 8 бит, 16 бит, 32 бита или даже 64 бита.

DWORD с другой стороны, указывает свой собственный размер, который является двойным словом. Word — это 16 бит, поэтому DWORD будет называться 32-битным по всей платформе

Похожие вопросы:

предупреждение: сужающее преобразование ‘(stride * 4u) ‘из ‘unsigned int’ в’ WORD ‘ внутри < >неправильно сформировано в C++11 [-Wnarrowing] Я не могу понять, почему я.

Мой вопрос прост (я надеюсь)о синтаксисе c относительно объявления указателя. Я полностью осведомлен о том, как объявить указатель, как его использовать и каковы эффекты, например, следующим.

Я видел это из C Primer Plus, 6-го издания , вопросы обзора в главе 3. Вопрос: Ответ в приложении а: Обратите внимание d.0XAA, мой ответ int константа, шестнадцатеричный формат, но ответ unsigned.

Возможный Дубликат : сравнение unsigned int и signed char int j = 10; unsigned int i = 10; if( j > -1 ) printf(1n); else printf(2n); if( i > -1 ) printf(3n); else printf(4n); Выход есть .

Я получаю эту ошибку и не могу ее исправить. Любая помощь приветствуется. Спасибо. ошибка C2440: ‘=’: не удается преобразовать из ‘DWORD *’ в ‘unsigned int’ IntelliSense: значение типа DWORD *.

Error: Cannot convert DWORD* to ‘long unsigned int*’ for argument 1 to int tea_encrypt(long unsigned int*,) Вот этот код : bool CLZObject::Encrypt(DWORD * pdwKey) < if.

Поскольку long шире, чем int, дающий Нижний порядок unsigned long должен возвращать что-то большее, чем просто Нижний порядок unsigned int. Почему функция все еще знает, что такое нижний порядок.

Эта программа <$APPTYPE CONSOLE> <$TYPEDADDRESS ON>uses Winapi.Windows; procedure Foo(P: PDWORD); begin end; procedure Bar; var dw: DWORD; begin Foo(@dw); end; begin end. компилируется в XE3, но не.

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

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

Потоки (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.

Читать еще:  Как открыть файл docm в word

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(LPVOID); int main() < cout

Классовая оболочка для потоков

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

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

Многозадачность — один из наиболее трудных аспектов программирования. Поэтому, для нее тем более важно обеспечить простой набор абстракций и инкапсулировать их в хорошей объектно-ориентированной оболочке. В ОО мире, естественным аналогом потока, являющегося, чисто процедурной абстракцией, служит «Активный объект». Активный объект обладает удерживаемым потоком, который асинхронно выполняет некоторые задачи. Этот поток имеет доступ к всем внутренним (закрытым) данным и методам объекта. Открытый интерфейс Активного объекта доступен внешним агентам (таким как основному потоку, или потоку, несущему сообщения Windows). Поэтому, они могут манипулировать состоянием объекта также, как эта манипуляция осуществляется из удерживаемого потока. Хотя, режим управления при этом сильно ограничен.

Активный Объект сформирован как каркас по имени ActiveObject. Построение производного класса, как предполагается, обеспечивает реализацию для чистых виртуальных методов InitThread, Run и Flush (также как и написание деструктора).

virtual void InitThread() = 0;

virtual void Run() = 0; virtual void FlushThread() = 0;

static DWORD WINAPI ThreadEntry(void *pArg);

Конструктор класса ActiveObject инициализирует удерживаемый поток, передавая ему указатель функции, которую предполагается выполнить и указатель «this» на себя. Мы должны отключить предупреждение, сигнализирующее об использовании «this» до полного создания объекта. Мы знаем, что этот объект не будет использоваться раньше положенного, потому что поток создается в неактивном состоянии. Предполагается, сто конструктор производного класса вызывает _thread.Resume() чтобы активизировать поток.

// The constructor of the derived class

// at the end of construction

ActiveObject::ActiveObject() : _isDying (0),

#pragma warning(disable: 4355) // ‘this’ used before initialized

#pragma warning(default: 4355)

Метод Kill вызывает виртуальный метод FlushThread — это необходимо для завершения потока из любого состояния ожидания и дает ему возможность запустить _isDying для проверки флажка.

// Let’s make sure it’s gone

Мы также имеем каркас для функции ThreadEntry (это — статический метод класса ActiveObject, поэтому мы можем определять соглашение о вызовах, требуемое API). Эта функция выполняется удерживаемым потоком. Параметр, получаемый потоком от системы является тем, который мы передали конструктору объекта потока — это указатель «this» Активного Объекта. API ожидает void-указатель, поэтому мы должны делать явное приведение указателя на ActiveObject. Как только мы овладеваем Активным Объектом, мы вызываем его чистый виртуальный метод InitThread, делать все специфические для реализации приготовления, а затем вызываем основной рабочий метод Run. Реализация метода Run оставлена клиенту каркаса.

DWORD WINAPI ActiveObject::ThreadEntry(void* pArg) <

ActiveObject* pActive = (ActiveObject*)pArg;

Объект Thread — это тонкая инкапсуляция API. Обратите внимание на флажок CREATE_SUSPENDED, который гарантирует, что нить не начнет выполняться прежде, чем мы не закончим конструирование объекта ActiveObject.

Thread(DWORD(WINAPI* pFun)(void* arg), void* pArg) <

0, // Security attributes

pFun, pArg, CREATE_SUSPENDED, &_tid);

DWORD _tid; // thread id

Синхронизация — это то, что действительно делает многозадачный режим столь интенсивно используемым. Давайте, начнем со взаимных исключений. Класс Mutex — тонкая инкапсуляция API. Вы внедряете Mutexes (мутации) в ваш Активный Объект, а затем используете их через Блокировки. Блокировка (Lock) — умный объект, который создается на стеке. В результате чего, во время обслуживания, ваш объект защищен от любых других потоков. Класс Lock — одно из приложений методологии Управления ресурсами. Вы должны поместить Lock внутри всех методов вашего Активного Объекта, которые разделяют доступ к данным с другими потоками.

friend class Lock;

// Acquire the state of the semaphore

Lock(Mutex& mutex) : _mutex(mutex) <

// Release the state of the semaphore

Событие — это сигнальное устройство, которое потоки используют, чтобы связаться друг с другом. Вы внедряете Событие (Event) в ваш активный объект. Затем Вы переводите удерживаемый поток в состояние ожидания, пока некоторый другой поток не освободит его. Не забудьте однако, что, если ваш удерживаемй поток ожидает события, он не может быть завершен. Именно поэтому Вы должны вызывать Release из метода Flush.

// start in non-signaled state (red light)

// auto reset after every Wait

_handle = CreateEvent(0, FALSE, FALSE, 0);

// put into signaled state

// Wait until event is in signaled (green) state

Чтобы увидеть, как эти классы могут быть использованы, я предлагаю небольшую подсказку. Вы можете перейти к странице, которая объясняет, как класс ActiveObject используется в Частотном анализаторе для асинхронной модификации дисплеев. Или, Вы можете изучить более простой пример Наблюдателя Папки, который спокойно ждет, отображая папки, и пробуждается только, когда присходит изменение ее содержимого.

Жаль, что я не могу сказать, что программирование потоков является простым. Однако, оно будет проще, если Вы станете использовать правильные примитивы. Это примитивы, которые я рекламировал: ActiveObject, Thread, Mutex, Lock и Event. Некоторые из них фактически доступны в MFC. К примеру, там есть блокирующий объект CLock (а может быть — это ЧАСЫ [CLock — игра слов]?) деструктор которого управляет, критической секцией (он немного менее удобен из-за «двухшаговой» конструкции: Вы должны создать его, а затем выбирать его в два отдельных шага — как будто бы вы захотели создать его, а затем передумать). Другое отличие: MFC предлагает только некоторую тонкую фанеру над API и ничего нового.

Вы можете также распознать в некоторых из механизмов, которые я представил здесь, аналоги из языка программирования Java. Конечно, когда Вы имеете свободу проектирования многозадачного режима в языке, Вы можете позволять себе быть изящными. Их версия ActiveObject называется Runnable, и она имеет метод run. Каждый объект Java потенциально имеет встроенный mutex и все, что Вам надо сделать, чтобы осуществить блокировку, — это объявить метод (или область) засинхронизированным. Точно так же события реализованные с ожиданиями подтверждениями вызываются внутри любого синхронизированного метода. Поэтому, если Вы знаете, как программировать на Java, Вы знаете, как программировать на C++ (как будто есть знатоки Java, не осведомленные о C++).

C++ потоки в WIN32

Потоки всегда создаются в контексте какого-либо процесса, и вся их жизнь проходит только в его границах. На практике это означает, что потоки исполняют код и манипулируют данными в адресном пространстве процесса. Если два или более потока выполняются внутри одного процесса, они делят одно адресное пространство.
Любой поток (thread) состоит из двух компонентов:

  • объекта ядра, через который ОС управляет потоком. Там же хранится статистическая информация о потоке.
  • Стека потока, который содержит параметры всех функций и локальные переменные, необходимые потоку для выполнения кода.

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

    Читать еще:  Файл ms word

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

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

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

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

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

    Для создания своего потока необходимо использовать функцию CreateThread: Код

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

    Система выделяет память под стек потока из адресного пространства процесса. Новый поток выполняется в контексте того же процесса, что и родительский поток. Поэтому он получает доступ ко всем описателям объектов ядра, всей памяти и стекам всех потоков в процессе. За счет этого потоки в рамках одного процесса могут легко взаимодействовать друг с другом.

    CreateThread — это Windows-функция, создающая поток. Если вы пишете код на С/С++ не вызывайте ее. Вместо нее Вы должны использовать _beginthreadex из библиотеки Visual C++. Почему это так важно в наших следующих выпусках.

    DwStackSize — параметр определяет размер стека, выделяемый для потока из общего адресного пространства процесса. При передаче 0 — размер устанавливается в значение по умолчанию.

    LpStartAddress — указатель на адрес входной функции потока.

    LpParameter — параметр, который будет передан внутрь функции потока.

    DwCreationFlags — принимает одно из двух значений: 0 — исполнение начинается немедленно, или CREATE_SUSPENDED — исполнение приостанавливается до последующих указаний.

    LpThreadId — Адрес переменной типа DWORD в который функция возвращает идентификатор, приписанный системой новому потоку.

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

    Функция потока, возвращая управление, гарантирует корректную очистку всех ресурсов, принадлежащих данному потоку. При этом:

  • любые С++ объекты, созданные данным потоком, уничтожаются соответствующими деструкторами;
  • система корректно освобождает память, которую занимал стек потока;
  • система устанавливает код завершения данного потока. Его функция и возвращает;
  • счетчик пользователей данного объекта ядра (поток) уменьшается на 1.

    При желании немедленно завершить поток изнутри используют функцию ExitThread(DWORD dwExitCode).

    При этом освобождаются все ресурсы ОС, выделенные данному потоку, но С С++ ресурсы (например, объекты классов С++) не очищаются. Именно поэтому не рекомендовано завершать поток, используя эту функцию.

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

    Как и для CreateThread для библиотеки Visual C++ существует ее аналог _endthreadex, который и стоит использовать. Об причинах в следующем выпуске.

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

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

    При завершении процесса происходит следующее.

    Завершение потока происходит принудительно. Деструкторы объектов не вызываются, и т.д. и т.д.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    WinAPI окна

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

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

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

    Читать еще:  Ms word c

    Функция WinMain

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Заключение

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

  • Ссылка на основную публикацию
    Adblock
    detector