Программирование на основе Win32 API в Delphi
Программирование на основе Win32 API в Delphi
Программирование на основе Win32 API в Delphi Источник: www.codenet.ru 1. Введение Любую современную программу или программную технологию можно представить как совокупность программных "слоев". Каждый из этих слоев производит свою собственную работу, которая заключается в повышении уровня абстракции производимых операций. Так, самый низший слой (слои) вводит понятия, которые позволяют абстрагироваться от используемого оборудования; следующий слой (слои) позволяет программисту абстрагироваться от сложной последовательности вызовов функций, вводя такое понятие как протокол и т.д. Практически в любом современном программном продукте можно обнаружить и выделить около десятка последовательных слоев абстракции. Абстракция от оборудования и низкоуровневых протоколов вводится в ядра операционных систем в виде библиотек api (application program interface). Однако современные тенденции приводят к необходимости абстрагирования и от самих операционных систем, что позволяет переносить программы с одной операционной системы на другую путем простой перекомпиляции (транслируемые программы, в основном, вообще не требуют никаких действий по переносу). Абстракцию, которая доступна программисту в виде библиотек api можно назвать базовой. Это самый низкий уровень абстракции, который доступен для прикладного программирования. На уровне ядра системы доступны и более низкие уровни абстракции, однако для их использования необходимо разрабатывать специализированные программы (драйвера, модули). Базовый уровень абстракции (api) предоставляет максимально широкие возможности для прикладного программирования и является наиболее гибким. Однако, программирование с использованием api является гораздо более трудоемким и приводит к значительно большим объемам исходного кода программы, чем программирование с использованием дополнительных библиотек. Дополнительные библиотеки поставляются со многими средствами разработки с целью уменьшения трудоемкости и сроков разработки программ, что в итоге приводит к повышению их конкурентноспособности. Но применение дополнительных библиотек абстракций приводит к резкому увеличению размеров откомпилированных программ, из-за того что в программу включается код используемых библиотек, к тому же это включение зачастую бывает неэффективным - в программу включаются неиспользуемые участки кода. Кроме того, чем больше уровень абстракции библиотеки, тем сложнее ее код, и тем больше трудностей возникает при решении сложных задач. Приходится учитывать множество взаимосвязей и взаимных влияний отдельных элементов и процессов библиотеки друг на друга. Кроме того, структура и функциональность любой библиотеки обычно рассчитывается на удовлетворение всех потенциально возникающих задач, что приводит к ее громоздкости и неэффективности. В delphi используется очень мощная и сложная библиотека vcl (visual components library), которая помимо непосредственных абстракций вводит также и множество своих функциональных классов. В этой библиотеке находятся компоненты для визуального отображения информации, работы с базами данных, с системными объектами, компоненты для работы с internet-протоколами, классы для написания своих com-объектов и многое другое. Модули библиотеки подключаются к компиляции по мере необходимости, однако базовый размер простейшего диалогового проекта с одной формой превышает 300кБ (со статически скомпонованной библиотекой). И такой размер во многих случаях может оказаться слишком большим, особенно если программа не требует большой функциональности в интерфейсе. Для решения этой проблемы можно отказаться от использования библиотеки vcl, и программировать, используя базовый набор функций win32 api. Однако, если при разработке линейных, недиалоговых, нерезидентных программ не возникает никаких трудностей, то разработка программ, требующих активного взаимодействия с пользователем или системой, становится трудоемкой. Структурное программирование, рекомендуемое в таких случаях, оказывается неэффективным и трудоемким. Данная статья посвящена проблеме создания и использования компактной объектно-ориентированной библиотеки, которая бы облегчила построение небольших и эффективных программ на основе win32 api. 2. Существующие решения Автору известны три объектно-ориентированные библиотеки, которые можно рассматривать как альтернативу библиотеке vcl при написании компактных программ. Это библиотеки классов xcl, acl и kol. Все библиотеки бесплатны и поставляются в исходных кодах. Библиотека acl (api control library) Автор: Александр Боковиков, Екатеринбург, Россия Страничка: http://a-press.ur.ru/pc/bokovikov e-mail: abb@adx.ru Классы и модули: tfont, tfonts, tcontrol, twincontrol, tstdcontrol, tlabel, tedit, tlistbox, tbutton, tcheckbox, tcombobox, tgroupbox, tprogressbar, tkeyboard Библиотека xcl (extreme class library) Автор: vladimir kladov (mr.bonanzas) Страничка: http://xcl.cjb.net e-mail: bonanzas@xcl.cjb.net Классы и модули: xform, xapplet, xcanvas, xpen, xbrush, xfont, zddb, zhibmp, zdibitmap, zbitmap, zicon, zgifdecoder, zgif, zjpeg, xlabel, xbutton, xbevel, xpanel, xsplitpanel, xstatus, xgrep, xgroup, xcheckbox, xradiobox, xpaint, xscroller, xscrollbox, xscrollboxex, xedit, xnumedit, xcombo, xgrid, xlistview, xmultilist, xnotebook, xtabs, xtabbednotebook, xcalendar, xgauge, xgaugepercents, xhysto, xhystoex, ximagelist, ximgbutton, xtooltip, xcustomform, xdsgnform, xdsgnnonvisual, clabel, cpaint, cbutton, cedit, cmemo, ccheckbox, cradiobox, clistbox, ccombobox, zlist, zmenu, zpopup, zmainmenu, zpopupmenu, ztimer, zstrings, zstringlist, zinifile, zthread, zqueue, zfilechange, zdirchange, zopensavedialog, zopendirdialog, ztree, zdirlist, zdirlistex, zregistry, zstream, zfilestream, zmemorystream, xstrutils, xdateutils, xfileutils, xwindowutils, xprintutils, xshelllinks, xjustone, xjustonenotify, xpascalunit, xsysicons, xcanvasobjectsmanager, xrotatefonts, xfocuspainter, xformsstdmouseevents, xformsstdkeyevents, x formautosizer, xaligner, xcontrolautoplacer, xmfcantiflicker, xsplitsizer, xresizeantiflicker, xcaretshower, xeditmouseselect, xeditclipboard, xeditundo, xlistmousesel, xlistkeysel, xlistedit, znamedtags, xbtnrepeats, xbuflabels, xbackgrounds, xwnddynhandlers Библиотека kol (key object library) Автор: vladimir kladov (mr.bonanzas) Страничка: http://xcl.cjb.net e-mail: bonanzas@xcl.cjb.net Классы и модули: tobj, tlist, tgraphictool, tcanvas, tcontrol, ttimer, ttrayicon, tstream, tstrlist, tdirlist, tinifile Как видно из списка приведенных для каждой библиотеки классов, эти библиотеки предендуют скорее не на помощь при написании программ с использованием win32 api, а пытаются создать более высокий уровень абстракции чем api, по крайней мере в графической части (особенно это относится к xcl). Более того, иерархия и перечень объектов совпадают с соответствующими структурами в библиотеке vcl, что скорее всего связано с желанием авторов обеспечить логическую совместимость с vcl при построении программ на основе этих библиотек. Данные библиотеки не обеспечивают минимального размера программы, за счет того что предоставляют более высокий уровень абстракции. Они являются компромисом между программированием с использованием vcl и программированием на чистом api. 3. Принципы построения api-библиотеки тандартным видом api-программирования является структурное программирование. Примеры такого программирования на win32 api есть практически в любой книжке по borland pascal, borland c++, microsoft visual c++ и другим системам разработки. Множество примеров api-программирования на С содержится в поставке microsoft visual c++. Структурное программирование с оконными функциями, процедурами обработки команд, не в состоянии обеспечить быструю и эффективную разработку программ. В современной ситуации большинство программистов привыкло к объектно-ориентированному методу, с возможностью инкапсуляции, наследования и переопределения методов объектов. Такое программирование оказывается наиболее эффективным. Кроме того, для построения эффективной api-библиотеки прежде всего нужно выяснить, какие задачи при работе с win32 api являются наиболее трудоемкими. Практика показывает, что наиболее неудобным и трудоемким элементом является реализация основного диспетчера логики программы - оконной функции. Реализация этой функции в качестве метода класса, а не простой глобальной функции, позволила бы улучшить структуру кода и облегчить программирование путем инкапсулирования всех переменных внутри оконного класса. Программирование может быть еще более облегчено, есть возпользоваться механизмом message-процедур языка object pascal. Вызов этих процедур полностью лежит на компиляторе и корневом объекте tobject и включает в себя методы dispatch, defaulthandler, а также все методы, объявленные с директивой message. Такое решениее позволит полностью отказаться от громоздкого оператора case в оконной функции. Учитывая все вышеперечисленное автором была создана компактная библиотека оконных классов winlite. Эта библиотека является минимальной, она не вводит более высоких уровней абстракции чем существуют в win32 api - она только облегчает работу, переводом программирования в объектно-ориентированное русло. Размер библиотеки очень небольшой и вся она помещается в один модуль. Библиотека реализует базовый класс tliteframe и построенные на основе него оконные классы: tlitewindow - класс окна, с возможностью subclass'инга; tlitedialog - класс немодального диалога; tlitedialogbox - класс модального диалога. Библиотека может быть использована совместно с vcl. На первый взгляд, это возможность является абсурдной и ненужной, так как об экономии размера в этом случае не может быть и речи. Однако, иногда бывают моменты, когда реализация специфических оконных элементов на основе объектов twincontrol или tcustomcontrol может быть затруднена или неэффективна из-за их сложности и неочевидного поведения. В этом случае, можно реализовать такой элемент на базе класса tlitewindow - он будет вести себя стандартным образом, как и полагается вести себя стандартному оконному элементу win32. Благодаря своей простой архитектуре библиотека может быть использована в многопоточной программе. Конечно, вы не сможете вызывать методы классов одного потока из другого потока без соответствующей синхронизации. Однако, вы можете беспрепятственно создавать оконные классы в различных потоках без блокировки и синхронизации, а также посылать сообщения оконным классам в другом потоке. Практический совет: при api-программировании программист должен сам следить за корректным освобождением многочисленных ресурсов, которые занимает программа во время выполнения. Поэтому, для облегчения этой задачи используйте какую-либо контролирующую утилиту, например memproof или numega boundschecker. Корректное освобождение занятых ресурсов крайне необходимо ! Для редактирования шаблонов диалогов можно использовать любой редактор ресурсов, например borland resource workshop, правда он несколько неудобен, а окончательный результат все равно приходится корректировать вручную. Вся документация необходимая для api-программирования содержится в поставляемых компанией microsoft компакт-дисках с документацией под общим названием msdn (microsoft developer's network). Существует online-версия документации по адресу http://msdn.microsoft.com. Урезанная версия msdn, содержащая основные файлы помощи, поставляется с delphi. Прежде чем вы решите работать над своим проектом в русле win32 api, подумайте, а зачем вам это нужно? В подавляющем числе случаев размер программы не имеет никакого значения. Я не хочу сказать, что api-программирование сложнее чем vcl-программирование. Во многих случаях легче изучить и написать 10 вызовов api с кучей аргументов и понимать, что происходит, чем написать 1 вызов простой, на первый взгляд, vcl-инструкции и потом долго исследовать дебри vcl в поисках ответа. Просто api-программирование - это другая культура, к которой вы, возможно, не привыкли. И первоначальная работа может вызвать у вас сильное разочарование. api-программирование требует дотошности, кропотливости и внимательного изучения документации. Те же, кто отважился программировать на api, наряду с библиотекой winlite могут совместно использовать невизуальные классы как из состава vcl (модули sysutils, classes), так и многие сторонние - естественно, что размер вашей программы при этом увеличится. Невизуальные классы библиотеки acl - http://a-press.ur.ru/pc/bokovikov Невизуальные классы библиотеки xcl - http://xcl.cjb.net jedi code library - http://www.delphi-jedi.com Системные компоненты на torry - http://www.torry.ru Заслуживает внимание работа Владимира Кладова по изменению функциональности обязательного модуля system.pas. Со времен первых версий turbo pascal этот модуль по умолчанию компонуется в исполняемый код программы. Код модуля реализует многие принципы и решения заложенные в синтаксис и логику языка object pascal, и изменение этого модуля позволяет модифицировать реализацию этой логики. Такое решение является специфичным для языка object pascal в отличие, например, от c/c++, где компилятор и абсолюдно все модули никак не связаны. Изменение модуля system.pas, а именно его разбиение на блоки и сокращение редко используемых участков кода позволило сократить постоянные (не переменные) издержки примерно на 8 кБ. Конечно, для больших проектов, такое сокращение может быть и незаметным, однако интересен сам принцип. Модифицированный модуль system.pas - http://xcl.cjb.net 4. Библиотека winlite //////////////////////////////////////////////////////////////////////////////// // winlite, библиотека классов и функций для работы с win32 api // (c) Николай Мазуркин, 1999-2000 // _____________________________________________________________________________ // Оконные классы //////////////////////////////////////////////////////////////////////////////// unit winlite; interface uses windows, messages; Инициализационные структуры Объявление структур, которые используются для формирования параметров вновь создаваемых окон и диалогов соответственно. //////////////////////////////////////////////////////////////////////////////// // Параметры для создания окна //////////////////////////////////////////////////////////////////////////////// type twindowparams = record caption : pchar; style : dword; exstyle : dword; x : integer; y : integer; width : integer; height : integer; wndparent : thandle; wndmenu : thandle; param : pointer; windowclass : twndclass; end; //////////////////////////////////////////////////////////////////////////////// // Параметры для создания диалога //////////////////////////////////////////////////////////////////////////////// type tdialogparams = record template : pchar; wndparent : thandle; end; Декларация базового класса tliteframe Базовый класс для окон и диалогов. Инкапсулирует в себе дескриптор окна и объявляет общую оконную процедуру. Реализует механизм message-процедур. //////////////////////////////////////////////////////////////////////////////// // tliteframe // _____________________________________________________________________________ // Базовый класс для объектов tlitewindow, tlitedialog, tlitedialogbox //////////////////////////////////////////////////////////////////////////////// type tliteframe = class(tobject) private fwndcallback: pointer; fwndhandle : thandle; fwndparent : thandle; function windowcallback(hwnd: hwnd; msg, wparam, lparam:longint): longint; stdcall; protected procedure windowprocedure(var msg: tmessage); virtual; public property wndhandle: thandle read fwndhandle; property wndcallback: pointer read fwndcallback; public constructor create(awndparent: thandle); virtual; destructor destroy; override; end; Декларация оконного класса tlitewindow Создание уникального класса окна и создание окна. Возможность субклассинга стороннего окна. //////////////////////////////////////////////////////////////////////////////// // tlitewindow // _____________________________________________________________________________ // Оконный класс //////////////////////////////////////////////////////////////////////////////// type tlitewindow = class(tliteframe) private fwndparams : twindowparams; fwndsubclass: pointer; protected procedure createwindowparams(var windowparams: twindowparams); virtual; public procedure defaulthandler(var msg); override; constructor create(awndparent: thandle); override; constructor createsubclassed(awnd: thandle); virtual; destructor destroy; override; end; Декларация диалогового класса tlitedialog Загрузка шаблона диалога и создание диалога. //////////////////////////////////////////////////////////////////////////////// // tlitedialog // _____________________________________________________________________________ // Диалоговый класс //////////////////////////////////////////////////////////////////////////////// type tlitedialog = class(tliteframe) private fdlgparams : tdialogparams; protected procedure createdialogparams(var dialogparams: tdialogparams); virtual; public procedure defaulthandler(var msg); override; constructor create(awndparent: thandle); override; destructor destroy; override; end; Декларация модального диалогового класса tlitedialogbox Загрузка шаблона диалога и создание диалога. Модальный показ диалога. //////////////////////////////////////////////////////////////////////////////// // tlitedialogbox // _____________________________________________________________________________ // Модальный диалоговый класс //////////////////////////////////////////////////////////////////////////////// type tlitedialogbox = class(tliteframe) private fdlgparams : tdialogparams; protected procedure createdialogparams(var dialogparams: tdialogparams); virtual; public procedure defaulthandler(var msg); override; public function showmodal: integer; end; Реализация базового класса tliteframe implementation //////////////////////////////////////////////////////////////////////////////// // tliteframe // _____________________________________________________________________________ // Инициализация / финализация //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // Конструктор //////////////////////////////////////////////////////////////////////////////// constructor tliteframe.create(awndparent: thandle); begin inherited create; // Запоминаем дескриптор родительского окна fwndparent := awndparent; // Создаем место под блок обратного вызова fwndcallback := virtualalloc(nil,12,mem_reserve or mem_commit, page_execute_readwrite); // Формируем блок обратного вызова asm mov eax, self mov ecx, [eax].tliteframe.fwndcallback mov word ptr [ecx+0], $6858 // pop eax mov dword ptr [ecx+2], eax // push _self_ mov word ptr [ecx+6], $e950 // push eax mov eax, offset(tliteframe.windowcallback) sub eax, ecx sub eax, 12 mov dword ptr [ecx+8], eax // jmp tliteframe.windowcallback end; end; //////////////////////////////////////////////////////////////////////////////// // Деструктор //////////////////////////////////////////////////////////////////////////////// destructor tliteframe.destroy; begin // Уничтожаем структуру блока обратного вызова virtualfree(fwndcallback, 0, mem_release); // Уничтожение по умолчанию inherited; end; //////////////////////////////////////////////////////////////////////////////// // tliteframe // _____________________________________________________________________________ // Функции обработки сообщений //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // Функция обратного вызова для получения оконных сообщений //////////////////////////////////////////////////////////////////////////////// function tliteframe.windowcallback(hwnd: hwnd; msg, wparam, lparam: integer): longint; var windowmsg : tmessage; begin // Запоминаем дескриптор окна, если это первый вызов оконной процедуры if fwndhandle = 0 then fwndhandle := hwnd; // Формируем сообщение windowmsg.msg := msg; windowmsg.wparam := wparam; windowmsg.lparam := lparam; // Обрабатываем его windowprocedure(windowmsg); // Возвращаем результат обратно системе result := windowmsg.result; end; //////////////////////////////////////////////////////////////////////////////// // Виртуальная функция для обработки оконных сообщений //////////////////////////////////////////////////////////////////////////////// procedure tliteframe.windowprocedure(var msg: tmessage); begin // Распределяем сообщения по обработчикам dispatch(msg); end; Реализация оконного класса tlitewindow //////////////////////////////////////////////////////////////////////////////// // tlitewindow // _____________________________________________________________________________ // Инициализация / финализация //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // Конструктор //////////////////////////////////////////////////////////////////////////////// constructor tlitewindow.create(awndparent: thandle); begin inherited; // Формируем параметры окна createwindowparams(fwndparams); // Регистрируем класс окна registerclass(fwndparams.windowclass); // Создаем окно with fwndparams do createwindowex(exstyle, windowclass.lpszclassname, caption, style, x, y, width, height, wndparent, wndmenu, hinstance, param ); end; //////////////////////////////////////////////////////////////////////////////// // Конструктор элемента с субклассингом //////////////////////////////////////////////////////////////////////////////// constructor tlitewindow.createsubclassed(awnd: thandle); begin inherited create(getparent(awnd)); // Сохраняем оконную функцию fwndsubclass := pointer(getwindowlong(awnd, gwl_wndproc)); // Сохраняем дескриптор окна fwndhandle := awnd; // Устанавливаем свою оконную функцию setwindowlong(fwndhandle, gwl_wndproc, dword(wndcallback)); end; //////////////////////////////////////////////////////////////////////////////// // Деструктор //////////////////////////////////////////////////////////////////////////////// destructor tlitewindow.destroy; begin // Наш объект - объект субклассиннга ? if fwndsubclass = nil then begin // Уничтожаем класс окна unregisterclass(fwndparams.windowclass.lpszclassname, hinstance); // Уничтожаем окно if iswindow(fwndhandle) then destroywindow(fwndhandle); end else // Восстанавливаем старую оконную функцию setwindowlong(fwndhandle, gwl_wndproc, dword(fwndsubclass)); // Уничтожение по умолчанию inherited; end; //////////////////////////////////////////////////////////////////////////////// // Формирование параметров окна по умолчанию //////////////////////////////////////////////////////////////////////////////// procedure tlitewindow.createwindowparams(var windowparams: twindowparams); var wndclassname : string; begin // Формируем имя класса str(dword(self), wndclassname); wndclassname := classname+':'+wndclassname; // Заполняем информацию о классе окна with fwndparams.windowclass do begin style := cs_dblclks; lpfnwndproc := wndcallback; cbclsextra := 0; cbwndextra := 0; lpszclassname := pchar(wndclassname); hinstance := hinstance; hicon := loadicon(0, idi_application); hcursor := loadcursor(0, idc_arrow); hbrbackground := color_btnface + 1; lpszmenuname := ''; end; // Заполняем информацию об окне with fwndparams do begin wndparent := fwndparent; caption := 'lite window'; style := ws_overlappedwindow or ws_visible; exstyle := 0; x := integer(cw_usedefault); y := integer(cw_usedefault); width := integer(cw_usedefault); height := integer(cw_usedefault); wndmenu := 0; param := nil; end; end; //////////////////////////////////////////////////////////////////////////////// // tlitewindow // _____________________________________________________________________________ // Функции обработки сообщений //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // Обработчик сообщений по умолчанию //////////////////////////////////////////////////////////////////////////////// procedure tlitewindow.defaulthandler(var msg); begin // Наш объект - объект субклассиннга ? if fwndsubclass = nil then // Вызываем системную функцию обработки сообщений with tmessage(msg) do result := defwindowproc(fwndhandle, msg, wparam, lparam) else // Вызываем старую оконную функцию обработки сообщений with tmessage(msg) do result := callwindowproc(fwndsubclass, fwndhandle, msg, wparam, lparam); end; Реализация диалогового класса tlitedialog //////////////////////////////////////////////////////////////////////////////// // tlitedialog // _____________________________________________________________________________ // Инициализация / финализация //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // Конструктор //////////////////////////////////////////////////////////////////////////////// constructor tlitedialog.create(awndparent: thandle); begin inherited; // Формируем параметры диалога createdialogparams(fdlgparams); // Создаем диалог with fdlgparams do createdialogparam(hinstance, template, wndparent, wndcallback, 0); end; //////////////////////////////////////////////////////////////////////////////// // Деструктор //////////////////////////////////////////////////////////////////////////////// destructor tlitedialog.destroy; begin // Уничтожаем диалог if iswindow(fwndhandle) then destroywindow(fwndhandle); // Уничтожение по умолчанию inherited; end; //////////////////////////////////////////////////////////////////////////////// // Формирование параметров диалога по умолчанию //////////////////////////////////////////////////////////////////////////////// procedure tlitedialog.createdialogparams(var dialogparams: tdialogparams); begin dialogparams.wndparent := fwndparent; dialogparams.template := ''; end; //////////////////////////////////////////////////////////////////////////////// // Обработка сообщений по умолчанию //////////////////////////////////////////////////////////////////////////////// procedure tlitedialog.defaulthandler(var msg); begin // Возвращаемые значения по умолчанию with tmessage(msg) do if msg = wm_initdialog then result := 1 else result := 0; end; Реализация модального диалогового класса tlitedialogbox //////////////////////////////////////////////////////////////////////////////// // tlitedialogbox // _____________________________________________________________________________ // Инициализация / финализация //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // Формирование параметров диалога по умолчанию //////////////////////////////////////////////////////////////////////////////// procedure tlitedialogbox.createdialogparams(var dialogparams: tdialogparams); begin dialogparams.wndparent := fwndparent; dialogparams.template := ''; end; //////////////////////////////////////////////////////////////////////////////// // Активизация модального диалога //////////////////////////////////////////////////////////////////////////////// function tlitedialogbox.showmodal: integer; begin // Формируем параметры диалога createdialogparams(fdlgparams); // Показываем диалог with fdlgparams do result := dialogboxparam(hinstance, template, wndparent, wndcallback, 0); end; //////////////////////////////////////////////////////////////////////////////// // Обработка сообщений по умолчанию //////////////////////////////////////////////////////////////////////////////// procedure tlitedialogbox.defaulthandler(var msg); begin // Возвращаемые значения по умолчанию with tmessage(msg) do if msg = wm_initdialog then result := 1 else result := 0; end; end.