OpenGL в Delphi

         

DLL


Файлы DLL (Dynamic Link Library, библиотека динамической компоновки) являются основой программной архитектуры Windows и отличаются от исполняемых файлов фактически только заголовком

Замечание
Но это не означает, что если переименовать DLL-файл, то он станет исполняемым имеется в виду заголовочная информация файла

Для загрузки операционной системы необходимо запустить файл win com, имеющий размер всего 25 Кбайт Как легко догадаться, в файл такого размера невозможно поместить код, реализующий всю ту гигантскую работу, которая производится по ходу выполнения любого приложения Этот файл является загрузчиком ядра операционной системы, физически размещенным в нескольких DLL-файлах.
Помимо кода, DLL-файлы могут хранить данные и ресурсы. Например, при изменении значка (ярлыка) пользователю предлагается на выбор набор значков из файла SHELL32. DLL.
Как мы уже знаем, для создания любой программы Windows, имеющей собственное окно, в проекте необходимо подключать как минимум два модуля' windows и Messages Первый из этих файлов содержит прототипы функций API и GDI Посмотрим на прототип одной из них:

function CreateDC;
external gdi32 name 'CreateDCA';
Здесь величина gdi32 - константа, описанная в этом же модуле'
const
gdi32 = 'gdi32. dll';

Таким образом, функция CreateDC физически размешена в файле gdi32 dll и каждое приложение, использующее функции GDI, обращается к этой библиотеке.
Приблизительно так же выглядят прототипы остальных функций и процедур, но указываемые в прототипе имена библиотек индивидуальны для каждой из них.
Обратите внимание, что в этом же файле находится описание константы opengl32 Использование DLL, в частности, позволяет операционной системе экономить память.
Приложение не умеет ни создавать окно, ни выводить в него информацию и не содержит кода для этих действий. Все запущенные приложения (клиенты) передают соответствующие команды файлу gdi32. dll (серверу), который отрабатывает их, осуществляя вывод на устройство, ссылку на контекст которого передается в качестве первого аргумента функции. При этом клиентов обслуживает единственная копия сервера в памяти. Помимо важности DLL как основы программной архитектуры операционной системы, представление о динамических библиотеках необходимо иметь каждому разработчику программных систем. Многие программные системы строятся по следующему принципу: основной код и данные размещаются в динамических библиотеках, а исполняемому файлу отводится роль загрузчика. Подробнее о такой организации мы поговорим в главе 5. Библиотека OpenGL физически также размещена в виде двух DLL-файлов: opengl23. dll и glu32. dll. Первый из этих файлов и есть собственно библиотека OpenGL. Назначение его - осуществление взаимодействия с акселератором или программная эмуляция ускорителя за счет центрального процессора. Поддержка ЗD-акселерации осуществляется с помощью полного (устанавливаемого) клиентского драйвера (Installable Client Driver, ICD) и мини-драйвера (Mini-Client Driver, MCD).
Библиотека OpenGL реализована по клиент-серверной схеме, т. e. ее одновременно может использовать несколько приложений при единственной копии сервера в памяти или вообще при удаленном расположении сервера (сервер в принципе может располагаться и не на компьютере клиента). Иногда программисту, как, например, в случае с OpenGL, бывает необходимо просмотреть список функций и процедур, размещенных в конкретном файле DLL. Для этого можно воспользоваться либо утилитой быстрого просмотра, поставляемой в составе операционной системы, либо утилитой tdump. exe, поставляемой в составе Delphi.
Для вызова утилиты быстрого просмотра установите курсор мыши на значок нужного файла и нажмите правую кнопку. В появившемся меню должен присутствовать пункт "Быстрый просмотр", если этого пункта нет, то требуется установить компонент операционной системы "Быстрый просмотр". Для использования утилиты tdump скопируйте ее и необходимый dll-файл в отдельный каталог. Запустите его с параметрами <имя анализируемого файла> и <имя файла-результата>, например:

TDUMP. EXE opengl32. dll opengl. txt

В файле opengl. txt будет выдана информация, извлеченная утилитой из заголовка библиотеки, аналогичная той, что выводится утилитой быстрого просмотра. Информация группируется по секциям, среди которых наиболее
часто программисту требуется секция экспортируемых функций для уточнения содержимого библиотеки.
Итак, чаще всего DLL представляет собой набор функций и процедур. Как говорится в справке Delphi по DLL, "динамические библиотеки являются идеалом для многоязыковых проектов". Это действительно так: при использовании OpenGL совершенно безразлично, в какой среде созданы сама библиотека и вызывающие ее модули.



Формат пиксела


Напомню, ссылка на контекст устройства содержит характеристики устройства и средства отображения. Упрощенно говоря, получив ссылку на контекст устройства, мы берем в руки простой либо цветной карандаш или кисть с палитрой в миллионы оттенков.
Сервер OpenGL, прежде чем приступать к работе, также должен определиться, на каком оборудовании ему придется работать Это может быть скромная персоналка, а может быть и мощная графическая станция
Прежде чем получить контекст воспроизведения, сервер OpenGL должен получить детальные характеристики используемого оборудования. Эти характеристики хранятся в специальной структуре, тип которой - TPlxelFormatDescriptor (описание формата пиксела). Формат пиксела определяет конфигурацию буфера цвета и вспомогательных буферов.
Наберите в тексте модуля фразу "PixelFormatDescriptor", нажмите клавишу <Fl>, и вы получите подробную информацию об этом типе Обращаю внимание: мы видим раздел справки Microsoft, рассчитанной на программистов, использующих С или C++, поэтому описание содержит термины и стилистику именно этих языков. По традиции Delphi имена типов начинаются с префикса Т, но нам неудасться найти помощь по термину TPixelFormatDescnptor.
К сожалению, это не единственное неудобство, которое придется испытать Например, если мы заглянем в файл windows. pas и найдем описание записи TPixelFormatDescriptor, то обнаружим, что в файле помощи не указаны некоторые константы, имеющие отношение к этому типу, а именно:

PFD_SWAP_LAYER_BUFFERS,
PFD_GENERIC_ACCELERATED и PFD_DEPTH_DONTCARE.

А константа, названная PFD_DOUBLE__BUFFER_DONTCARE, по-видимому, соответствует константе, описанной в модуле windows. pas как PFD_DOUBLEBUFFER
DONTCARE.
Итак, смысл структуры pixelFormatDescriptor - детальное описание графической системы, на которой происходит работа. Вас может озадачить дотошность этого описания, но, уверяю, особое внимание из всего этого описания требуют совсем немногие вещи.
В проекте я привел описание всех полей структуры TPixelFormatDescriptor на русском языке (в момент их первоначального заполнения). Делается это в процедуре setDCPixelFormat, вызываемой между получением ссылки на контекст устройства и созданием ссылки на контекст воспроизведения OpenGL.
Посмотрим подробнее, что там делается. Полям структуры присваиваются желаемые значения, затем вызовом функции choosePixelFormat осуществляется запрос системе, поддерживается ли на данном рабочем месте выбранный формат пиксела, и, наконец, вызовом функции SetPixelFormat устанавливается формат пиксела в контексте устройства.
Функция choosePixelFormat возвращает индекс формата пиксела, который нам нужен в качестве аргумента функции SetPixelFormat.
Заполнив поля структуры TPixelFormatDescriptor, мы определяемся со своими пожеланиями к графической системе, на которой будет происходить работа приложения, OpenGL подбирает наиболее подходящий к нашим пожеланиям формат и устанавливает уже его в качестве формата пиксела для последующей работы. Наши пожелания корректируются сервером OpenGL применительно к реальным характеристикам системы.
To, что OpenGL не позволит нам установить нереальный для конкретного рабочего места формат пиксела, значительно облегчает нашу задачу. Предполагая, что разработанное приложение будет работать на машинах разного класса, можно запросить "всего побольше", а уж OpenGL разберется в каждом конкретном случае, каковы параметры и возможности оборудования, на котором в данный момент выполняется приложение.
На этом можно было бы и закончить разговор о формате пиксела, если бы мы могли полностью довериться выбору OpenGL.
Обратим внимание на поле структуры "битовые флаги", dwFlags. To, как мы зададим значение флагов, может существенно сказаться на работе нашего
приложения, и наобум задавать эти значения не стоит. Тем более что некоторые флаги совместно "не уживаются", а некоторые присутствуют только в паре с определенными флагами.
В рассматриваемом примере я присвоил флагам значение PFD_DRAW_TO WINDOW or PFD_SUPPORT_OPENGL, сообщив тем самым системе, что собираюсь осуществлять вывод в окно и что моя система в принципе поддерживает OpenGL (Рисунок 1. 1).


Подключение OpenGL



В этой главе дается представление о том, как в действительности работает Windows-приложение. Для понимания действий, требуемых для подключения OpenGL, необходимо иметь представление о важнейших понятиях операционной системы Windows, завуалированных в Delphi и напрямую обычно не используемых программистом. В качестве примера подробно разбирается минимальная программа, использующая OpenGL.
Весь последующий материал книги основывается на содержании этой главы, поэтому я рекомендую прочесть ее достаточно внимательно. Проекты примеров записаны на дискете в каталоге Chapterl.



Контекст устройства и контекст воспроизведения


