1. Главная страница » Компьютеры » Hinstance c что это

Hinstance c что это

Автор: | 16.12.2019

Вам еще не надоело создавать консольные приложения? Я так и знал: Ну что ж, тогда я не зря старался при написании данного материала, который обязан пролить свет на программирование в среде Windows, и научить вас создавать полноценные оконные приложения, в зависть вашему соседу, который еще этого не умеет.

Система, основанная на сообщениях

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

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

Win32 API

Win32 API (Application Programming Interface — интерфейс прикладного программирования) — это набор функций, позволяющих программисту создавать приложения для Windows. Win32 API является основой для каждой Windows-программы.

Программисты, пишущие на С++ уже привыкли, что точкой входа в программу является функция main(). Но в системе Windows это не так. Все Win32-приложения используют в качестве точки входа в программу функцию WinMain. Ее объявление можно найти в заголовочном файле winbase.h:

Давайте рассмотрим все ее аргументы:

  • hInstance — идентификатор экземпляра приложения.
  • hPrevInstance — идентификатор предыдущего экземпляра приложения. В Win32 он всегда равен нулю.
  • lpCmdLine — указатель на командную строку.
  • nShowCmd — флаги для окна.

Как видите — все не так сложно! Вас, наверное, удивили только какие-то непонятные типы данных. Не волнуйтесь — разберемся.

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

При первом взгляде на исходный код Windows-приложения, начинающих программистов начинают пугать "странные" типы данных, используемые в программе. Но это только на первый взгляд. На самом, деле разобраться в них вам не составит особого труда. Основные типы данных Win32 API приведены в таблице 1.

BOOL, BOOLEAN Булев. Имеет только 2 значения: TRUE или FALSE
CHAR 8-битный символ (ANSI Windows)
WCHAR 16-битный символ (Unicode)
TCHAR CHAR или WCHAR (если используется Unicode)
USHORT, WORD Целое беззнаковое 16-битное число
DWORD, DWORD32, UINT32 Целое беззнаковое 32-битное число
DWORD64, UINT64, ULONGLONG Целое беззнаковое 64-битное число
FLOAT Число с плавающей точкой
SHORT Целое знаковое 16-битное число
INT, INT32, LONG, LONG32 Целое знаковое 32-битное число
INT64, LONG64, LONGLONG Целое знаковое 64-битное число
VOID Пустой тип

Структура Windows-программ

Каждая Windows-программа состоит как минимум из двух основных функций. Это WinMain и функция окна.

Давайте напишем простую программу, создающую пустое окно. Для этого в Visual C++ создайте пустой проект Win32 Application, добавьте новый файл (например, myprog.cpp), и вставьте туда следующий код:

Теперь давайте рассмотрим приведенный код подробнее.

К сожалению, привести здесь описания всех типов данных, структур, функций и т.д., используемых в Win32 API, не представляется возможным, поэтому советую обратиться к официальному источнику, где можно найти всю необходимую информацию о программировании под Windows — Microsoft Developer Network (msdn.microsoft.com).

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

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

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

После регистрации класса окна можно приступать к созданию самого окна. Для этого используется функция CreateWindow(), которая возвращает хендл создаваемого окна.

Создав окно, нам необходимо отобразить его на экране. Этим занимается функция ShowWindow(), которая принимает в качестве аргументов идентификатор окна, и флаг, указывающий на способ отображения окна (в данном случае SW_SHOW, определяющий, что окно необходимо показать на экране). Затем, с помощью функции UpdateWindow() мы посылаем нашему окну сообщение WM_PAINT, указывающее на необходимость перерисовать клиентскую область окна.

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

Читайте также:  Maestro rainbow 6 siege

Функция GetMessage() возвращает ненулевое значение, поэтому цикл не завершается до момента завершения программы. При завершении программы окну посылается сообщение WM_QUIT, которое является единственным сообщением, при получении которого функция GetMessage() возвращает ноль, и цикл обработки сообщений завершается, а код выхода из программы, хранящийся в элементе wParam структуры MSG, возвращается функцией WinMain.

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

  • hWnd — идентификатор окна.
  • msg — код текущего сообщения.
  • wParam и lParam — дополнительная информация о сообщении.

