Выпуск 11: Исследуем WinApi

 

Архив рассылки

Попрошу у всех внимания!

1 сентября, в районе поселка Большой Утриш, Анапский район, пропала девочка 3-х лет, София Шарыкина. Была одета в розовые шорты, футболку и голубой платок. Загорелая. Волосы русые, короткие. Глаза серо-карие. Сейчас ребенок может оказаться где угодно, в том числе и за границей. Если Вы обладаете какой-либо информацией о Софии, сообщите, пожалуйста, по тел. 8-501-430-38-79 или напишите на lost_sofia@mail.ru Вознаграждение гарантируется.

Подробнее на сайте http://www.lost-sofia.narod.ru/

Дело серьёзное, так что если чего нибудь знаете об этом - не молчите!

Перед тем как начать, я хочу извинится. В прошлом выпуске я сделал одну ошибку. В языке Си исключающий ИЛИ записывается не "::", а "^".

Раз вопросов нету, я не буду останавливаться на прошлом выпуске.

Поскольку вы уже знаете, что такое подпрограммы и как они дейвствуют, мы можем продолжить с нашим проектом WinApi. Но перед этим мы создадим такой проект на Dev-C++ (для Си программистов).

Создайте новый файл (как и в предыдущих выпусках). Добавьте модуль windows.h. В Си вам не понадобится отдельный млдуль для сообщений. Теперь добавим три переменные, как и в Дельфи проекте. Обратите внимание, как выглядят типы этих переменных в Си:

WNDCLASSEX Wc;
HWND Wnd;
MSG Msg;

Теперь нам надо создать функцию, в которой будут обрабатываться разные сообщения, например выход из программы и другие. Эта функция называется оконной процедурой. Её имя будет WndProc. Конечно, вы можете назвать её по своему. Тип функции - LResult (LRESULT в Си).

Delphi:

function WndProc(): LResult;
begin

end;

C:

LRESULT WndProc()
{

}

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

Delphi:

uses
  Windows, // Модуль для работы с окном
  Messages; // Модуль для работы с сообщкниями

var
  Wc: TWndClassEx; // Переменная для определения класса окна
  Wnd: HWND; // Ссылка на окно
  Msg: TMsg; // Переменная для хранения сообщений

(*
Оконная процедура
Её задача "ловить" сообщения и правильно на них отреагировать
Тип переменной для сообщений отличается от того, который использовался среди
глобальных переменных
*)

function WndProc(wnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): lResult;
begin

end;

begin

end.

C:

#include < windows.h > // Модуль для работы с окном

WNDCLASSEX Wc; // Переменная для определения класса окна
HWND Wnd; // Ссылка на окно
MSG Msg; // Переменная для хранения сообщений

/*
Оконная процедура.
Её задача "ловить" сообщения и правильно на них отреагировать
Тип переменной для сообщений отличается от того, который использовался среди
глобальных переменных
*/

LRESULT WndProc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam)
{

}

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

Переменные могут быть глобальными и локальными. Какая разница? Разница в возможностях использования. Точнее - в месте использования. Начну с примера (в комментариях - перевод на Си, хоть это и не нужно :).

Delphi:

var
  x: Integer; // int x;

procedure Nonsense(); // void Nonsense()
var
  y: Integer; // int y;
begin // {
 x:= 15; // x = 15;
 y:= x*x; // y = x*x;
end; // }

begin // void main() {
 WriteLn(x, y); // printf("%d %d\n", x, y);
end; // }

Есть ли тут ошибки? Вроде не видно... Синтаксических ошибок и на самом деле нету. Но посмотрите на места объявлений переменных. Программа явно не будет работать. А где ошибка то? А ошибка в главном блоке программы. Там программа пробует "достать" переменную, которую разрешенно трогать только её создавшей процедуре (переменная y). Т.е. она может быть использованна только в процедуре Nonsense. Переменная, созданная в подпрограмме называется локальной и может быть использованна только в этой подпрограмме. Переменная x созданна в самом начале проекта, перед всеми подпрограммами. Именно по этому (потому что её не ограничивают командные скобки) эту переменную можно использовать везде. Такие переменные называются глобальными. С константами точно так же. Избегайте глобальных переменных! Вы можете сами незная этого изменить значение не той переменной и этим "погубить" программу. Кстати, не так как в Дельфи, в Си, когда определяется константа, надо сразу указать её тип: const int x = 15.

