Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Программирование для начинающих и не только


Информационный Канал Subscribe.Ru

Чистый WinAPI

Время от времени в разных форумах посвященных Delphi возникают вопросы об уменьшении размера готовых приложений. И очень часто в ответах мелькает фраза: <Используй WinAPI>. Но для человека, который начал изучение программирования с Delphi с его джентельментским набором под названием VCL, бывает очеть трудно перестроится на логику WinAPI.

О чем реч

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

Итак.

Для начала следует сказать, что мы здесь не будем использовать все Forms,Buttons,StdCtrls - модули созданные Borland для упрощения жизни программистов. Мы полечем как всегда через окно, и будем использовать только модули Windows, в котором объявлены все необходимые функции и модуль Messages, в котором объявлены константы сообщений системы необходимые для организации реакции элементов на те или иные события.

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


function CreateWindow(lpClassName: PChar; lpWindowName: PChar;
  dwStyle: DWORD; X, Y, nWidth, nHeight: Integer; hWndParent: HWND;
  hMenu: HMENU; hInstance: HINST; lpParam: Pointer): HWND;
Эта функция принимает такие параметры:
  • LpClassName - указатель на зарегистрированное имя класса. Это имя может быть как зарегистрированным новым именем класса, так и уже имеющемся в системе классом. Так среди уже зарегистрированных имен классов можно увидеть:BUTTON,COMBOBOX,EDIT,LISTBOX,MDICLIENT,SCROLLBAR,STATIC.
  • LpWindowName - указатель на название окна
  • DwStyle - флаги задающие стиль окна. Их значения приведены в табл.1
  • x, y, nWidth, nHeight - координаты окна
  • hWndParent - идентификатор родительского окна.
  • HMenu - идентификатор меню.
  • HInstance - идентификатор приложения.
  • LParam - указатель на дополнительные данные для создания окна.
  • Возвращаемое значение - если создание окна прошло удачно, то функция возвращает его идентификатор, если же окно по каким либо причинам не было созданно, то возвращает 0.
Но перед тем, как использовать эту функцию нам придется сделать некоторые подготовительные работы. Дело в том, что стандартного класса формы в стандартном перечне объектов, приведенном выше, нет и нам предстоит его создать и зарегистрировать припомощи функци RegisterClass. Прототип этой функции имеет следующий вид:
function RegisterClass(const lpWndClass: TWndClass): ATOM; stdcall; где
  • lpWndClass - структура описывающая все особенности поведения окон созданных при помощи этого класса.
  • Результат функции - тип ATOM по сути тот же тип Word. Если выполнение функции прошло удачно, то возвражает идентификатор класса, и значение NULL (т.е. 0) в противном случае.
Структура WndClass имеет следующий вид:
style: UINT;Значение задающее стиль окна. Возможные значения поданы в табл.2
lpfnWndProc: TFNWndProc;Указатель на процедуру-обработчик событий возникающих для данного элемента
cbClsExtra: Integer;Задает размер екстра-памяти выделяемой при создании оконного класса.В нашем случае - 0
cbWndExtra: Integer;Задает размер екстра-памяти выделяемой при создании нового экземпляра окна. В нашем случае - 0
hInstance: HINST;Идентификатор нашего приложения
hIcon: HICON;Идентификатор иконки.
hCursor: HCURSOR;Идентификатор курсора.
hbrBackground: HBRUSH;Идентификатор класса фоновой заливки
lpszMenuName: PAnsiChar;Указатель на название ресурса-меню в ресурсах приложения
lpszClassName: PAnsiChar;Указатель на название регистрируемого класса