В Windows существует более тысячи стандартных сообщений. Конечно же, программист не должен обрабатывать их все. Для этого существует функция DefWindowProc(), которая обрабатывает переданное ей сообщение по умолчанию. Таким образом, вы должны обрабатывать только те сообщения, обработка по умолчанию которых вас не устраивает. Также, функция DefWindowProc() не обрабатывает сообщение WM_DESTROY, поэтому вы должны предусмотреть его обработку самостоятельно. В приведенном примере, при получении окном сообщения WM_DESTROY, мы, с помощью функции PostQuitMessage(), ставим в очередь сообщение WM_QUIT, чтобы завершить работу программы.

Заметьте, каким образом сообщения обрабатываются по умолчанию. В структуре switch оконной процедуры предусмотрена метка default, которая пересылает все необрабатываемые нашей программой сообщения функции DefWindowProc() и возвращает результат этой функции. А если сообщение обрабатывается нашей программой, тогда возвращается ноль.

Вот оно, чудо

Теперь, разобравшись с кодом программы, откомпилируйте созданный проект и запустите приложение. Вы увидите пустое окно, с которым уже можно выполнять стандартные действия — перемещать, изменять размеры, сворачивать/разворачивать, и даже закрыть! : Все это достигается одним единственным стилем окна WS_OVERLAPPEDWINDOW, определенным при создании окна функцией CreateWindow().

Примечание: в элементе style, структуры WNDCLASS, определяется общий стиль для всех окон данного класса. Следует заметить, что стиль класса это не тоже самое что и стиль окна, указанный в вызове функции CreateWindow(). Тот стиль, который устанавливается посредством функции CreateWindow(), является индивидуальным стилем окна, а не общим стилем, определенным в классе.

Ресурсы программы

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

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

Автор: Яковлев Игорь Сергеевич
Источник: RSDN Magazine #4-2005
Опубликовано: 30.12.2005
Исправлено: 16.03.2006
Версия текста: 1.0
От редакции
Предисловие
First Blood
You have a Message!
CLASSные окна
Our Windows
План полета
Итого
PS

От редакции

ПРИМЕЧАНИЕ

Данная статья является введением в программирование на WinAPI. Важно понимать, что она носит скорее информационный характер, чем служит примером кода для реальных приложений. Дело в том, что WinAPI создавался для языка С, и имеет целый ряд недостатков в применении, как-то: невысокая безопасность, большой объем ручного кодирования для решения простейших задач, С-стиль, плохо выглядящий в C++-приложениях. Практически любое средство разработки на языке C++ для Windows включает те или иные высокоуровневые C++-библиотеки (MFC, ATL/WTL для Visual C++, VCL для C++ Builder), значительно упрощающие разработку Windows-приложений, и делающие ее более безопасной.

Предисловие

Эта статья посвящена описанию программирования приложений на «чистом» Win32 API. Она написана в основном для начинающих программистов, пишущих программы на Visual C++ 6 с использованием библиотеки MFC, но я надеюсь, может пригодиться и более опытным людям.

First Blood

После создания нового проекта Win32 Application, в зависимости от выбранных опций, мастер генерирует стартовый код. Из этого кода программисту впоследствии, и придется писать программу. Создавая новый проект Win32 Application, выберите в окне мастера опцию An empty project и добавьте в раздел Source Files новый файл с расширением .cpp.

В этом файле добавьте функцию WinMain вида:

Вот и готова первая программа на WinAPI. Она выводит сообщение, после чего завершает свою работу. Обратите внимание на параметры функции WinMain :

  • HINSTANCE hInstance – дескриптор экземпляра приложения. Этот дескриптор содержит адрес начала кода программы в ее адресном пространстве. Дескриптор hInstance чаще всего требуется функциям, работающим с ресурсами программы.
  • HINSTANCE hPrevInstance – дескриптор предыдущего экземпляра приложения. Этот дескриптор остался от старых версий Windows — скорее всего, вам он никогда не пригодится.
  • LPSTR lpCmdLine – указатель на начало командной строки, введенной при запуске программы.
  • int nCmdShow – это значение содержит желаемый вид окна (например, свернутый или развернутый)

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