Мы уже знаем, что ссылка на контекст устройства - это величина типа HDC. Для ее получения можно вызвать функцию GetDC, аргументом которой является ссылка на нужное окно.
Ссылке на контекст устройства соответствует свойство canvas. Handie формы, принтера и некоторых компонентов Delphi.
Каков же все-таки смысл контекста устройства, если он и так связан с однозначно определенным объектом - окном, областью памяти или принтером, и зачем передавать дополнительно какую-то информацию об однозначно определенном объекте?
Для ответа на эти вопросы обратим внимание на замечательное свойство вывода в Windows, состоящее в том, что одними и теми же функциями осуществляется вывод на различные устройства. Строки программы

Forml. Canvas. Ellipse (0, 0, 100, 100);

И

Printer. BeginDoc;
Printer. Canvas. Ellipse (0, 0, 100, 100);
Printer. EndDoc;



рисуют один и тот же круг как на поверхности формы, так и в распечатываемом документе, т. e. на различных устройствах, причем если мы будем выводить разноцветную картинку на монохромный принтер, он справится с этой задачей, передавая цвета оттенками серого.
Даже если мы рисуем только на поле формы, мы имеем дело с различными устройствами - нам неизвестно, какова графическая плата компьютера и каковы характеристики текущей установки настроек экрана. Например, имея в своем распоряжении более 16 миллионов цветов, приложение не заботится об отображении этой богатой палитры на экране, располагающем всего 256 цветами. Такие вопросы приложение перекладывает на плечи операционной системы, решающей их посредством использования драйверов устройств
Для того чтобы воспользоваться функциями воспроизведения Windows, приложению необходимо только указать ссылку на контекст устройства, содержащий средства и характеристики устройства вывода.
Справочный файл Win32 Programmer's Reference фирмы Microsoft, поставляемый в составе Delphi, o контексте устройства сообщает следующее "Контекст устройства является структурой, которая определяет комплект графических объектов и связанных с ними атрибутов и графические режимы, влияющие на вывод Графический объект включает в себя карандаш для изображения линии, кисть для закраски и заполнения, растр для копирования или прокрутки частей экрана, палитру для определения комплекта доступных цветов, области для отсечения и других операций, маршрут для операций рисования"
В OpenGL имеется аналогичное ссылке на контекст устройства понятие ссылка на контекст воспроизведения
Графическая система OpenGL, как и любое другое приложение Windows (хоть и размещенное в DLL), также нуждается в ссылке на устройство, на которое будет осуществляться вывод Это специальная ссылка на контекст воспроизведения - величина типа HGLRC (Handle openGL Rendering Context, ссылка на контекст воспроизведения OpenGL).

Замечание
Контекст устройства Windows содержит информацию, относящуюся к графическим компонентам GDI, a контекст воспроизведения содержит информацию, относящуюся к OpenGL, т e играет такую же роль, что и контекст устройства для GDI
В частности, упомянутые контексты являются хранилищами состояния системы, например, хранят информацию о текущем цвете карандаша



Минимальная программа OpenGL


Рассмотрев основные вопросы функционирования приложения и его взаимодействия с операционной системой, мы можем перейти к изучению собственно OpenGL. Заглянем в подкаталог Ex20, содержащий проект минимальной программы, использующей OpenGL B программе с помощью команд OpenGL окно формы окрашивается в голубоватый цвет Во-первых, обратите внимание на то, что список uses дополнен модулем OpenGL - это программист должен сделать сам Раздел private описания класса формы содержит строку

hrc: HGLRC; // ссылка на контекст воспроизведения

Смысл этой величины мы рассмотрели в предыдущем разделе. Обработчик события OnCreate формы содержит следующие строки:

SetDCPixelFormat(Canvas. Handle); //задаем формат пиксела
hrc: = wglCreateContext(Canvas. Handle); // создаем контекст воспроизведения

Первая строка - обращение к описанной в этом же модуле пользовательской процедуре, задающей формат пиксела

procedure SetDCPixelFormat (hdc: HDC);
var
pfd: TPixelFormatDescriptor;
nPixelFormat: Integer;
begin
FillChar (pfd, SizeOf (pfd), 0);
nPixelFormat: = ChoosePixelFormat (hdc, @pfd);
SetPixelFormat (hdc, nPixelFormat, @pfd);
end;

По поводу формата пиксела мы подробнее поговорим в следующем разделе Во второй строке обработчика OnCreate, как ясно из комментария, задается величина типа HGLRC, т. e. создается контекст воспроизведения. Аргументом функции wglCreateContext является ссылка на контекст устройства, на который будет осуществляться вывод Сейчас устройством вывода служит окно формы Для получения этого контекста OpenGL необходима величина типа HDC Здесь, как и во многих других примерах, мы используем факт, что canvas. Handle и есть ссылка на контекст устройства, связанная с окном формы.
Поскольку это первая функция, имеющая непосредственно отношение к OpenGL, то я немного отвлекусь на некоторые общие пояснения Как уже говорилось, только начиная с пятой версии Delphi поставляется с системой помощи, удовлетворительно настроенной с точки зрения получения справок по командам OpenGL и функциям API, в предыдущих версиях ее вроде как и нет Однако на самом деле такая помощь доступна и в ранних версиях, и самый простой способ ее получения - контекстная подсказка По командам OpenGL справки мы будем получать точно так же, как и по функциям API, т. e если вам необходима более подробная информация, например, о функции wglcreateContext, то установите курсор на строку с этой функцией и нажмите клавишу <F1>.
Функция wgicreateContext физически размещается в файле opengl32 dll, а прототип ее находится в файле windows. pas В этот файл также помещены прототипы всех функций и процедур, имеющих отношение к реализации OpenGL под Windows, a прототипы собственно команд OpenGL расположены в файле opengl pas функции и процедуры, имеющие отношение только к Windows-версии OpenGL, обычно имеют приставку wgl, как, например, wglcreateContext, но могут и не иметь такой приставки, как, например, SwapBuffers. Собственно команды OpenGL имеют приставки gl или glu в зависимости от размещения в библиотеках opengl32. dll и glu32. dll, соответственно. Итак, контекст воспроизведения создан, и теперь можно осуществлять вывод командами OpenGL. Обработка события onpaint выглядит следующим образом:

wglMakeCurrent (Canvas. Handle, hrc); // установить контекст
glClearColor (0. 5, 0. 5, 0. 75, 1. 0); // цвет фона
glClear (GL_COLOR_BUFFER_BIT); // очистка буфера цвета
wglMakeCurrent (0, 0); // освободить контекст

Первая строка делает контекст воспроизведения текущим, т e занимает его для последующего вывода. Далее задаем цвет фона. Следующую строку будем понимать как очистку экрана и окрашивание его заданным цветом После работы освобождаем контекст.

Замечание
Согласно справке, для освобождения контекста воспроизведения оба параметра должны быть установлены в NULL, но хотя компилятор и пропустит такие значения, во время выполнения получим ошибку "Invalid variant type conversion", так что будем всегда для освобождения контекста задавать эти значения нулевыми Обработка события onDestroy формы состоит из одной строки:

wglDeleteContext (hrc);

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

Замечание
Очень важно запомнить, что процедуры и функции, имена которых начинаются на gl или glu, т e команды OpenGL, имеют какой-либо результат только при установленном контексте воспроизведения

Вернемся к команде glclearcolor, определяющей цвет фона. У нее четыре аргумента, вещественные числа, первые три из которых задают долю красного, зеленого и синего в результирующем цвете. О четвертом аргументе мы поговорим в четвертой главе, здесь я его значение задал равным единице. Можете варьировать это значение произвольно, в данном примере это никак не скажется, так что пока можете просто не обращать внимания на этот аргумент. Согласно справке, все четыре аргумента функции glclearColor имеют тип GLclampf, соответствующий вещественным числам в пределах от нуля до единицы. О типах OpenGL подробнее поговорим ниже
Теперь обратитесь к проекту из подкаталога Ex21, представляющему собой еще один вариант минимальной программы Delphi, использующей OpenGL, но без VCL. Надеюсь, читатель, вы уже можете ориентироваться в таких программах. Отличие от предыдущего примера состоит в том, что приходится самому описывать все те действия, которые при обычном подходе выполняет за нас Delphi, т. e. в данном случае "вручную" создавать ссылку на контекст устройства, устанавливать, освобождать и удалять ее.
Если вы используете Delphi версии три или четыре, вы, возможно, столкнетесь с одной небольшой проблемой. Если запускать проекты, использующие OpenGL, под управлением среды Delphi, программа может случайным образом аварийно завершаться. Оборот "случайным образом" здесь я употребил постольку, поскольку один и тот же проект может привести к аварийному завершению, а может и работать вполне успешно.
Я сталкивался с этой проблемой на компьютерах с различной конфигурацией и с различными версиями операционной системы, и, по-видимому, она связана с некорректным взаимодействием среды Delphi c драйверами. Если подобная проблема возникла и у вас, я рекомендую просто не запускать под управлением среды проекты, использующие OpenGL, a запускать собственно откомпилированные модули. В пятой версии Delphi такая ситуация не возникала, так что, по-видимому, этот недостаток разработчиками выявлен и устранен.



Минимальная Windows-программа


