В os windows кто исполняет вызовы процедур описанных в msdn

Обновлено: 07.07.2024

В любой операционной системе существует набор базовых концепций и базовых механизмов, ставших неотъемлемой частью теории и практики ОС. Например, в "Создание ОС Windows. Структура ОС Windows" были приведены краткие описания процессов и потоков. Ниже будет подробно рассмотрена реализация ряда других важных концепций современных ОС.

Из теории ОС известно [ Карпов ] , [ Таненбаум ] , [ Столлингс ] , что современные ОС реализуют поддержку системных вызовов, обработку прерываний и исключительных ситуаций, которые относят к основным механизмам ОС.

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

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

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

Прогон программы реализующей структурную обработку исключений

В качестве упражнения рекомендуется выполнить прогон программы, в которой произведена обработка деления на 0. Особенности применения операторов try и except описаны в MSDN.

Реализация прерываний, системных вызовов и исключений в ОС Windows

Рассмотрим реализацию основных механизмов операционной системы в ОС Windows . Следует отметить, что терминология корпорации Microsoft несколько отличается от общепринятой. Например, системные вызовы называются системными сервисами, а под программным прерыванием (см. прерывания DPC и APC ) понимается выполнение специфичных функций ядра, требующих прерывания работы текущего процесса.

Ловушки

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

В типичном случае сохраняются и впоследствии восстанавливаются:

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

Эта информация специфицирована в структуре CONTEXT (файл winnt.h), и может быть получена пользователем с помощью функции GetThreadContext .

Адрес части ядра ОС, ответственной за обработку данного конкретного события определяется из вектора прерываний, который номеру события ставит в соответствие адрес процедуры его первичной обработки. Это оказывается возможным, поскольку все события типизированы и их число ограничено. Для асинхронных событий их номер определяется контроллером прерываний , а для синхронных - ядром. В [ Руссинович ] описана процедура просмотра вектора прерываний, который в терминологии корпорации Microsoft называется таблицей диспетчеризации прерываний ( interrupt dispatch table, IDT ), при помощи отладчика kd. Например, для x86 процессора прерыванию от клавиатуры соответствует номер 0x52 , системным сервисам - 0x2e , а исключительной ситуации, связанной со страничной ошибкой, - 0xE (см. рис. 3.1рс. 3.1).

Вектор прерываний (IDT)

После прохождения первичной обработки для каждого события предусмотрена процедура его последующей обработки другими частями ОС. Например, обработка системного сервиса (системного вызова) предполагает передачу управления по адресу 0x2e , где располагается диспетчер системных сервисов, которому через регистры EAX и EBX передаются номер запрошенного сервиса и список параметров, передаваемых этому системному сервису.

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

В качестве примера рассмотрим процедуру обработки создания файла. Вызов Win32 функции CreateFile() генерирует передачу управления функции NtCreateFile исполнительной системы, ассемблерный код которой содержит следующие операции:

Рисунок 3.2 иллюстрирует дальнейшую обработку данного сервиса.

Пример обработки системного вызова (системного сервиса).


Рис. 3.2. Пример обработки системного вызова (системного сервиса).

Приоритеты. IRQL

В большинстве операционных систем аппаратные прерывания имеют приоритеты, которые определяются контроллерами прерываний. Однако ОС Windows имеет свою аппаратно-независимую шкалу приоритетов, которые называются уровни запросов прерываний ( interrupt request levels, IRQL), и охватывает не только прерывания, а все события, требующие системной обработки. В таблице 3.1 приведены значения IRQL уровней для x86 систем.

Обрабатываемые события обслуживаются в порядке их приоритета, и события с более высоким приоритетом вытесняют обработку событий с меньшим приоритетом. При возникновении события с высоким приоритетом IRQL процессора повышается до уровня данного события. После его обработки могут проявить себя замаскированные менее приоритетные события, которые, в свою очередь, могут быть обработаны по обычной схеме. Текущий уровень приоритета хранится в данных, описывающих состояние процессора, и может быть определен системным отладчиком kd или посредством вызова функции KeGetCurrentIrql .

Значения IRQL для аппаратных прерываний расставляются диспетчером Plug and Play с помощью уровня абстрагирования от оборудования HAL , а для остальных событий - ядром. Таким образом, уровень IRQL определяется источником события , что имеет иной смысл, нежели приоритеты в стратегии планирования потоков. Разбиение на IRQL уровни является основным механизмом упорядочивания по приоритетам действий операционной системы.