Функция WinMain – первая функция, которая выполнятся в программе (ее еще называют «точка входа» или «entry point»). С нее все начинается, и ею (желательно) все должно закончиться.

ПРИМЕЧАНИЕ

Функция WinMain – это первая функция, которую вы можете увидеть и заполнить кодом. На самом деле до этой функции выполняется достаточно много кода из библиотеки C++

You have a Message!

Программисты, незнакомые с программированием на WinAPI, спросят: «Что с этим делать?!», — или: «Где создавать CDialog?». В данном случае ответ прост – нигде! В нашем проекте нет класса CDialog или, предположим, CButton – ведь эта статья посвящена тому, как обойтись без них.

Читайте также:  Forkplayer для lg dns сервера 2018

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

СОВЕТ

Сообщение можно отослать функцией SendMessage или ее асинхронным аналогом PostMessage.

Для приема сообщений в программе должен находиться «цикл сообщений» («message loop») который обычно выглядит так:

Функция GetMessage принимает следующие параметры:

  • LPMSG lpMsg – указатель на структуру сообщения, в которую GetMessage вернет результат.
  • HWND hWnd – описатель окна, от которого GetMessage примет сообщение ( NULL означает, что GetMessage принимает сообщения от всех окон, принадлежащих потоку).
  • UINT wMsgFilterMin – наименьший идентификатор сообщения, которое примет GetMessage.
  • UINT wMsgFilterMax – наибольший идентификатор сообщения, которое примет GetMessage (если в значениях параметров wMsgFilterMin и wMsgFilterMax передать 0, функция будет принимать ВСЕ сообщения).

Функция GetMessage не отдает управление программе, пока не придет какое-либо сообщение. Если пришедшее сообщение – WM_QUIT , функция GetMessage вернет 0 . Тогда цикл прервется, и программа завершит свою работу. При любом другом сообщении функция GetMessage возвращает значение больше нуля, и начинатся выполнение тела цикла. При ошибке GetMessage возвращает -1.

СОВЕТ

Сообщение WM_QUIT лучше посылать с помощью специальной функции PostQuitMessage(int iExitCode). Эта функция отошлет сообщение WM_QUIT, а в параметре wParam передаст код завершения программы, указанный в iExitCode.

Функция DispatchMessage должна вызвать «функцию обработки сообщений». В простейшем варианте она выглядит так:

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

  • HWND hWnd – описатель окна, от которого пришло сообщение.
  • UINT message – идентификатор сообщения.
  • WPARAM wParam и LPARAM lParam – параметры сообщения.

Функция обработки сообщений не обязательно должна иметь имя WndProc . Таких функций в программе может быть несколько, но их прототипы обязательно должны выглядеть так:

При вызове этой функции DispatchMessage передает в параметре message идентификатор сообщения. По этому идентификатору производится выборка и выполняется какое-либо действие ( «реакция на сообщение» ).

В Windows существует очень много сообщений! Писать обработчики для всех сообщений – нереальная задача. Чтобы Windows сама обработала бесполезное для вас сообщение, необходимо вызвать функцию DefWindowProc :

Желательно передавать все необработанные сообщения этой функции, а результат ее выполнения возвращать при выходе из WndProc . Это очень важно, так как от обработки некоторых сообщений Windows ждет возврата конкретных результатов или действий.

Одной функцией обработки сообщений могут пользоваться несколько окон, но для одного окна может существовать только одна функция обработки сообщений! Как же система определяет, какой именно функцией обработки сообщения пользоваться для конкретного окна и где она находится?! За это отвечает «класс окна» («window class»).

CLASSные окна

При создании нового окна ему присваивается «Класс окна» (window class). Класс окна задает оконную функцию, используемую по умолчанию. Кроме этого, класс окна задает другие параметры окна, такие, как стиль, меню окна, цвет рабочей области и т.д. Разные классы окон могут указывать на одну и ту же функцию обработки сообщений. Для создания класса его необходимо зарегистрировать.