Посмотрите на проект из подкаталога Ex07 - код минимальной программы Windows. Минимальной она является в том смысле, что в результате получается просто пустое окно. Также ее можно назвать минимальной программой потому, что откомпилированный модуль занимает всего около 16 Кбайт. Приложение меньшего размера, имеющее собственное окно, получить уже никак не удастся, хотя могут быть и программы еще короче и меньше, например, такая:

program p; uses Windows;
begin
MessageBeep(mb_ok)
end.

Единственное, что делает эта программа, - подача звукового сигнала. Однако вернемся к коду проекта из подкаталога Ex07. Первое, на что необходимо обратить внимание: в списке uses указаны только два модуля - windows и Messages. Это означает, что в программе используются исключительно функции API, и как следствие - длинный С-подобный код. И действительно, перенести эту и подобные ей программы на С потребует немного усилий.
Данная программа для нас крайне важна, поскольку она станет шаблоном для некоторых других примеров.
Программу условно можно разделить на две части - описание оконной функции и собственно головная программа.
В оконной функции задается реакция приложения на сообщения Windows. Именно оконную функцию необходимо дополнять кодом обработчиков сообщений для расширения функциональности приложения. Нечто подобное мы имеем в событийно-ориентированном программировании, но, конечно, в совершенно ином качестве.
В минимальной программе задана реакция на единственное сообщение wm_Destroy. На все остальные сообщения вызывается функция ядра операционной системы DefWindowProc, осуществляющая стандартную реакцию окна. Полученное окно ведет себя обычно, его можно изменять в размерах, минимизировать, максимизировать. Приложение реагирует также привычным образом, однако необходимости кодировать все эти действия нет.
В принципе, можно удалить и обработку сообщения wm_Destroy, но в этом случае приложение после завершения работы оставит след в памяти, съедающий ресурсы операционной системы.
Значение переменной-результата обнуляется в начале описания оконной функции для предотвращения замечания компилятора о возможной неинициализации переменной.
Головная программа начинается с того, что определяются атрибуты окна. Термин "структура", перешедший в Delphi из языка С, соответствует термину "запись". Термин "класс окна" имеет к терминологии объектно-ориентированного программирования скорее приближенное, чем непосредственное отношение.
Значения, задаваемые полям структуры, определяют свойства окна. В этой программе я задал значения всем полям, что, в принципе, делать не обязательно, мы обязаны указать адрес оконной функции, а все остальные значения можно брать по умолчанию. Однако в этом случае окно будет выглядеть или вести себя необычно. Например, при запуске любого приложения операционная система задает курсор для него в виде песочных часов, и если мы не станем явно задавать вид курсора в классе окна, курсор окна приложения так и останется в виде песочных часов.
После заполнения полей класса окна его необходимо зарегистрировать в операционной системе.
В примере я анализирую результат, возвращаемый функцией Registerclass. Это также делать не обязательно, невозможность регистрации класса окна - ситуация крайне редкая при условии корректного заполнения его полей.
Следующие строки можно интерпретировать как "создание конкретного экземпляра на базе зарегистрированного класса" Очень похоже на ООП, но схожесть эта весьма приблизительная и объясняется тем, что первая версия Windows создавалась в эпоху первоначального становления концепции объектно-ориентированного программирования.
При создании окна мы уточняем его некоторые дополнительные свойства - заголовок, положение, размеры и прочее. Значения этих свойств задаются аргументами функции createWindow, возвращающей внимание, величину типа HWND - ту самую ссылку на окно, что в Delphi называется Handle. После создания окна его можно отобразить - вызываем функцию showWindow. Как правило, окно сразу после этого перерисовывают вызовом функции updateWindow - действие тоже необязательное, но для корректной работы приложения удалять эту строку нежелательно.
Далее следует цикл обработки сообщений, наиважнейшее место в программе, фактически это и есть собственно работа приложения. В нем происходит диалог приложения с операционной системой: извлечение очередного сообщения из очереди и передача его для обработки в оконную функцию. Как уже говорилось, функции API и сообщения - темы очень обширные, и я не ставлю целью исчерпывающе осветить эти темы. В разумных объемах я смогу изложить только самое необходимое, а более подробную информацию можно получить в оперативной помощи Delphi.
К сожалению, версии Delphi 3 и 4 поставляются с системой помощи, не настроенной должным образом для получения информации по функциям API и командам OpenGL. Если судить по содержанию помощи, то может сложиться впечатление, что эти разделы в ней вообще отсутствуют.
Можно либо настраивать справочную систему самостоятельно, либо, что я и предлагаю, пользоваться контекстной подсказкой - для получения сведений по любой функции API достаточно поставить курсор на соответствующую строку и нажать клавишу <F1>.

Замечание
В пятой версии Delphi система помощи настроена вполне удовлетворительно, а сами файлы помощи обновлены

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

Замечание
Итак, в приложениях Windows на самом деле управление постоянно находится в цикле обработки сообщений, событийно-ориентированная схема как таковая отсутствует. При получении окном очередного сообщения управление передается оконной функции, в которой задается реакция на него, либо вызывается функция API DefWindowProc для реакции, принятой по умолчанию

Приведенный пример лишь отдаленно напоминает то, что мы имеем в Delphi - событийно-ориентированное программирование, основанное на объектах. Конечно, совершить путь, обратный исторически пройденному, нелегко. Отказаться от библиотеки VCL при написании программ на Delphi для многих оказывается непосильным. Вознаграждением здесь может стать миниатюрность полученных программ: как мы видим, минимальная программа уменьшилась минимум в десять раз, быстрее загружается, выполняется и быстрее выгружается из памяти.
Такой размер нелегко, а порой и невозможно получить в любом другом компиляторе, кроме как в Delphi. К тому же проекты, в списке uses которых стоят только windows и Messages, компилируются еще стремительнее, несмотря на устрашающую массивность кода.
А сейчас посмотрите проект из подкаталога Ex08, где окно дополнено кнопкой и меткой. В коде появились новые строки, а простейшие манипуляции, например, изменение шрифта метки, еще потребуют дополнительных строк.
Подобный обширный код обычно обескураживает новичков, поэтому я не буду злоупотреблять такими примерами и ограничусь только самыми необходимыми для нас темами - как создать обработчик мыши, клавиатуры и таймера.
Я не буду заставлять вас писать все программы таким изнурительным способом, нам просто нужно иметь представление о работе базовых механизмов, чтобы лучше понимать, что делает за нас Delphi и что необходимо сделать, чтобы подключить OpenGL к нашим проектам.



Перехват сообщений


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

Замечание
В угоду программистам, до сих пор использующим третью версию Delphi для получения сравнительно небольших по объему исполняемых модулей, все примеры данной книги я разрабатывал именно в этой версии, но все они прекрасно компилируются и в более старших версиях

У программистов всегда будет возникать потребность обрабатывать сообщения, не имеющие аналогов в списке событий, либо самостоятельно перехватывать сообщения, для которых есть аналоги среди событий формы и компонентов. Как увидим ниже, сделать это несложно.
Для начала обратимся к проекту из подкаталога Exl3, где мы опять будем программировать без VCL. Задача состоит в том, чтобы при двойном щелчке левой кнопкой мыши выводились текущие координаты указателя. Прежде всего обратите внимание, что в стиль окна добавилась константа cs_Dblclks, чтобы окно могло реагировать на двойной щелчок, а оконная функция дополнилась обработкой сообщения wm_LButtonDblclk, в которой выводятся координаты курсора.
Теперь создадим обработчик этого же сообщения в проекте Delphi обычного типа (проект располагается в подкаталоге Exl4).
Описание класса формы я дополнил разделом protected, в который поместил forward-описание соответствующей процедуры:

procedure MesDblClick (var MyMessage TWMMouse); message
wm LButtonDblClk;

Замечание
Как правило, перехватчики сообщений для повышения надежности работы приложения описываются в блоке protected

Имя процедуры я задал таким, чтобы не появлялось предупреждение компилятора о том, что я перекрываю соответствующее событие формы.
Служебное слово message указывает на то, что процедура будет перехватывать сообщение, мнемонику которого указывают за этим словом. Тип аргумента процедуры-перехватчика индивидуален для каждого сообщения. Имя аргумента произвольно, но, конечно, нельзя брать в качестве имени служебное СЛОВО message.
Пожалуй, самым сложным в процессе описания перехвата сообщений является определение типа аргумента процедуры, здесь оперативная помощь оказывается малополезной. В четвертой и пятой версиях Delphi инспектор кода облегчает задачу, но незначительно.
Чтобы решить эту задачу для сообщения wm_LButtonDblclk, я просмотрел все вхождения фразы "LButtonDblClk" в файле messages. pas и обнаружил строку, подсказавшую решение:

TWMLButtonDblClk = TWMMouse;

В этом же файле я нашел описание структуры Twmouse, чем и воспользовался при кодировании процедуры MesDblclick для получения координат курсора Обратите внимание, что здесь не пришлось самостоятельно разбивать по словам значение параметра, как в предыдущем проекте Итак, в рассматриваемом примере перехватывается сообщение "двойной щелчок левой кнопки мыши". Событие Dblclick формы наступает точно в такой же ситуации. Выясним, какая из двух процедур, перехватчик сообщения или обработчик события, имеет преимущество или же они равноправны. Создайте обработчик события OnDblclick формы - вывод любого тестового сообщения (можете воспользоваться готовым проектом из подкаталога Exl5). Запустите проект, дважды щелкните на форме. Процедура-перехватчик среагирует первой и единственной, до обработчика события очередь не дойдет.

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