Можно сказать, что в ОС Windows действует двухуровневая схема планирования. Приоритеты высшего уровня (в данном случае IRQLs) определяются аппаратными или программными прерываниями , а приоритеты низшего уровня (в своем диапазоне от 0 до 31) устанавливаются для пользовательских потоков, выполняемых на нулевом уровне IRQL, и контролируются планировщиком.

На нулевом ( PASSIVE LEVEL) уровне IRQL работают пользовательские процессы и часть кода операционной системы. Программа, работающая на этом уровне, может быть вытеснена почти любым событием, случившимся в системе. Большинство процедур режима ядра старается удерживать IRQL уровень процессора как можно более низким.

IRQL уровни 1 ( APC LEVEL) и 2 (DISPATCH LEVEL) предназначены для так называемых программных (в терминологии Microsoft) прерываний соответственно: асинхронный вызов процедуры - APC (asynchronous procedure call) и отложенный вызов процедуры - DPC (deferred procedure call). Если ядро принимает решение выполнить некоторую системную процедуру, но нет необходимости делать это немедленно, оно ставит ее в очередь DPC и генерирует DPC прерывание. Когда IRQL процессора станет достаточно низким, эта процедура выполняется. Характерный пример - отложенная операция планирования. Из этого следует, что код, выполняемый на IRQL уровне, выше или равном 2, не подвержен операции планирования. Асинхронный вызов процедур - механизм, аналогичный механизму DPC , но более общего назначения, в частности, доступный пользовательским процессам.

IRQL уровни 3-26 относятся к обычным прерываниям от устройств. Более подробное описание IRQL уровней имеется в [ Руссинович ] .

Заключение

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

Системные вызовы в операционных системах семейства MS Windows, начиная с версии Windows 95, реализованы на основе интерфейса прикладного программирования, получившего название «Win32 API». Под Win32 API понимают совокупность функций, предоставляющих программисту возможность создавать приложения для операционных систем Windows 95/98/ME/NT/2000/XP, базирующихся на использовании 32-х разрядных процессоров Intel, начиная с i386 (и его аналогов). При этом, несмотря на различия между версиями операционной системы, основное множество функций API для них одно и то же. Большинство функций API доступны для вызова из программ на любом исходном языке программирования (в том числе и на ассемблере).

Функции API хранятся в так называемых динамических библиотеках (Dynamic Link Library), которые размещаются в файлах с расширением dll, таких как kernel.dll, user32.dll, gdi32.dll и некоторых других. Эти файлы размещаются в системном каталоге Windows (обычно C:\WINDOWS\SYSTEM).

Фактически функции API для Windows играют ту же самую роль, что и программные прерывания для MS DOS, однако вызов функций API производится более простым и привычным для программиста способом - через символические имена. Например, функция удаления файла вызывается по имени DeleteFile, функция установки системного времени - SetSystemTime и т.д.

При программировании на ассемблере передача параметров функциям Win32 осуществляется не через регистры процессора, а через стек. Результат работы функции API помещается в регистр EAX. Более сложные типы данных возвращаются через адреса памяти (указатели), передаваемые функции в виде входных параметров.

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

Подробное описание функций WIN32 API и их параметров можно найти в литературе 3, в файле справки win32.hlp (обычно размешается в каталоге C:\Program Files\Borland Shared\MSHelp\win32.hlp), а также на многочисленных сайтах Интернет (например, /library).

3.2. Типы данных, применяемые в Win32 API

Помимо совокупности функций API Windows поддерживает целый ряд специальных типов данных (например, HINSTANCE, HWND, LPSTR и т.п.), не совпадающих со стандартными типами, определенными в основных языках программирования. Использование типов, специально «изобретенных» для Windows, упрощает написание программы, делает ее более ясной и читабельной. Некоторые простейшие типы данных Windows приведены в таблице 2.

Таблица 2. Основные типы данных Windows

Логическая переменная, принимающая значения TRUE (ИСТИНА) или FALSE (ЛОЖЬ).

Байтовое число без знака

32-разрядное целое число без знака.

32-разрядное целое число со знаком.

32-разрядное целое число без знака.

Дальний указатель на строку символов с завершающим нулевым символом

16-разрядное целое число без знака.

Дескриптор объекта (четырехбайтовое целое число)

Особую роль в Windows играют специальные переменные - дескрипторы (хэндлы). Дескрипторы – это уникальные целые четырехбайтовые числа, применяемые для идентификации объектов, которые создаются и используются в системе. Большинство дескрипторов являются значениями индексов внутренних таблиц, которые Windows использует для доступа и управления своими объектами. Прикладная программа может получить или изменить данные, связанные с каким-либо объектом, только с помощью вызова функции API с указанием дескриптора соответствующего объекта. Для каждого вида объектов используется специальный дескрипторный тип, например – HWND – дескриптор окна, HDC - дескриптор контекста устройства, HFILE – дескриптор открытого файла, HLOCAL - дескриптор локального блока памяти и т.д. Общим для всех дескрипторов является наличие в описании первого символа “H”.