В этой структуре нам следует обратить внимание на параметр lpfnWndProc так как именно суда записывается указатель на функцию-обработчик событий, который выглядит следующим образом:
function (hWnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall; здесь

  • hwnd - идентификатор окна
  • Msg - сообщение переданное окну
  • lParam,wParam - дополнительная информация зависящая от переданного сообщения
  • Возвращаемое значение - результат выполнения функции и зависит от переданного сообщения
Кроме того, в модуле Windows объявлена <заглушка> для этой функции под названием DefWindowProc, которую надо использовать в тех случаях, когда вам не надо обрабатывать абсолютно все сообщения поступающие окну, тогда вы реализуете обработку только необходимых сообщений, а все лишнее передаете этой функции.

Подготовительные работы

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


ZeroMemory(@WndClass,SizeOf(WndClass));
WndClass.style:=CS_HREDRAW or CS_VREDRAW;
WndClass.lpfnWndProc:=@DefWindowProc;
WndClass.hInstance:=Sysinit.hInstance;
WndClass.hIcon:=0;
WndClass.hCursor:=0;
WndClass.hbrBackground:=COLOR_WINDOW;
WndClass.lpszClassName:='WinAPITestForm';
Далее посредством вызова функции Windows.RegisterClass мы регистрируем наш класс под названием WinAPITestForm:
Windows.RegisterClass(WndClass); Теперь мы уже можем приступить к созданию самого окна:

Wnd:=CreateWindow('WinAPITestForm','Test Form',
            WS_BORDER or WS_CAPTION or WS_SYSMENU or WS_VISIBLE,
     100,100,300,300,0,0,hInstance,@CSTR);
После этого в реальном приложении необходимо проверить правильно ли созданно окно, и вывести информацию об ошибке при негативном ответе и сделать <аварийный> выход из программы. Но здесь мы этого делать не будем по причине тривиальности этого кода.

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

Тело цыкла выглядит следующим обратом:


while GetMessage(Msg,0,0,0) do 
 begin
  TranslateMessage(Msg);
  DispatchMessage(Msg);
 end;
Вот так вот простенько и реализовываем обработку сообщений. Здесь главную роль играет функция GetMessage прототип еоторой объявлен следующим образом:

function GetMessage(var lpMsg: TMsg; hWnd: HWND;
  wMsgFilterMin, wMsgFilterMax: UINT): BOOL; stdcall;
Здесь
  • lpMsg - адрес структуры с сообщением и дополнительными параметрами
  • hWnd - идентификатор окна для которого идет обработка сообщений wMsgFilterMin
  • wMsgFilterMax - флаги фильтрации сообщений
  • Результат - 0 если возникло сообщение WM_QUIT, тогда реализуется выход из приложения. Или же ненулевое значение для других сообщений. При возникновении ошибки результат функции равен -1
Далее в теле цыкла идет трансляция сообщения в приемлемый для обработки вид (TranslateMessage), и его обработка (DispatchMessage). После тела цыкла мы завершаем наше приложение вызовом процедуры Halt и удалением информации о нашем классе, чтоб не засорял память:

 Windows.UnregisterClass('WinAPITestForm',hInstance);
 Halt(Msg.wParam);
Все. Конец приложения. Полний текст приложения можно увидеть в листинге.1, а результат работы приложения на рис.1

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

Но в таком виде (пустая форма), наше приложение никому не нужно, и кроме как занимания места на ґкране оно ничего не делает. К стати оно даже не может нормально завершится, поскольку функция DefWindowProc, которая была объявлена в качестве обработчика сообщений нашей формы не реализуе посылку сообщения WM_QUIT. Можете проверить нажав на Ctrl+Alt+Del. Для того чтоб сдеталь нашу форму более живой нам потребуется реализовать свой собственный обработчик сообщений. Для этого еще раз вспомним объявление, функции-обработчика сообщения:
function (hWnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall; И постараемся добавить до функции DefWindowProc дополнительные возможности. Для этого мы сначала объявим следующую функцию:
function MyWindowProc(Wnd:Hwnd;Msg:UINT;lParam:LPARAM;wParam:WPARAM):LRESULT;stdcall; И не забываем, не забываем об stdcall иначе у нас как минимум ничего не получится. Далее в теле этой функции мы можем обрабатывать все необходимые сообщения. Первым делом что мы сделаем - это будет реализация нормального закрытия нашего приложения:


Result:=0;
case Msg of
 WM_DESTROY:PostQuitMessage(0);
else Result:=DefWindowProc(Wnd,Msg,lParam,WParam);
end;
Теперь все что нам остается сделать, это перенаправить обработку сообщений из DefWindowProc на наш обработчик при объявлении параметорв класса. (просто заменим ссылку WndClass.lpfnWndProc на MyWindowProc). И откомпилировать приложение.

Другие окна

Итак, теперь мы уже знаем как <открывать> окна без помоши VCL, но конечно одними окнами сыт не будеш, и глаз уже интуитивно ищет чтоб такое нажать. Для этого эстественно предназначены конпки, которые мы и создадим на следующем этапе.

И спешу вас обрадовать, что нам больше не придется ничего регистрировать и писать лишние обработчики. Для создания конпки достаточно только одной строки:
Wnd1:=CreateWindow('BUTTON','Button1',BS_PUSHBUTTON or WS_VISIBLE or WS_CHILD,110,110,150,40,Wnd,0,hInstance,@CSTR); Да и то большинство элементов которой Вам уже знакомы. Так здесь мы создаем элемент оконного класса