В проекте из подкаталога Exl6 создан обработчик сообщения wmPaint - перерисовка окна:

protected
procedure WMPaint(var Msg: TWMPaint); message WM_PAINT;
...
procedure TForml. WMPaint(var Msg: TWMPaint);
var
ps: TPaintStruct;
begin
BeginPaint(Handle, ps);
Rectangle (Canvas. Handle, 10, 10, 100, 100);
EndPaint(Handle, ps);
end;

Строки Beginpaint и EndPaint присутствуют для более корректной работы приложения, при их удалении появляется неприятное мерцание при изменении размеров окна. Обратите внимание на функцию построения прямоугольника: я воспользовался тем, что свойство canvas. Handle и есть ссылка на контекст устройства, соответствующая окну формы
Точно так же, как перехватчики сообщений предпочтительнее обработчиков событий, использование непосредственно ссылок на окно и ссылок на контекст устройства предпочтительнее использования их аналогов из мира ООП.
Запомните этот пример, таким приемом мы будем пользоваться очень часто



Почему приложения Delphi имеют большой размер


Этот вопрос часто задают начинающие программисты при сравнении приложений, созданных в различных средах программирования. Действительно, минимальное приложение, созданное в различных версиях Delphi, может достигать от 170 до 290 Кбайт. Это очень большая цифра для операционной среды Windows, в компиляторах C++ она составляет порядка 40 Кбайт. Конечно, это не катастрофическая проблема, когда емкости накопителей измеряются гигабайтами, и средний пользователь, как правило, не обращает внимания на размер файла. Неудобства возникают, например, при распространении приложений по сети.
Использование пакетов значительно снимает остроту проблемы для масштабных проектов, но суммарный вес приложения и используемых пакетов все равно значителен.
Краткий ответ на вопрос, поставленный в заголовке раздела, состоит в том, что большой размер откомпилированных приложений является платой за невероятное удобство проектирования, предоставляемое Delphi. Архитектура среды программирования, RTTI, компонентный подход - все это превращает Delphi в поразительно мощный инструмент. С помощью Delphi легко написать приложения, в которых, например, динамически создаются интерфейсные элементы любого типа (класса).
Однако приложения среднего уровня не используют и не нуждаются в этих мощных возможностях. Часто ли вам встречались приложения, предлагающие пользователю перед вводом/выводом данных определиться, с помощью каких интерфейсных элементов будет осуществляться ввод или вывод, а затем разместить эти элементы на окне в удобных местах? И пользователи, и разработчики в таких средствах, как правило, не испытывают необходимости.
Однако откомпилированный модуль содержит в себе весь тот код, благодаря которому в Delphi так легко производить манипуляции со свойствами и методами объектов. К примеру, если просмотреть содержимое откомпилированного модуля, то мы встретим в нем фразы, имеющие к собственно операционной системе косвенное отношение, например, "OnKeyDown" или другие термины Delphi.
Дело здесь не в несовершенстве компилятора, компилятор Delphi оптимизирует код превосходно, дело в самой идеологии Delphi.
Очень часто после выяснения этого факта начинающие программисты задают вопрос, как избавиться от RTTI, от включения "ненужного" кода в исполняемые модули.
К сожалению, это сделать невозможно. Кардинально проблема решается только через отказ от использования библиотеки классов Delphi, т. e. программирование без VCL.


Подробнее о заголовочном файле opengl. pas


Вместе с Delphi версии три и выше поставляется заголовочный файл, позволяющий подключать библиотеку OpenGL к проектам Delphi. Этот файл содержит только прототипы используемых функций и процедур, сами функции и процедуры размещены в соответствующих файлах DLL.
Например, в секции interface заголовочного файла содержится следующее forward-описание использованной нами во всех предыдущих примерах процедуры:

procedure glClearColor (red, green, blue, alpha: GLclampf); stdcall;

В секции implementation модуля собственно описание процедуры выглядит так:

procedure glClearColor; external opengl32;

Служебное слово stdcall, указанное для всех процедур и функций в этом модуле, означает стандартный вызов функции или процедуры и определяет некоторые правила обмена данными между приложением и библиотекой: как передаются параметры (через регистры или стек), в каком порядке перечисляются параметры и кто, приложение или библиотека, очищает области после их использования.
Служебное слово external указывается для функций, подключаемых из библиотек. После него следует указание имени подключаемой библиотеки. Здесь opengl32 - константа, определяемая, как я отмечал раньше, в другом модуле - в windows. pas:

opengl32 = 'opengl32. dll1;

Константа, соответствующая второй используемой библиотеке, содержится в модуле opengl.

pas:
const
glu32 = 'glu32. dll';

Содержательная часть модуля opengl, соответствующая его инициализации, содержит единственную строку:

Set8087CW($133F);

В справке по этой процедуре можно прочитать, что она служит для включения/выключения исключений при проведении операций с плавающей точкой. Здесь же отмечается, что для OpenGL рекомендуется эти исключения отключать.
Разброс описаний констант и некоторых функций и процедур по разным модулям объясняется тем, напомню, что информация, относящаяся к реализации OpenGL под Windows, помещена в заголовочный файл windows pas Это логично и объяснимо, но в некоторых случаях может вызвать дополнительные проблемы, например, при использовании альтернативных заголовочных файлов или библиотек.
Далее мы встретимся с ситуациями, когда выяснится, что в стандартном модуле, поставляемом с Delphi, не окажется прототипов многих полезных команд. Там же попробуем найти объяснение этому факту.
Альтернативные заголовочные файлы, разработанные некоторыми сторонними организациями или энтузиастами, оказываются более полными в этой части и поэтому полезны в некоторых ситуациях, но из-за того, что модуль windows pas уже содержит описание некоторых процедур, связанных с OpenGL, могут возникнуть накладки.
Следующая возможная ситуация - использование других, нежели производства фирмы Microsoft, библиотек, например, версии OpenGL фирмы SGI, которую отличают более высокие скоростные характеристики по некоторым показателям. В приложении "OpenGL в Интернете" я указал адрес, по которому вы можете найти ее дистрибутив Правда, эту версию OpenGL имеет смысл использовать только на компьютерах, не оснащенных акселератором, поскольку она не может самостоятельно использовать драйверы ICD и MCD, а переадресует все вызовы в Microsoft OpenGL, чем сводятся на нет все ее достоинства В свое время SGI обещала в следующей версии устранить этот недостаток, однако планы этой корпорации по поддержке операционной системы Windows и сотрудничеству с Microsoft, по-видимому, изменились, так что эта работа, возможно, не будет завершена.
Если вам потребуется модифицировать заголовочные файлы для подключения другой библиотеки, то придется учитывать несколько нюансов, связанных с версиями используемой Delphi.
Здесь я смогу только обозначить эти тонкости Хотя я и нашел решение проблемы подключения других версий OpenGL, но не имею возможности поделиться им - файл windows pas даже в сжатом виде имеет очень большой размер, поэтому на одной дискете модифицированные версии этого файла не разместить (для всех версий Delphi).
Первое, с чего надо начать, - это изменить значения констант opengl32 и glu32 в заголовочных файлах и установить имена соответствующих файлов библиотек. Если вас не страшит то, что модифицируются стандартные модули Delphi, можете сразу компилировать проекты Если же вы модифицируете копии стандартных модулей, то придется учитывать, что почти каждый из стандартных модулей Delphi в списке uses подключает модуль windows, и, возможно, придется переставлять порядок модулей в списке uses.
Обязательно посмотрите с помощью утилит быстрого просмотра или tdump заголовочную информацию откомпилированного приложения для того, что-бы убедиться, что оно использует только те библиотеки, которые подразумевались При наличии нескольких библиотек невозможна ситуация, когда функции берутся вперемешку из разных библиотек.
Если используется только одна определенная библиотека, но контекст воспроизведения оказывается невозможным получить, попробуйте явным образом заполнять формат пиксела, указывая требуемые значения для каждого поля - это может помочь
В приложении "OpenGL в Интернете" я указал адреса, по которым вы можете получить свободно распространяемые заголовочные файлы независимых разработчиков, более полные, чем стандартный модуль Ими можно воспользоваться и в том случае, если у вас не получится самостоятельно подключать другие версии OpenGL.



Полноэкранные приложения