typedef struct _SYSTEMTIME WORD wYear; //текущий год
WORD wMonth; //номер месяца (январь-1, и т.д.)
WORD wDayOfWeek; //день недели (вск-0, пн-1, …)
WORD wDay; //день месяца
WORD wHour; //час
WORD wMinute; //минуты
WORD wSecond; //секунды
WORD wMilliseconds; //миллисекунды

При написании программ на языке C/C++ типы данных Windows и прототипы функций API определяются во включаемых заголовочных файлах Win32, основным из которых является файл windows.h. Помимо типов данных в этом файле определено более 1000 констант. Имена констант стандартизированы: они пишутся заглавными буквами и имеют вид «префикс_пояснение». Например, IDC_RESOURCE,CS_HREDRAW, WM_QUIT, DRIVE_UNKNOWN и т.п. Константы также широко применяются при установке значений параметров вызова функций API и при проверке результатов их выполнения.

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

каждое слово в имени переменной пишется с прописной буквы и слитно с другими словами. Например, идентификатор для обозначения какой-то переменной может выглядеть следующим образом - MyVariable, YourVariable, VariableForSavingAnotherVariable и т.п.;

каждый идентификатор предваряется несколькими строчными символами, определяющими его тип. Например, целочисленная переменная MyVariable будет выглядеть как nMyVariable (n – общепринятый префикс для целочисленных переменных), символьная (char) переменная YourVariable превращается в cYourVariable. Указатель на строку символов, заканчивающуюся нулевым байтом, следут записать lpszVariableForSavingAnotlierVariable (lpsz - сокращение от Long Point то String with Zero). Как видим, префикс указателя может комбинироваться с другими префиксами. Примеры подобных префиксов приведены в таблице 3.

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

RET - это команда возврата из функции. Сама функция вызывается командой CALL. Вот так например.

  • Через регистры
  • Через глобальные переменные
  • Через стек

Но это еще не все. Когда параметров много, передать их можно с права налево или наоборот. Итак, первая проблема разнообразия способов вызова заключается в том, как передаются параметры. Через что и в каком направлении. Но и это еще не все. Если параметры допустим с стеке, то есть два варианта кто будет их оттуда убирать. Для работы со стеком в ассемблере есть две команды pop и push. С помощью них что-то помещается и извлекается из стека. При вызове функции ее адрес помещается в стек. При вызове Ret и возврата этот адрес из стека извлекается. Итак, при вызове параметры помещаются в стек, после вызова они должны из стека очиститься, так кто это делает кто вызывал или кого вызывают ? Вот все эти причины и порождают разнообразие методов вызова функций. Но и это еще не все. Еще один вопрос как будут интерпретироваться имена. Обычно когда Вы пишите функции на C++ вы об этом не задумываетесь, так как все вызовы идут по одному правилу. Это правило устанавливается в Project settings.

По умолчанию используется _cdecl - это стандартные правила вызовов C++. Windows, например, использует _stdcall. Поэтому к функциям Windows нам нужно обращаться используя stdcall. Что делать ? Перед функцией можно поставить модификатор, который будет указывать как функция вызывается, то есть по каким правилам. Вот так это делается:

А вообще эти вызовы окутывают в определения, например WINAPI не что иное как:

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

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

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

Новое окно VisualStudio WinAPI

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

WinAPI Окна

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

WinAPI Окно

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

WinAPI Привет Мир

Перед тем как мы начнём рассматривать код, давайте поговорим о типах данных в 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 - всё, что касается окон и обработки событий.

Функция WinMain

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

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

int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow)

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

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

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

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

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

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

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

WNDCLASS windowClass = < 0 >; windowClass.lpfnWndProc = WindowProc; windowClass.hInstance = hInstance; windowClass.lpszClassName = "HELLO_WORLD"; RegisterClass(&windowClass);

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

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

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

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

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

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

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

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

HWND hwnd = CreateWindow( windowClass.lpszClassName, "Пустое WinAPI окно - Привет Мир!", WS_OVERLAPPEDWINDOW, 100, 50, 1280, 720, nullptr, nullptr, hInstance, nullptr);

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

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

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

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

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

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

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

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

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

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

LRESULT __stdcall WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) < switch (message) < case WM_DESTROY: PostQuitMessage(0); return 0; >return DefWindowProc(hWnd, message, wParam, lParam); >

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

Заключение

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

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