Итак, регистрация! За нее отвечает функция RegisterClass . В ее параметре необходимо передать указатель на структуру WNDCLASS . Обычно для заполнения структуры и вызова RegisterClass создают отдельную функцию. Но это — дело вкуса.

Вот простейший пример такой функции:

  • WNDPROC lpfnWndProc – адрес функции обработки сообщений.
  • HINSTANCE hInstance – уже знакомая переменная, описывающая экземпляр.
  • LPCTSTR lpszClassName – имя нового класса.
  • HICON hCursor – описатель курсора мыши.
  • HBRUSH hbrBackground – цвет рабочей области окна.

Функция RegisterClass возвращает уникальный «описатель класса окна» типа ATOM . Если при регистрации класса произошла ошибка, это значение будет равно нулю. Чтобы узнать, что произошло, можно вызвать функцию GetLastError() .

Существует также функция RegisterClassEx. Это аналог функции RegisterClass с возможностью присвоения окнам маленькой иконки. При работе с этой функцией необходимо пользоваться структурой WNDCLASSEX.

ПРИМЕЧАНИЕ

Если вы решились работать с GUI Windows вручную, то пользоваться нужно именно RegisterClassEx, поскольку приложение, не имеющее маленькой иконки, сегодня выглядит в Windows как минимум странно. – прим.ред.

СОВЕТ

Следите, чтобы имя вашего класса не совпадало с именами системных классов (например: button или edit).

ПРИМЕЧАНИЕ

Я описал не всю структуру. Все незаполненные поля, которых нет в примере, сейчас равны нулю. Об их значениях можно узнать из MSDN.

Сообщения от окон, созданных на базе класса, зарегистрированного описанной выше функцией RegMyWindowClass, будут обрабатываться функцией с именем WndProc. Чтобы функция WndProc поняла, от какого именно окна пришло сообщение, ей передается уникальный описатель окна HWND .

Our Windows

На вашем месте у меня возникло бы желание увидеть те самые пресловутые окна, из-за которых столько шума. Окно в Windows создается функцией CreateWindow . Вот ее прототип:

Как видите, у функции множество параметров:

  • LPCTSTR lpClassName – имя класса для создаваемого окна (это имя использовалось при регистрации класса).
  • LPCTSTR lpWindowName – имя окна.
  • DWORD dwStyle – стиль окна.
  • int x – позиция по горизонтали верхнего левого угла окна.
  • int y – позиция по вертикали.
  • int nWidth – ширина окна.
  • int nHeight – высота окна.
  • HWND hWndParent – используется для создания «дочернего окна» («child window»). Сюда передается описатель «родительского окна» («parent window»).
  • HMENU hMenu – описатель меню (если hMenu равно нулю, используется меню класса, указанного в lpClassName ).
  • HINSTANCE hInstance – экземпляр приложения.
  • LPVOID lpParam – указатель на пользовательский параметр окна. Этот указатель со всеми остальными параметрами функции CreateWindow будет занесен в структуру CREATESTRUCT . В сообщениях WM_CREATE или WM_NCCREATE параметр lParam будет содержать указатель на эту структуру.

Функция CreateWindow возвращает уникальный описатель окна HWND . Если функция вернула ноль, значит, во время создания окна произошла ошибка. Какая именно, можно узнать, вызвав функцию GetLastError .

Читайте также:  Gigabyte gtx 1060 g1 gaming 3gb
ПРИМЕЧАНИЕ

Существует также функция CreateWindowEx, в которой дополнительно присутствует параметр dwExStyle. С его помощью можно создать окно с дополнительными стилями.

План полета

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

  1. Пользователь нажимает левую кнопку мыши в то время когда курсор мыши находится над рабочей областью окна.
  2. Windows помещает сообщение WM_LBUTTONDOWN в очередь потока.
  3. Цикл обработки сообщения должен вынуть сообщение с помощью функции GetMessage и передать его на обработку функции DispatchMessage .
  4. Функция DispatchMessage находит окно, которому предназначено сообщение и помещает сообщение в его очередь.
  5. Функция окна обрабатывает сообщение WM_LBUTTONDOWN и возвращает результат.
  6. Тело цикла заканчивается, и управление снова передается функции GetMessage для ожидания новых сообщений.