Такие приложения достойны отдельного разговора в силу их особой значимости. Занять всю область экрана вам может потребоваться для того, чтобы повысить эффектность и зрелищность вашего проекта, особенно если на экране присутствует много объектов.
Прежде всего, необходимо сказать о том, что некоторые графические акселераторы поддерживают ускорение только в полноэкранном режиме и при определенных установках экрана, например, только при разрешении экрана 640x480 и цветовой палитре 16 бит на пиксел.
К сожалению, мне придется сообщить о наличии здесь некоторых проблем, впрочем, мы их успешно разрешим. Вернитесь к последнему примеру проекту из подкаталога Ex27, где вывод средствами OpenGL осуществляется на окно без рамки и области заголовка. Поменяйте свойство windowstate формы на wsMaximized, чтобы окно после запуска раскрывалось на весь экран. Запустите проект или откомпилированный модуль. Что у вас получится, я предсказать не могу, он зависит от конкретной конфигурации машины. Если на вашем компьютере все происходит в ожидаемом режиме, т. e. весь экран занят окном голубоватого цвета, вы избавлены от множества проблем, если только не собираетесь распространять свои приложения. Дело в том, что я тестировал подобные проекты на компьютерах самой различной конфигурации и обнаружил, что чаще всего результат получается обескураживающий и неприятный: окно раскрывается некорректно, не полностью занимая экран. Причем неприятности появляются именно в связи c использованием OpenGL, простые проекты вполне справляются с задачей раскрывания на весь экран. Возможно, корень проблемы в несогласованности работы драйверов (как всегда!) или ошибках операционной системы.
Однако решение проблемы оказывается совсем простым
Посмотрите пример из подкаталога Ex28. Это тот же пример, что и предыдущий, только немного измененный. Свойство windowstate окна формы Установлено в wsNormal, а обработчик события onCreate дополнился строкой:

windowState: = wsMaximized;

Теперь все работает превосходно, окно действительно раскрывается на весь экран.
Свойство FormStyle окна можно задать как fsstayOnTop, и тогда приложение будет вести себя так, как многие профессиональные игры, не позволяя переключиться на другие приложения.
Другое решение проблемы очень похоже на предыдущее. Посмотрите пример Ex29 - модифицированный пример вывода на поверхность панели. Панель занимает всю клиентскую область окна (свойство Align имеет значение alclient), а окно формы максимизировано и не имеет рамки.
Надеюсь, у вас теперь не будет неприятностей при попытке захватить всю область экрана, хотя нелегко объяснить, почему обычный метод не работает, а такой метод, по сути, ничем от него не отличающийся, работает.
Итак, полный экран отвоевывать мы научились. Теперь необходимо научиться менять программно, т. e. без перезагрузки, разрешение экрана: приложению может потребоваться другое, нежели установленное пользователем разрешение, к которому он привык в своей повседневной работе.
Проект FullScr из подкаталога Ех30 является упрощенным вариантом такой программы. После запуска приложения пользователю из списка предлагается выбрать желаемое разрешение экрана, которое устанавливается на время работы основного модуля - минимальной программы OpenGL. После окончания работы модуля возвращается первоначальное разрешение экрана.
Пример построен на использовании функции API ChangeDisplaySettings, первый аргумент которой - структура, описывающая требуемый режим. Второй аргумент - битовая комбинация констант, одна из которых задает тестирование режима без его установки.
Массив LowResModes заполняем перечислением всех возможных режимов, тестируем последовательно каждый из них и отмечаем те, тестирование для которых оказалось успешным. После пользовательского выбора действительно устанавливаем выбранный режим, а по завершению работы приложения возвращаем запомненный первоначальный режим.
Протестировав программу в различных режимах, вы можете выяснить, что не во всех из них возможно использование OpenGL, в некоторых режимах контекст воспроизведения не может быть получен.
В целом такой способ создания полноэкранного приложения я нахожу вполне удовлетворительным. При тестировании на компьютере без акселератора приложение в проблемных режимах выдавало сообщение о невозможности получения контекста, а при подключении акселераторов сообщение не появлялось, но в некоторых режимах окно и не окрашивалось. Акселераторы первых моделей могут искаженно передавать картинку в некоторых режимах.
Приведу еще один пример на полноэкранный режим работы (проект из подкаталога Ex31). Здесь для переключения в различные режимы используется DirectDraw, все необходимые модули для его использования находятся также в этом подкаталоге.
Параметры режима - ширина, высота и разрядность пиксела - задаются в виде трех чисел в командной строке.
С помощью этого приложения вы можете выяснить, что на самом деле не все режимы доступны для использования в принципе, чем и объясняется то, что в предыдущем примере не во всех режимах можно было получить контекст воспроизведения. В проблематичных режимах на экране хорошо заметно искажение изображения.
Я думаю, что описанный способ создания полноэкранного приложения вполне можно считать универсальным и достаточно надежным.
Есть еще один, очень простой, способ создания полноэкранного приложения: рисование прямо на поверхности рабочего стола. Во второй главе я приведу соответствующий пример, хотя вы уже сейчас знаете, что для этого необходимо сделать. Но этот способ может не работать с вашей картой.



Программирование на Delphi без VCL


После того как мы прикоснулись к основополагающим терминам и понятиям операционной системы Windows "сообщение" и "ссылка на окно", мы сможем опуститься ниже уровня объектно-ориентированного программирования, VCL и RAD-технологий. Требуется это по четырем причинам.
Во-первых, приложения, активно использующие графику, чаще всего не нуждаются и не используют богатство библиотеки классов Delphi. Таким приложениям, как правило, достаточно окна в качестве холста, таймера и обработчиков мыши и клавиатуры.
Во-вторых, при программировании, основанном только на использовании функций API, получаются миниатюрные приложения. Откомпилированный модуль не отягощается кодом описания компонентов и кодом, связанным с концепциями ООП.
В-третьих, для понимания приемов, используемых для увеличения скорости воспроизведения, нужно иметь представление о подлинном устройстве Windows-программы. Например, чтобы команды перерисовки окна выполнялись быстрее, мы будем избегать использования методов Refresh и paint формы.
В-четвертых, это необходимо для понимания действий, производимых для подключения OpenGL. Эта библиотека создавалась в эпоху становления ООП, и ее пока не коснулись последующие нововведения в технологии программирования.


Работа с мышью и клавиатурой


Как обработать двойной щелчок левой кнопки мыши, опираясь на сообщения, мы рассмотрели выше в разделе "Перехват сообщений" данной главы. Проект из подкаталога Exl8 является примером на обработку остальных сообщений, связанных с мышью. При нажатой левой кнопки мыши за указателем остается след. Оконная функция дополнилась обработчиками сообщений wm_LButtonDown, wm_LButtonUp И wm_MouseMove Для определения координат курсора пользуемся тем, что поле iParam подобных сообщений содержит эти самые координаты.

Down: = not Down;
wm_Create: Down: = False;
wm_LButtonDown, wm_LButtonUp
wm_MouseMove: begin
If Down then begin xpos: = LoWord ( LParam);
ypos: = HiWord ( LParam);
InvalidateRect(Window, nil, False);
end;
end;
wm_Paint: begin
If Down then begin
dc: = BeginPaint (Window, MyPaint);
Ellipse (dc, xPos, yPos, xPos + 2, yPos + 2);
EndPaint (Window, MyPaint);
ReleaseDC (Window, dc);
end;
end;

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

wm_Char: // анализ нажатой клавиши
case wParam of
$58, $78: If HiWord {GetKeyState (vk_Shift)) = 0 { Shift }
then MessageBox(Window, 'X', 'Нажата клавиша', MB_OK)
else MessageBox(Window, 'X вместе с Shift', 'Нажата клавиша', MB_OK);
end; // wm char

При нажатии клавиши 'X' выводится сообщение, в котором указано, нажата ли одновременно клавиша <Shift>. Я использовал шестнадцатеричное представление кода клавиши, но, конечно, можно использовать и десятичное. Надеюсь, здесь не требуются особые пояснения, и мы сможем использовать этот код в качестве шаблона в будущих проектах.



Работа с таймером


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

const
AppName = 'WinPaint';
id_Timer = 100; // идентификатор таймера

Идентифицировать таймер необходимо потому, что у приложения их может быть несколько. Для включения таймера (то, что в привычном антураже соответствует Timerl. Enabled: = True) вызывается функция API SetTimer, где задается требуемый интервал таймера:

SetTimer (Window, id_Timer, 200, nil); // установка таймера

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

wm_Timer: InvalidateRect (Window, nil, False);

Если в приложении используется несколько таймеров, необходимо отделять их по значению идентификатора, передаваемому в wParam.
В моем примере каждые 200 миллисекунд окно перерисовывается вызовом функции API invalidateRect Запомните этот прием, потом мы не раз будем его использовать. Изменение цвета кружочка достигается тем, что при каждой перерисовке объект "кисть" принимает новое значение

Brush: = CreateSolidBrush (RGB(random (255), random (255), random(255)));

Как всегда в Windows, созданные объекты должны по окончании работы удаляться, дабы не поглощали ресурсы Для удаления таймера вызываем Функцию KillTimer в обработчике сообщения wm_Destroy:

KillTimer (Window, id_Timer);

Как видим, работать с таймером, используя только функции API, совсем не сложно. Компонент Delphi TTimer основывается на функциях и сообщениях, которые мы только что рассмотрели.



Решение проблем


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

If hrc=0 then ShowMessage('Oтсутствует контекст воспроизведения OpenGL');

Обратите также внимание на следующую важную вещь. В самой первой программе, использующей OpenGL, как и в подавляющем большинстве последующих примеров, процедура установки формата пиксела записана мною в самом коротком варианте:

FillChar {pfd, SizeOf (pfd), 0);
nPixelFormat: =ChoosePixelFormat (hdc, @pfd);
SetPixelFormat (hdc, nPixelFormat, @pfd);