Чтобы как можно реже использовать глобальные переменные, мы их переместим в главный блок/функцию программы. Но это не сейчас. Нам пора познакомится с одним сообщением. WM_DESTROY называется :) Оно посылается, когда программу собираются уничтожить. Мягко говоря закрыть :) А мягко потому, что для закрытия приложения на самом деле посылаются несколько сообщений. Но вам не обязательно про них знать :) Значит, когда посылается сообщение, оно записывается в переменную. Наша переменная называется msg. Чтобы закрыть программу, нам нужно проверить какое сообщение находится в переменной. Логично? Ведь мы не будем закрывать программу, когда она включается, т.е. в переменной находится сообщение WM_CREATE. Думаю, это понятно каждому. Так что надо делать? Воспользуемся условным оператором. Вы бы догадались? Если нет, то вам надо больше практики по этой теме. Если вы чувствуете, что так оно и есть (только честно!), пишите мне и я дам вам ещё задач, а также отвечу на возникшие вопросы. Если вы не выучите первые уроки, то дальше вам лучше не заглядовать! Я всё время говорю, что код создающий окно OpenGL и полностью приготовляющий его к работе занимает много места. А сколько места то? Как вам звучит: 7 страниц Word'а!? Чтобы написать такой код надо не только получать от программирования удовольствие, но и не мало знаний. Не забудьте про терпеливость!

Продолжим. Теперь функция должна выглядить так:

Delphi:

function WndProc(wnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): lResult;
begin
 if Msg = WM_DESTROY // Обработка сообщения "уничтожения" приложения
  then
   begin

   end;
end;

Осталось нам "правильно" закрыть программу. Ведь это сообщение и просит её закрыть :) Конечно мы можем использовать функции из выпуска 05, но есть и другой способ, который используется более часто. Он также является более удобным и кратким:

Delphi:

function WndProc(wnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): lResult;
begin
 if Msg = WM_DESTROY // Обработка сообщения "уничтожения" приложения
  then
   begin
    PostQuitMessage(0); // Выход из программы
    Result:= 0; // Сообщает программе, что всё прошло отлично
   end;
end;

C:

LRESULT WndProc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
     if(msg == WM_DESTROY) // Обработка сообщения "уничтожения" приложения
     {
         PostQuitMessage(0); // Выход из программ
         return 0; // Сообщает программе, что всё прошло отлично
     }
}

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

Delphi:

function WndProc(wnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): lResult;
begin
 if Msg = WM_DESTROY // Обработка сообщения "уничтожения" приложения
  then
   begin
    PostQuitMessage(0); // Выход из программы
    Result:= 0; // Сообщает программе, что всё прошло отлично
   end
  else // Остальные сообщения обрабатываются по умолчанию
   Result:= DefWindowProc(wnd, Msg, wParam, lParam);
end;

C:

LRESULT WndProc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
     if(msg == WM_DESTROY) // Обработка сообщения "уничтожения" приложения
     {
         PostQuitMessage(0); // Выход из программ
         return 0; // Сообщает программе, что всё прошло отлично
     }
     else // Остальные сообщения обрабатываются по умолчанию
      return DefWindowProcA(wnd, msg, wParam, lParam);
     }
}

Теперь уже всё :) Всё, да не совсем :) Программа то рабочая (кроме версии Си, потому что мы там ещё не создали главную функцию), но само окно ещё не созданно :) Сейчас всё исправим. Писать придётся больше, но не сложно. Вообще - пустое окно на WinApi вещь легкая :)

Но перед тем, как создать окно, нам надо кое чем пополнить оконную функцию. Для более правильной работы нам надо добавить в конце (в начале для Си) функции stdcall (в Си CALLBACK). Теперь функция выглядит так:

Delphi:

function WndProc(wnd: HWND; Msg: UINT;
               ;   wParam: WPARAM; lParam: LPARAM): lResult; stdcall;

C:

LRESULT CALLBACK WndProc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam)

stdcall = стандартный вызов функции. Нужен, чтобы функции WinApi написанные на разных языках могли нормально друг с другом взаимодействовать (WinApi написанна на языке Си, а также и OpenGL). Теперь можно перейти к созданию самого окна.

Чтобы создать работоспособное окно на WinApi вам не хватит его "тупо" создать. Вообще эта работа состоит из нескольких частей. Надо:

1. Определить свойтва окна
2. Создать окно
3. Зарегистрировать его
4. Отобразить на экране

Начнём, конечно, с первого пункта. Свойства окна у нас находятся в переменной Wc, которая пока является глобальной. Теперь очень коротко объясню, что это такое - класс. Класс - группа свойств и подпрограмм. На самом деле это вещь более обширная, но вам пока не надо морочить голову этим материалом. Больше вы узнаете, когда мы сами будем создавать свои классы. Потомок класса является объектом. Он имеет свойства, описанные в классе. Например, кнопка - объект. Он имеет свойство Caption, которое описанно в классе этого объекта. А сам класс называется TButton. Все классы начинаются с буквы T. Окно нашей программы будет потомком класса TWndClassEx. Один класс может иметь кучу потомков. Это доказывает возможность на форму поставить сотни (если надо :) кнопок, или других компонентов. Значит Wc можно назвать новым объектом. Обращатся к этому объекту будем также, как и к другим (например кнопки) - через точку. Наш новый объект имеет десять свойств (их больше (12), но нам нужно всего 10)! :) Девять из ник имеют дурные начала :) Т.е. они названны не очень удобно. Я часто путаюсь :) Давайте перейдём к первому свойству.

Первое свойство называется Style. Оно определяет стиль окна, т.е. каким оно должно быть. Окно должно перерисовываться, когда изменяют его размеры. Размер изменять можно вертикально и горизонтально. Не забудьте, это всё надо делать в главном блоке программы! А программистов Си попрошу ещё не начинать :)

Delphi:

begin
 Wc.Style:= CS_VREDRAW or CS_HREDRAW;
end.

CS_VREDRAW значит перерисовку при вертикальном изменении размера окна. Соответственно CS_HREDRAW - перерисовка окна при его горизонтальном изменении размера (от Vertical Redraw и Horizontal Redraw). Почему мы использовали or? Ведь надо, чтобы перерисовка происходила при изменении и того, и другого размера окна! Те, кто так думают, думают совсем правильно. Но представьте себе, что будет происходить, если мы поставим and! Вы помните, когда надо использовать одно или другое логическое выражение? Если мы поставим and, то перерисовка будет происходить только тогда, когда будут измененны и вертикальные, и горизонтальные размеры окна! А пользователь может захотеть изменить только вертикальный, или только горизонтальный размер, т.е. высоту или ширину. Теперь понятно? Точно также и в Си. Но почему я попросил ещё не начинать? А потому, что название главной функции в косольном приложении отличается от того, что нам надо использовать на WinApi! Во первых, она называется не просто main(), a WinMain(), и перед названием функции надо сказать программе, что эта функция является WinApi программой, а не консольной. Вот так это будет выглядеть на языке Си:

C:

int WINAPI WinMain()
{
    Wc.Style = CS_VREDRAW || CS_HREDRAW;
}

Теперь нам надо указать пиктограмму приложения и курсор. Свойства называются hIcon и hCursor.

Delphi:

Wc.Style:= CS_VREDRAW or CS_HREDRAW;
Wc.hIcon:= LoadIcon(0, IDI_APPLICATION);
Wc.hCursor:= LoadCursor(0, IDC_ARROW);

C:

Wc.Style = CS_VREDRAW || CS_HREDRAW;
Wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
Wc.hCursor = LoadCursor(NULL, IDC_ARROW);

Чтобы вы лучше всё усвоили, я продолжу тему в следущем выпуске :) Мы уже написали половину всего кода :) Если появятся вопросы - пишите.

На сегодня всё.

 

2005-10-24

Spider3D http://spider3d.narod.ru

Евгений Нарышкин Spider3D@yandex.ru

Архив Рассылки

Хостинг от uCoz