Итого

WinMain, регистрация класса, цикл сообщений, функция обработки сообщений, создание окна. Как все это связать?! Вот код, который объединяет все написанное выше в одну программу:

Вот, в принципе, и все! Это полноценное приложение на WinAPI.

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

Признаю, что данная статья и программа опускает очень много деталей! Многие вещи были не раскрыты (например, остальные переменные структуры WNDCLASS ). Все это сделано для того, чтобы максимально упростить статью и уменьшить код программы.

. when altering one’s mind becomes as easy as programming a computer, what does it mean to be human.

пятница, 17 апреля 2009 г.

В чём разница между HINSTANCE и HMODULE?

Сегодня это одно и то же, но когда-то они обозначали разные вещи.

Это пришло к нам из 16-ти битных Windows.

В те дни, "модуль" (module) представлял файл на диске, который был загружен в память, и этот модуль имел "описатель" (handle), который указывал на структуру данных, описывающую части файла, откуда они взялись, откуда были загружены в память (если были загружены). С другой стороны, "экземпляр" (instance) представлял собой "набор переменных".

Аналогия, которая может пролить свет на это дело (или нет?): "модуль" — это как-бы код для класса в языке программирования; он описывает, как создать объект, как реализованы его методы, он описывает, как ведут себя объекты этого класса. С другой стороны, "экземпляр" — это как-бы конкретный объект, который является объектом какого-то класса; он описывает состояние конкретного экземпляра этого класса.

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

USER32 HMODULE USER32 HINSTANCE
дескриптор сегмента кода Код USER32. Данные USER32.
дескриптор сегмента кода (не в памяти)
дескриптор сегмента кода Код USER32.
дескриптор сегмента данных
HRSRC (не в памяти)
HRSRC Ресурсы USER32.
HRSRC (не в памяти)
таблица экспорта

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

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

В терминах гиков: был только один "экземпляр" (instance) DLL в системе.

С другой стороны, если вы запускали две копии Блокнота, каждая из них получала свой собственный набор переменных — это были два разных "экземпляра":

HMODULE Блокнота HINSTANCE
дескриптор сегмента кода Код Блокнота. Данные Блокнота.
дескриптор сегмента кода (не в памяти)
дескриптор сегмента данных HINSTANCE
HRSRC (не в памяти) Данные Блокнота.
HRSRC Ресурсы Блокнота.

Обе запущенные копии Блокнота разделяли модуль NOTEPAD (так что код и ресурсы были общими), но каждая копия имела свой набор переменных (раздельные сегменты данных).
Поэтому это были два "экземпляра" Блокнота.

Дескрипторы "экземпляров" (instance) в диаграммах выше являются сегментами данных.

Запущенные программы идентифицировались по их instance handle. Вы не могли использовать module handle, потому что две копии Блокнота имели один и тот же module handle (поскольку один и тот же код выполняется в обоих копиях). Штука, которая их отличала друг от друга — это их собственный набор переменных.

Вот почему функции WinExec и ShellExecute возвращают HINSTANCE: это рудимент 16-ти битных Windows, где HINSTANCE использовались для идентификации запущенных программ.

Метод, которым код получал свой HINSTANCE (чтобы узнать, где лежат его глобальные переменные), я оставлю для будущего поста. Он имеет отношение к (теперь уже устаревшей) функции MakeProcInstance.

Когда пришло время проектировать Win32, возник вопрос: "что нам делать с HINSTANCE и HMODULE для Win32?". Поскольку программы теперь выполняются каждая в своём адресном пространстве, вам не нужно делать instance handle видимыми между процессами. Поэтому дизайнеры сделали единственную вещь, которая у них была: базовый адрес модуля. Это аналог HMODULE, поскольку файловый заголовок описывает содержимое файла и его структуру. Но он же является аналогом HINSTANCE, потому что данные хранятся в сегменте данных, который теперь проецируется в адресное пространство процесса напрямую.

Поэтому в Win32, HINSTANCE и HMODULE являются просто базовыми адресами модуля.

Завтра, я поговорю об этом загадочном параметре hinstPrev в WinMain.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*

code