To есть ни одно из полей pfd я не задаю явно, отдавая все на откуп OpenGL. B некоторых же примерах я ограничиваюсь только заданием необходимых значений для полей битовых флагов.
Я не встречал ситуаций, когда бы такой подход не срабатывал, не считая случаев с использованием других, нежели фирмы Microsoft, версий OpenGL, но поручиться за то, что он будет работать для всех графических карт, не могу. Возможно, проблемы возникнут также из-за некорректной работы Драйверов (стандартная отговорка, не правда ли?).
Если примеры с прилагаемой дискеты у вас не работают, выдавая просто черный экран, начните поиск причины с определения значения hrc сразу жe после создания ссылки на контекст воспроизведения. Если это значение Равно нулю, в процедуре установки формата пиксела задайте всем полям значения согласно полученным с помощью приложения проекта TestPFD.Скорее всего, вам рано или поздно потребуется разрешать подобные проблемы, связанные с неверным форматом пиксела или подобными системными ошибками. Сделать это в проектах, где не используется библиотека классов Delphi, оказывается сложным для новичков. Помощью может стать пример из подкаталога Ex23, где я демонстрирую, как выдать информацию о последней системной ошибке. В программе намеренно сделана ошибка путем превращения строки с получением ссылки на контекст устройства в комментарий.
Функция API FormatMessage позволяет преобразовать сообщение об ошибке в формат, пригодный для вывода:

lpMsgBuf: PChar;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER or
FORMAT_MESSAGE_FROM_SYSTEM,
nil, GetLastError(), LANG_NEUTRAL, @lpMsgBuf, 0, nil);
MessageBox(Window, lpMsgBuf, 'GetLastError', MB_OK);

Сообщение выводится в осмысленной форме и на языке, соответствующем локализации операционной системы. В данном случае выводится фраза "Неверный дескриптор".
Если убрать знаки комментария со строки с получением ссылки, а закомментировать строку с вызовом функции setPixelFormat, сообщение об ошибке будет выглядеть как "Неправильный формат точки" (подразумевается "Неверный формат пиксела").
Полный список системных ошибок, связанных с использованием OpenGL, можно посмотреть в файле windows. pas, в разделе "OpenGL Error Code".
Учтите, что в этом примере выводится информация о последней системной ошибке, а она могла произойти задолго до работы приложения, так что следует использовать такую диагностику ошибок только при отладке приложений. Первый аргумент функции API FormatMessage позволяет определять дополнительные детали вывода сообщения.

Замечание
Во второй главе мы познакомимся с еще одним способом диагностирования ошибок, стандартным для OpenGL.



Поумолчанию режим




Замечание
Кадр, содержимое которого мы непосредственно видим на экране, называется передним буфером кадра, вспомогательный раздел памяти, в котором подготавливается изображение, называется задним буфером кадра

Константу PFD_GENERIC_ACCELERATED имеет смысл устанавливать только в случае, если компьютер оснащен графическим акселератором.
Флаги, заканчивающиеся на "DONTCARE", сообщают системе, что соответствующий режим может иметь оба значения, например, при установке флага PFD_DouBLE_BUFFER_DONTCARE запрашиваемый формат пиксела допускает оба режима - как одинарной, так и двойной буферизации.
Со всеми остальными полями и константами я предоставляю вам возможность разобраться самостоятельно. Отмечу, что поле iLayerType, описанное в windows. pas как имеющее тип Byte, может, согласно справке, иметь три значения: PFD_MAIN_PLANE, PFD_OVERLAY__PLANE И PFD_UNDERLAY_PLANE, однако константа PFD_UNDERLAY_PLANE имеет значение -1, так что установить такое значение для величины типа Byte не удастся.
OpenGL позволяет узнать, какой же формат пиксела он собирается использовать. Для этого необходимо использовать функцию DescribePixelFormat, заполняющую величину типа TPixelFormatDescriptor установленным форматом пиксела.
Построим несложное приложение на основе использования этой функции, которое позволит детальнее разобраться с форматом пиксела и подобрать формат для конкретного рабочего места (проект из подкаталога Ex22).
примере битовым флагам задаем все возможные значения одновременно, числовым полям задаем заведомо нереальное значение 64, и смотрим на выбор формата пиксела, сделанный OpenGL. Результат, который вы получите для выбранного OpenGL формата пиксела, я предсказать не могу: он индивидуален для каждой конкретной конфигурации компьютера и текущих настроек. Скорее всего окажется, что режим двойной буферизации не будет установлен. (Напоминаю: многие флаги устанавливаются только в определенных комбинациях с другими.)Наше приложение позволяет менять параметры формата пиксела и устанавливать его заново, а чтобы видеть воспроизведение, небольшая площадка на экране при каждом тестировании окрашивается случайным цветом, используя функции OpenGL. Поэкспериментируйте с этим приложением, например, определите комбинацию флагов для установления режима двойной буферизации. Посмотрите значение числовых полей формата при различной палитре экрана: 16 бит, 24 бита, 32 бита, если у вас есть такой режим, но не в палитре с 256 цветами. О выводе OpenGL при палитре экрана в 256 цветов у нас будет отдельный разговор.
Это приложение, в частности, дает ответ на вопрос, как определить, оснащен ли компьютер графическим акселератором. Сделать это можно после вызова функции DescribePixelFormat следующим образом:

var
i, j: Integer;
i: = pfd. dwFlags and PFD_GENERIC_ACCELERATED; ]: = pfd.
dwFlags and PFD_GENERIC__FORMAT;
If (i = 0) and (з = 0)
then // полноценным ICD-драйвер с функциями ускорения
else If (i = 1) and (j = 1)
then // MCD-драйвер, аппаратно реализуется
// только часть функций ускорения
else // режим программной эмуляции, всю работу выполняет центральный
// процессор

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



Событие, сообщение, ссылка


С понятием "событие" знаком каждый программист, использующий Delphi. Термин "сообщение" напрямую в концепции Delphi не используется.
Очень часто это синонимы одного и того же термина операционной системы, общающейся с приложениями (окнами) посредством посылки сигналов, называемых сообщениями.
Код, написанный в проекте Delphi как обработчик события OnCreate, выполняется при получении приложением сообщения WM_CREATE, сообщению WM_PAINT соответствует событие OnPaint и т. д.
Такие события - аналоги сообщений операционной системы - используют мнемонику, сходную с мнемоникой сообщений, т. e. сообщения начинаются с префикса "WM_" (Windows Message), a аналогичные события начинаются с префикса "On".
Для того чтобы операционная система могла различать окна для осуществления диалога с ними, все окна при своем создании регистрируются в операционной системе и получают уникальный идентификатор, называемый "ссылка на окно". Тип этой величины в Delphi - HWND (Handle WiNDow). Синонимом термина "ссылка" является дескриптор.
Ссылка на окно может использоваться не только операционной системой, но и приложениями для идентификации окна, с которым необходимо производить манипуляции.
Попробуем проиллюстрировать смысл ссылки на окно на несложном примере.
Откомпилируйте минимальное приложение Delphi и начните новый проект. Форму назовите Form2, разместите на ней кнопку. Обработчик события нажатия кнопки OnClick приведите к следующему виду (готовый проект располагается на дискете в подкаталоге Ex01 каталога Chapter1):

procedure TForm2.ButtonlClick(Sender: TObject);
var
H : HWND; // ссылка на окно
begin
H := FindWindow ('TForm1', 'Form1'); // ищем окно
If H <> 0 then ShowMessage ('Есть Form1!') // окно найдено
else ShowMessage ('Нет Form1!') // окно не найдено
end;

Теперь при нажатии кнопки выдается сообщение, открыто ли окно класса, зарегистрированного в операционной системе как 'TForml1', имеющее заголовок 'Form1'. Если одновременно запустить обе наши программы, то при нажатии кнопки будет выдано одно сообщение, а если окно с заголовком 'Form1' закрыть, то другое.
Здесь мы используем функцию FindWindow, возвращающую величину типа HWND - ссылку на найденное окно либо ноль, если такое окно не найдено. Аргументы функции - класс окна и его заголовок. Если заголовок искомого окна безразличен, вторым аргументом нужно задать nil.
Итак, ссылка на окно однозначно определяет окно. Свойство Handle формы и есть эта ссылка, а тип THandle в точности соответствует типу HWND, так что в предыдущем примере переменную Н можно описать как переменную типа THandle.
Рассмотрим подробнее некоторые выводы. Класс окна минимального приложения, созданного в Delphi, имеет значение 'TForm1', что полностью соответствует названию класса формы в проекте. Следовательно, то, как мы называем формы в проектах Delphi, имеет значение не только в период проектирования приложения, но и во время его работы. Начните новый проект, назовите форму каким-нибудь очень длинным именем и откомпилируйте проект. Сравните размер откомпилированного модуля с размером самого
первого проекта, и убедитесь, что он увеличился - вырос только за счет длинного имени класса.
Также очень важно уяснить, что, если вы собираетесь распространять какие-либо приложения, необходимо взять за правило называть формы отлично от значения, задаваемого Delphi по умолчанию. Лучше, если эти названия будут связаны по смыслу с работой вашего приложения. Так, например, головную форму в примерах этой книги я буду называть, как правило, frmGL.
Имея ссылку на окно, операционная система общается с ним путем посылки сообщений - сигналов о том, что произошло какое-либо событие, имеющее отношение именно к данному окну. Если окно имеет намерение отреагировать на событие, операционная система совместно с окном осуществляет эту реакцию.
Окно может и, не имея фокус, получать сообщения и реагировать на них. Проиллюстрируем это на примере.
Обработчик события OnMouseMove формы приведите к следующему виду (проект находится в подкаталоге Ех02):

procedure TForm2.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer) ;
begin
Caption := 'x=' + IntToStr (X) + ', y=' + IntToStr (Y) // X, Y -
// координаты курсора
end;

При движении курсора мыши в заголовке формы выводятся его координаты.
Запустите два экземпляра программы и обратите внимание, что окно, не имеющее фокус, т. е. неактивное, тоже реагирует на перемещение указателя по своей поверхности и выводит в заголовке текущие координаты курсора в своей системе координат.
Имея ссылку на окно, приложение может производить с ним любые (почти) действия путем посылки ему сообщений.
Изменим код обработки щелчка кнопки (проект из подкаталога Ех03):

procedure TForm2.ButtonlClick(Sender: TObject);
var
H : HWND;
begin
H := FindWindow ('TForml', ' Forml');
If H <> 0 then SendMessage (H, WM_CLOSE, 0, 0)//закрыть найденное окно
end;

Если имеется окно класса 'TForm1' с заголовком 'Form1', наше приложение посылает ему сообщение WM_CLOSE - пытается закрыть окно. Для посылки сообщения используем функцию операционной системы (функцию API) SendMessage. Функция postMessage имеет сходное назначение, но отличается тем, что не дожидается, пока посланное сообщение будет отработано. У этих функций четыре аргумента - ссылка на окно, которому посылаем сообщение, константа, соответствующая посылаемому сообщению, и два параметра сообщения, смысл которых определяется в каждом конкретном сообщении по-своему. Параметры сообщения называются wParam и lParam. При обработке сообщения WM_CLOSE эти значения никак не используются, поэтому здесь их можно задавать произвольно.
Заметим, что одновременно могут быть зарегистрированы несколько окон класса 'TForm1', и необходимо закрыть их все. Пока наше приложение закрывает окна поодиночке при каждом нажатии на кнопку. Автоматизировать процесс можно разными способами, простейший из них используется в проекте подкаталога Ex04 и заключается в том, что вызов Findwindow заключен в цикл, работающий до тех пор, пока значение переменной H не станет равным нулю:

procedure TForm2. ButtonlClick(Sender: TObject);
var
H: HWND;
begin
Repeat
H: = FindWindow ('TForm1', 'Form1');
If H <> 0 then SendMessage (H, WM_CLOSE, 0, 0)
Until H = 0;
end;

Ну а как работать с приложениями, класс окна которых не известен, поскольку у нас нет (и не может быть) их исходного кода?
Для решения подобных проблем служит утилита Ws32, поставляемая с Delphi.
Например, с помощью этой утилиты я выяснил, что класс окна главного окна среды Delphi имеет значение 'TAppBuilder'. Узнав это, я смог написать проект, где делается попытка закрыть именно это окно (находится в подкаталоге Ex05).
Каждый раз я говорю именно о попытке закрыть окно, потому что приложение, получающее сообщение WM_CLOSE, может и не закрыться сразу же. Например, среда Delphi или текстовый процессор перед закрытием переспрашивают пользователя о необходимости сохранения данных. Но ведет себя приложение точно так же, как если бы команда поступала от пользователя.
В качестве следующего упражнения рассмотрите проект, располагающийся в подкаталоге Ex06, где по нажатию кнопки минимизируется окно, соответствующее минимальному проекту Delphi.
Для того чтобы минимизировать окно, ему посылается сообщение WM_SYSCOMMAND, соответствующее действию пользователя "выбор системного меню окна". Третий параметр функции SendMessage для минимизации окна необходимо установить в значение SC_MINIMIZE.
Работа с функциями API, сообщения Windows - темы весьма объемные. Пока мы рассмотрели только самые простейшие действия - закрыть и минимизировать окно.
В заключение раздела, необходимо сказать, что ссылки, в зависимости от версии Delphi, соответствуют типам Integer или LongWord и описываются в модуле windows. pas.



Стили окна и вывод OpenGL


В проекте из подкаталога Ex25 я немного модифицировал пример минимальной программы OpenGL таким образом, что получилось простое MDI-приложение, в котором каждое дочернее окно окрашивается случайным образом с использованием команд OpenGL (Рисунок 1. 2).


Тип Tcolor и цвет в OpenGL


Разберем еще одну версию нашей первой программы, использующей OpenGL - пример из подкаталога Ex32. Здесь на форму помещена кнопка, при нажатии которой появляется стандартный диалог Windows выбора цвета. После выбора окно окрашивается в выбранный цвет, для чего используются команды OpenGL. Поскольку такой прием мы активно будем применять в будущем, разберем подробно, как это делается.
Цвет, возвращаемый диалогом, хранится в свойстве color компонента класса TColorDialog. Согласно справке, значение $00FFFFFF этого свойства соответствует белому цвету, $00FF0000 - синему, $0000FF00 - зеленому, $000000FF - красному. To есть для выделения красной составляющей цвета необходимо вырезать первый слева байт, второй байт даст долю зеленого, третий - синего. Максимальное значение байта - 255, минимальное - ноль. Цвета же OpenGL располагаются в интервале от нуля до единицы.
В нашем примере я ввел пользовательскую процедуру, определяющую тройку составляющих цветов для OpenGL по заданному аргументу типа TColor:

procedure TfrmGL. ColorToGL (с: TColor; var R, G, В: GLFloat);
begin
R: = {c mod $100) / 255;
G: = ((c div $100) mod $100) / 255;
В: = (с div $10000) / 255; end;

Из аргумента вырезаются нужные байты и масштабируются в интервал [0; 1]

Замечание
Те же действия можно сделать и другим, более "продвинутым" способом:

R =(cand$FF)/255;
G. = ((c and $FFOO) shr 8) / 255,
В: = ((c and $FFOOOO) shr 16) / 255

Эта процедура используется в обработчике нажатия кнопки:

If ColorDialogl. Execute then begin
ColorToGL (ColorDialogl. Color, R, G, В);
Refresh;
end;

В примере для простоты окно перекрашивается обычным для Delphi способом - через вызов метода Refresh формы.



Типы OpenGL


Библиотека OpenGL является переносимой по отношению к платформам, операционным системам и средам программирования.
Для обеспечения этой независимости в ней, в частности, определены собственные типы. Их префикс - "GL", например, GLint.
В каждой среде программирования в заголовочных файлах эти типы переопределяются согласно собственным типам среды. Разберем, как это делается в Delphi.
Заголовочный файл Delphi opengl. pas начинается с определения знакомого нам типа HGLRC:

type
HGLRC = THandle;

Далее следует описание всех остальных типов OpenGL, например, наиболее "ходовой" тип GLfloat соответствует типу Single:

GLfloat = Single;

Поначалу многие испытывают затруднение, когда приходится использовать "неродные" для Delphi типы. По мере накопления опыта эта неловкость быстро проходит, и я рекомендую использовать с самого начала знакомства именно типы библиотеки OpenGL, даже если вы наизусть знаете их родные Для Delphi аналоги. Наверняка вам рано или поздно придется разбираться в чужих программах или переносить свои программы в другую среду программирования или даже в другую операционную систему В атмосфере бес прерывной смены технологий, в которой мы находимся все последние годы нельзя быть уверенным в том, что какая-либо операционная система (и/или среда программирования) на долгие годы станет надежным средством воплощения наших идей Вряд ли кто-то может поручиться, что его любимая операционная система проживет еще хотя бы десяток лет и не выйдет внезапно из моды, сменившись другой, о которой вы и не слышали пару месяцев назад.
Однако вернемся к типам OpenGL He все из них удается точно перевести Например, GLclampf - вещественное число в пределах от нуля до единицы - в Delphi определен просто как single Поэтому обычно в программах устанавливают "ручную" проверку на вхождение величины такого типа в требуемый диапазон
Будьте внимательны с целыми числами помимо типа GLint имеется тип GLUin - целое без знака, соответствующее типу Cardinal
В ряду типов OpenGL особо надо сказать о типе

GLboolean = BYTEBOOL,

Соответственно, определены две константы

GL_FALSE = 0, GL_TRDE = 1,

Константы эти имеют непосредственное отношение к типу GLboolean, однако их значения, как вы понимаете, не соответствуют типу BYTEBOOL Из-за ошибки в описании типа (или определении констант) не удастся использовать стандартный для OpenGL код, поэтому вместо констант GL_FALSE и GL_TRUE будем использовать False и True, соответственно
Конечно, можно самому скорректировать описание типа, например, так

GLboolean = 0 1,

После этой корректировки не придется отходить от стандарта кода графической библиотеки, но модифицировать стандартные модули Delphi нежелательно, иначе ваши проекты будут успешно компилироваться только на вашем рабочем месте
Помимо основных типов, стандартных для OpenGL и вводимых в любой среде программирования, в заголовочном файле введены также типы специфические только для Delphi, например, для наиболее часто употребляемых в OpenGL массивов введены специальные типы

TGLArrayf4 = array [0 ..3] of GLFloat,
TGLArrayf3 = array [0..2] of GLFloat,
TGLArrayf4 = array [0..3] of GLint,

Это сделано, по-видимому, для удобства кодирования и повышения читабельности кода, поскольку нигде больше в этом модуле указанные типы не встречаются
Разработчикам также пришлось ввести специальные типы для указателей, например

PGLfloat = ^GLFloat,

Такого типа нет в стандартном наборе типов OpenGL библиотека изначально создавалась на языке С, синтаксис которого хорошо приспособлен к использованию указателей, поэтому во введении особого типа для них просто не было необходимости
Вообще, должен сказать, что OpenGL наиболее приспособлен для программирования на С, поэтому некоторые моменты будут вызывать некоторые неудобства при использовании Delphi (уже упоминавшаяся система справок лишь одно звено в этой цепи) Тем не менее, это не помешает нам успешно освоить программировании на Delphi c использованием этой библиотеки
Система Delphi имеет, конечно, слабые места Чересчур большой размер откомпилированных модулей - не самое значительное из них Для графических приложений крайне важна скорость работы, и здесь пальма первенства тоже не за Delphi Если приложение интенсивно использует массивы и указатели, операции с памятью и проводит много вычислительных операций, то падение скорости при использовании Delphi вместо C/C++ оказывается значительным По некоторым тестам, лучшие компиляторы C++ создают код, работающий в два раза быстрее
Однако это не должно отпугнуть вас от дальнейшего изучения использования OpenGL в проектах Delphi, поскольку здесь как раз тот случай, когда скорость работы самого приложения не так уж и важна Основную долю работы берет на себя сервер OpenGL, a приложению достается роль транслятора команд серверу, поэтому нет особых потерь производительности, если вместо языка C++ мы используем Pascal и Delphi
Конечно, для сокращения потерь производительности желательно использовать приемы объектно-ориентированного программирования, хотя я бы не сказал, что эти приемы во всех случаях приведут к заметному на глаз ускорению работы приложения
Delphi имеет свои неоспоримые достоинства - прежде всего это несравнимая ни с каким другим средством скорость разработки и компиляции Именно поэтому, а также из-за "скрытого обаяния" Delphi (вы понимаете, о чем я говорю) мы и выбрали эту замечательную систему в качестве основы Для изучения OpenGL



Вывод на компоненты Delphi средствами OpenGL


Теоретически с помощью функций OpenGL можно осуществлять вывод не только на поверхность формы, но и на поверхность любого компонента, если у него имеется свойство Canvas. Handle, для чего при получении ссылки на контекст воспроизведения необходимо указывать ссылку на контекст устройства, ассоциированную с нужным компонентом, например, image1. Canvas. Handle. Однако чаще всего это приводит к неустойчивой работе, вывод то есть, то нет, хотя контекст воспроизведения присутствует и не теряется.
OpenGL прекрасно уживается с визуальными компонентами, как видно из примера TestPFD, так что чаще всего нет необходимости осуществлять вывод на поле не формы, а компонента Delphi.
Если для ваших задач необходимо ограничить размер области вывода, то для этого есть стандартные методы, которые мы обсудим во второй главе.
Подкаталог Ex24 содержит проект, в котором вывод осуществляется на поверхность панели - компонента, вообще не имеющего свойства canvas. Для этого мы пользуемся тем, что панель имеет отдельной окно:

dc: = GetDC (Panel1. Handle);
SetDCPixelFormat(dc);
hrc: = wglCreateContext(dc);

Аналогичным образом можно организовать вывод на поверхность любого компонента, имеющего свойство Handle (т. e. имеющего самостоятельное окно), например, на поверхность обычной кнопки. Обязательно попробуйте сделать это.
Для вывода на компонент класса TImage можете записать.

dc: = Image1. Canvas. Handle; и удалить строки BeginPaint и EndPaint, поскольку класс TImage не имеет

свойства Handle, т. e. не создает отдельного окна.
Однако вывод на компоненты, подобные компонентам класса Timage, т. e. не имеющие свойства Handle, отличается полной неустойчивостью, так что я не гарантирую вам надежного положительного результата.
Почему это происходит, выясним в следующем разделе.



Вывод с использованием функций GDI


В первом разделе мы рассмотрели, как, получив ссылку на чужое окно, можно производить с ним некоторые действия, например, закрыть его.
Точно так же, если необходимо нарисовать что-либо на поверхности чужого окна, первым делом нужно получить ссылку на него.
Для начала попробуем рисовать на поверхности родного окна.
Разместите еще одну кнопку, обработку щелчка которой приведите к следующему виду (проект из подкаталога Ex09):

procedure TForma2. Button2Click(Sender: TObject);
var
dc: HDC; // ссылка на контекст устройства
begin
dc: = GetDC (Handle); // задаем значение ссылки
Rectangle (dc, 10, 10, 110, 110); // рисуем прямоугольник
ReleaseDC (Handle, dc);
// освобождение ссылки DeleteDC (dc);
// удаление ссылки, освобождение памяти
end;

Запустите приложение. После щелчка на добавленной кнопке на поверхности окна рисуется квадрат.
Для рисования в этом примере используем низкоуровневые функции вывода Windows, так называемые функции GDI (Graphic Device Interface, интерфейс графического устройства). Эти функции требуют в качестве одного из своих аргументов ссылку на контекст устройства.
Тип такой величины - hdc (Handle Device Context, ссылка на контекст устройства), значение ее можно получить вызовом функции API GetDC с аргументом-ссылкой на устройство вывода. В нашем примере в качестве аргумента указана ссылка на окно.
После получения ссылки на контекст устройства обращаемся собственно к функции, строящей прямоугольник. Первым аргументом этой функции является ссылка на контекст устройства.
После использования ссылки ее необходимо освободить, а в конце работы приложения - удалить для освобождения памяти.
Поставленная "клякса" будет оставаться на окне формы при изменении размеров окна и исчезнет только при его перерисовке, для чего можно, например, минимизировать окно формы, а затем вновь его развернуть. Исчезновение квадрата после такой операции объясняется тем, что за перерисовку окна отвечает его собственный обработчик.
Теперь попробуем порисовать на поверхности чужого окна, для чего изменим только что написанный код (готовый проект находится в подкаталоге Ех10):

procedure TForm2. Button2Click(Sender: TObject);
var
dc: HDC; Window: HWND; begin
Window: = FindWindow ('TForml', 'Forml'); If Window <> 0 then begin // окно найдено
dc: = GetDC (Window); // ссылка на найденное окно
Rectangle (dc, 10, 10, 110, 110); // квадрат на чужом окне
ReleaseDC (Window, dc); // освобождение ссылки
DeleteDC (dc); // удаление ссылки
end;
end;

Теперь при щелчке на кнопке, если в системе зарегистрировано хотя бы одно окно класса 'TForm1' с заголовком 'Form1', вывод (рисование квадрата) будет осуществляться на него.
Запустите параллельно откомпилированные модули минимального и только что созданного приложений. При щелчке на кнопке квадрат рисуется на поверхности чужого окна.
Замечу, что если закрыть Projectl. exe и загрузить в Delphi соответствующий ему проект, то при щелчке на кнопке прямоугольник будет рисоваться на поверхности окна формы, что будет выглядеть необычно.
Этот эксперимент показывает, что окна, создаваемые Delphi во время проектирования, такие же равноправные окна, как и любые другие, т. e. они регистрируются в операционной системе, идентифицируются, и любое приложение может иметь к ним доступ. Если попытаться минимизировать окно класса 'TForm1', окно формы будет отрабатывать эту команду точно так же, как полученную от пользователя.
Следует обратить внимание на то, что мы не можем рисовать на поверхности вообще любого окна. Например, не получится, поменяв имя класса окна на 'TAppBuilder', поставить "кляксу" на главном окне среды Delphi.
Окно со значением ссылки, равным нулю, соответствует окну рабочего стола. В примере Exll я воспользовался этим, чтобы нарисовать квадратик на рабочем столе:

procedure TForm2. Button2Click(Sender: TObject);
var
dc: HDC;
begin
dc: = GetDC (0); // получаю ссылку на рабочий стол
Rectangle (dc, 10, 10, 110, 110);
ReleaseDC (Handle, dc);
DeleteDC (DC);
end;

Во всех примерах этого раздела я для вывода использовал функции GDI потому, что если для вывода на родном окне Delphi и предоставляет удобное средство - методы свойства формы canvas, то для вывода на чужом окне мы этими методами воспользоваться не сможем в принципе. Разберем основные детали вывода с помощью функций GDI. B проекте из подкаталога Exl2 оконная функция минимальной программы дополнилась обработкой сообщения WM_PAINT. Вывод заключен между строками с вызовом функций BeginPaint и EndPaint, первая из которых возвращает ссылку на контекст устройства, т. e. величину типа HDC, требуемую для функций вывода GDI. Еще раз повторю, что после использования ссылки ее необходимо освободить и удалить по окончании работы - это необходимо для корректной работы приложения. Смысл ссылки на контекст устройства мы подробно обсудим немного позже.
Остальные строки кода подробно прокомментированы, так что, надеюсь, особых вопросов не вызовут, разве только начинающих может в очередной раз поразить обширность кода, возникающая при отказе от удобств, предоставляемых библиотекой классов Delphi (которая выполняет за программиста всю черновую работу и превращает разработку программы в сплошное удовольствие).