Автор: Olej
Дистрибутив операционной системы QNX 6.X комплектуется, помимо прочего, собственным инструментом для разработки приложений на C/C++: Photon Application Builder - PhAB (о составе и особенностях системы можно прочитать в [1]-[3]). PhAB, по сути дела, не является IDE в привычном смысле: он не содержит собственного редактора исходных текстов, символьного отладчика, и, самое главное - не генерирует некоторый "проект", в том смысле, как это делают например CodeForge, MS Visual C++ или Borland С++Builder.
Это, скорее, построитель GUI-образов приложения: PhAB избавляет разработчика от рутинной работы по отслеживанию размеров, взаимных положений, цветов и других характеристик GUI-компонентов приложения, и позволяет привязать обрабатывающий код (или другие widget'ы) к GUI-событиям (нажатия кнопок, перемещения окон, ввод с клавиатуры и т.д.) Весь же программный код реакции на события разработчик прописывает традиционными методами на C/C++.
Примечание: Все последующее изложение ориентировано, в первую очередь, на создание приложений именно на C++, потому, что:
* автор сам работает на C++, избегая, по возможности, традиционного C,
* фирма QSSL, разработчик QNX, оперирует преимущественно C, и использованию C уделено достаточно внимания в документации QNX [4], в то время, как особенности использования C++ в PhAB освещены гораздо слабее.
Примечание: Ниже везде описывается техника программирования с использованием построителя приложений PhAB. Тем не менее, существует совершенно другая техника написания приложений для Photon - чисто "ручная" программная манипуляция widget'ами: создание, размещение, уничтожение и т.д. Она настолько "другая", что является сама по себе предметом для отдельного описания, и здесь не рассматривается.
Результатом работы генератора PhAB является достаточно понятная структура файлов заголовков, определений и т.д., и, главное Makefile, которые далее можно обрабатывать традиционно, утилитой make. Показательной является, например, возможность переноса сгенерированного в PhAB приложения в CodeForge, генерации в нем проекта на основании Makefile, и дальнейшего его развития уже в CodeForge.
Такое сочетание визуального проектирования GUI-компонентов с традиционным написанием собственно целевого программного кода дает очень высокую итоговую результативность разработки. Другими несомненными достоинствами PhAB являются простота адаптации и интуитивная понятность происходящего: программист с изрядным опытом на C++, но никогда ранее не сталкивавшийся с QNX и его системой разработки, вполне готов создавать приложения в PhAB после 3-4 часов знакомства с системой.
Начинаем создание проекта
Первое, что нужно сделать - запустить Photon Application Builder. Обычно это не вызывает проблем: Launch > Development > Builder. Однако, и с этим многие сталкивались на практике, после очередной неудачной установки программных пакетов в QNX, позиция Builder (как, впрочем, и любая другая) может исчезнуть из меню запуска приложений (Launch)! Это связано с некоторыми досадными дефектами системы установки программных пакетов в QNX, и, надо надеяться, будет исправлено в последующих версиях системы. Но нам-то нужно делать приложения сегодня! Эти трудности не должны вас смущать: запускайте PhAB командной строкой /usr/photon/appbuilder/ab, например, из окна терминала pterm. В любом случае, должно появиться окно приложения PhAB (показано на рис. 1) - все дальнейшие манипуляции мы будем производить внутри этого приложения.
Для начала мы должны создать для своего приложения главный widget-контейнер (widget'ом в терминологии Photon, да и большинства графических экранных систем в UNIX принято обобщенно называть любой отдельный графический компонент: кнопку, окно, диалог и т.д.) В терминологии PhAB требуемый нам widget-контейнер называется модулем (с вторичными, независимыми модулями мы еще будем сталкиваться далее по тексту).
Рисунок 1.
Для создания главного модуля приложения воспользуемся меню File > New - нам будет представлен на выбор список из 13 типовых видов окна главного модуля приложения. Выберем тип, например Plain, позже мы можем его поменять и произвольно расширить.
Примечание: PhAB обеспечивает работу с модулями следующих типов: window, dialog, menu, icon, picture. В качестве главного модуля приложения используются первые два.
Рисунок 2.
После выбора типа модуля приложения, ему по умолчанию будет присвоено имя base, и на экране появится диалог с его свойствами (рис. 2). Имя любого widget'а в проекте можно посмотреть или сменить на закладке Resources (видна на рис. 1), отметив требуемый widget в окне проекта.
При создании новых widget'ов в проекте (следующих после начального окна приложения), им будет присваиваться стандартное имя, совпадающее с наименованием класса, к которому принадлежит создаваемый widget. Эти начальные имена могут быть сохранены за widget'ом только в том случае, если для него не определяется какая-либо специфическая реакция на события GUI (пассивный widget). В противном случае, вы обязаны присвоить widget'у произвольное уникальное имя на закладке Resources (мы к этому будем еще неоднократно возвращаться).
Рисунок 3.
После определения главного модуля приложения стоит сразу же определить некоторые его параметры, воспользовавшись меню Application-Startup Info Modules (рис. 2) (а что за меню? На рисунке видно окно с названием Application Startup Information. Оно появляется после выбора упомянутого выше элемента меню?).Важной возможностью этой формы является задание имени функции для инициализации всего приложения в целом (Initialization Function) - она вызывается первой, ещё до создания окна приложения, и получает те же параметры "argv" и "argc", что и функция main в традиционных консольных приложениях.
Примечание (в качестве предостережения): к моменту вызова функции инициализации приложения еще ни одно окно приложения (в том числе и главное - base) еще не инициализировано, поэтому любая попытка манипуляции с ресурсами widget'ов в инициализирующей функции приведет к немедленному аварийному завершению приложения.
Еще одна возможность - это определение функции для инициализации главного окна (Setup Function), которая может вызываться непосредственно до или сразу после отображения окна на экране (подобную возможность вызова до и после мы скоро увидим еще раз при рассмотрении callback). Вы можете вызывать функцию для инициализации окна и дважды: до и после, но это уже экзотика. Синтаксис написания имен соответствующих функций в точности совпадает с синтаксисом записи callback-функций, и будет рассмотрен ниже. Один из самых важных, но малоприметных флажков формы - это Scan Source Files for Prototypes (он определяет, будет ли использоваться файл proto.h в вашем проекте). Для C++ этот флажок должен быть снят (по умолчанию он установлен), для C он обычно установлен. Это место для многих явилось долгим по времени препятствием для использования C++ в PhAB-приложениях. В левой части окна расположены флажки Enable Window State Arguments, Enable Window Dimension Arguments, Enable Window Position Arguments - они разрешают/запрещают соответственно управление состоянием (развернуто/свернуто и т.д.), размером и положением главного окна приложения при его старте указанием соответствующих ключей в командной строке запуска приложения (см. [4]).
Но все выполняемые до сих пор операции ни на шаг не приближают нас к созданию нового приложения! Фактическое создание проекта в PhAB происходит при первоначальном выборе из меню: File > Save As... для нового проекта. При этом PhAB предлагает вам определить имя нового проекта (и, тем самым, его место в файловой системе) как это показано на рис. 3 - новый проект назван ххх, и он будет помещен во вновь созданный каталог $HOME/xxx (в показанном на рисунке 4 случае - пользователь root - "домашний" каталог $HOME: /root , везде в дальнейшем изложении я буду считать этот каталог текущим).
Рисунок 4.
Далее мы должны произвести начальную генерацию проекта, воспользовавшись для этого меню Application > Build+Run (для первой фазы этого процесса - генерации - можно воспользоваться Application > Generate). Окно генерации и сборки проекта показано на рис. 4, и мы еще к нему будем неоднократно обращаться. Пока только обратим возможности на некоторые опциональные (вы их можете всегда сменить позже) параметры: Version (позволяет собрать Debug- или Release-версию), Action (использовать Make shared или Make static сборку проекта - собирать приложение с динамическими вызовами библиотек, или помещать библиотечные функции непосредственно в исполнимый файл), а также Clean - очистить каталог проекта от временных файлов периода сборки.
Для вновь созданного проекта предстоит выполнить генерацию. Необходимость генерации будет неоднократно возникать позже при любых изменениях во внешнем виде GUI: появлении новых widget'ов, изменении их размеров, положений (но генерация не обязательна при коррекции программного кода, не затрагивающей widget, или взаимодействия widget с кодом). На рис. 4 видно, что в случаях, требующих перегенерации проекта, операция Make становится недоступной, потому, что связность файлов проекта "разрушена" нашими корректировками, и должна быть предварительно восстановлена выполнением Generate.
При первичной генерации проекта в открывшемся окне выбора платформы вам предлагается сделать выбор (см. рис. 5) - напомню, что QNX поддерживает до десятка процессорных платформ [3]. В моем случае система инсталлирована с поддержкой только платформы x86, как впрочем, скорее всего, и у вас. В окне выбора согласитесь с предложенным "gcc". Не прельщайтесь выбором "default" - вам придётся повторить весь процесс создания проекта с самого начала.
Рисунок 5.
Теперь, выбрав процессорную платформу, можно, наконец, выполнить последовательно Generate и Make - после генерации файлов проекта и их компиляции вы должны получить работающее бинарное приложение с именем xxx (совпадающим с именем проекта) в каталоге ./src/ntox86 (последняя часть пути определяется выбранной аппаратной платформой). Проверьте новое приложение, выбрав Run в меню построения проекта (рис. 4). Заметьте, что мы уже имеем первое рабочее приложение для Photon, еще не написав ещё ни единой строчки кода!
Собственно, вся дальнейшая работа над приложением - это придание ему требуемой функциональности, для чего мы и должны написать соответствующие участки программного кода, и связать его с событиями GUI (нажатия кнопок, ввод текстовых полей, выбор из селективного списка и т.д.) Логика манипуляции с widget при этом (отбросив все детали) предельно проста:
* Каждый widget (будь то сложное окно-контейнер, или простейшая кнопка) может реагировать на события widget: нажатия кнопок, перемещения окон, изменения размеров... Событиями являются как действия пользователя, так и изменения состояния widget'а: запуск приложения, момент прорисовки окна на экране, истечение времени тайм-аута и т.д. (хотя для таких "внутренних" событий в их длинных цепочках все равно "спусковым механизмом" где-то в начале цепочки является действие пользователя).
* С помощью визуального построителя PhAB мы можем определить реакцию в программе, которую вызовет данное событие widget. Это достигается регистрацией callback-функций для widget'ов. Callback для события определяется на закладке Callback построителя (рис.1) при выборе конфигурируемого widget.
* Существуют callback нескольких классов (наверное, лучше сказать не "классов", а "типов", чтобы не путать с классами из ООП), но основные, интересующие нас, это "widget" - открытие другого окна по событию, и "code" - функция в программном коде, которая вызывается по наступлению события (другие классы, могут быть построены как комбинация этих двух).
* callback класса "widget" может инициировать порождение какого либо другого окна приложения (определяемого именем этого порождаемого widget), при наступлении события в порождающем окне. Порождённый widget может, в свою очередь, в процессе своего создания, порождать следующий widget и т.д. создавая целую цепочку порождаемых widget. (не понял, окно и widget - это одно и то же?) В частном случае, такой callback может порождать новую копию того же widget (с тем же именем), который реагирует callback-ом на событие, на манер рекурсивных вызовов функций. При этом создаётся целая цепочка однотипных GUI-компонентов. Естественно, и в этом (как и при рекурсии) есть определенная опасность, состоящая в том, что необходимо обеспечить завершение этого процесса, например, по числу созданных копий.
* Другой класс - callback типа "code" (наверное, самый широко используемый) - это вызов функции (определённой в программе) по аступлению событию. При этом функции передаются в качестве параметров характерные особенности события. После выполнения очередной перегенерации PhAB добавит в программный файл "заглушку" такой функции (с заказанным вами при определении callback именем функции). Это важно, потому, что параметры callback-функций (которые несут информацию о событии) имеют достаточно сложную структуру. Заметим здесь, кстати, что при последующем удалении или переопределении callback в проекте, когда необходимость в функции с этим именем отпадает, PhAB не может удалить текст callback-функций из программного кода, и, в отличие от создания, вы должны будете удалить его вручную.
* Для одного и того же события вы можете определить несколько реакций, часть из которых могут быть класса "widget", а другие - класса "code". Тогда все они отработаются поочередно (в том порядке, в котором они определялись и представлены в списке callback'ов, рис. 6) при наступлении события (а иначе не было бы никакой возможности псевдорекурсивного создания widget, как описывалось выше: кто-то должен считать созданные компоненты). Более того, вы можете переопределять последовательность срабатывания этих callback в списке.
* Для многих событий вы можете определить срабатывание callback до или после наступления события ("для многих" значит для "внутренних" событий, определяемых фазами жизни widget, например, для прорисовки на экране; с другой стороны - абсурдно было бы пытаться определить callback, срабатывающий перед нажатием пользователем кнопки...)
* С другой стороны, каждый widget имеет набор так называемых "ресурсов" - текущих свойств widget'а. Конкретный набор ресурсов определяется классом widget: ресурсы кнопки существенно отличаются от ресурсов текстового поля ввода. Ресурс определяет то, как widget выглядит на экране: цвет, размер, текст, отображаемый в поле ввода-вывода и т.д.
* Разработчик из своего кода всегда имеет возможность прочитать или изменить значение любого ресурса каждого (созданного, прорисованного на экране) widget'а. Для этого используется большая группа функций GetResource(s)/SetResource(s). Использование GetResource(s)/ SetResource(s) - столь объемный предмет, что требует отдельной статьи для описания. Я не буду останавливаться на этом предмете вообще, тем более, что он исчерпывающе описан в [4].
* Все widget'ы, которые вы добавляете в приложение Photon, упорядочиваются в древовидную иерархию, которая, в том числе, определяет и иерархию видимости widget (передний - задний план). Изменить положение каждого widget в иерархии можно просто "перетаскивая" его в дереве на закладке Module Tree. Этот процесс настолько визуальный, что ... "лучше один раз увидеть, чем сто раз услышать".
Собственно, освоившись с логикой модели приложения PhAB, строить приложения становится весьма просто (я говорю не о написании вашего целевого программного кода, который может быть сколь угодно изощренным, а только о следующих вещах: добавление нового widget к приложению, связывание реакций этого widget с вашим программным кодом, изменение свойств и вида widget из программного кода). Весь процесс происходит как многократная последовательность повторения (для каждого нового widget) следующих шагов:
* На закладке Widgets PhAB выбираем требуемый тип добавляемого widget-а, там их огромное множество. Выбранный widget мы "перетаскиваем" (мышью) в окно того контейнера, где ему надлежит быть по логике приложения. Вы можете таким же образом (мышью) подкорректировать (сейчас или позже) положение и размер устанавливаемого widget. Но точное местоположение и размер (с точностью до пикселя) лучше устанавливать с помощью полей X, Y, W, H в левой части окна PhAB (видны на рис.1).
* Для выбранного widget на закладке Callback (рис.6) для требуемых событий устанавливаем нужные нам реакции (чаще всего класса "code"). Общий синтаксис записи имени функции реакции на событие (то же относится и к функциям инициализации приложения и его главного окна, см. выше) выглядит так Class::
[email protected], где любой компонент записи кроме Function может быть опущен, и где:
o Function - имя функции обработчика callback. После перегенерации проекта в ваш программный код будет добавлена заготовка функции с таким именем и требуемым списком параметров.
o File - имя файла исходного кода программы, в который будет помещен текст обработчика. Если исходный файл с таким именем не существует в проекте, то он будет создан. Если вы не указываете имя файла, то PhAB создаст файл с именем, совпадающим с именем функции обработчика, и поместит его туда (но эта идея "на каждое событие - отдельный файл программы обработчика" не кажется мне самой удачной).
o Type - расширение имени файла ("c" или "cpp", "cc"). Если тип не указан - предполагается C. Это поле определяет, синтаксис какого языка (C или C++) задаст PhAB компилятору gcc. Во избежание недоразумений, особенно при работе с C++, лучше это поле всегда указывать явно.
o Class - используется только с С++ и определяет тот класс, функция-член (метод?) Function которого будет использована в качестве обработчика. Как вы понимаете, в качестве таких функций могут быть использованы только static-члены класса, которые должны быть определены вне зависимости от создания конкретных объектов этого класса (в Java для отражения этого отличия, использованы очень удачные термины: "члены объекта класса" и "члены класса").
Рисунок 6
После выполнения очередного Generate из окна построения приложения (рис. 4) в ваш программный код будет добавлена "заглушка" функции Function с требуемым набором параметров (коротко на параметрах, передаваемых обработчику, я остановлюсь во второй части статьи).
* Дописываем в Function требуемый по логике приложения программный код. Из этого кода вы можете "добираться" к указателю вызвавшего реакцию widget (а также и любого другого) в приложении (как это делать я опишу чуть ниже), и изменять его ресурсы, используя GetResource(s)/SetResource(s) (а вот как это делать - см. [4]).
* Делаем Generate - Make - Run из окна построения приложения (рис.4) и проверяем, достигли ли мы желаемого эффекта.
Примечание: созданное приложение может запускаться не только по Run из окна построения приложения, но и обычной командой запуска по имени приложения из окна pterm (консольного псевдотерминала). Есть одно принципиально важное отличие, делающее необходимым это примечание: GUI-приложение, построенное в PhAB может использовать вывод (ввод) в (из) стандартные потоки вывода (ввода): SYSOUT, SYSERR (SYSIN). Это настолько мощная возможность (сравните с Windows GUI), что ее нужно постоянно иметь в виду: для вывода отладочной информации, для ведения log-файлов перенаправлением потока вывода и др. В свою очередь, SYSIN может использоваться при связывании приложения, например, с последовательными терминальными линиями /dev/ser для ввода со специального оборудования: сканеров штрих-кодов, весового оборудования и т.д. Все эти возможности, в большинстве случаев, можно использовать при запусках приложения только из окна pterm.
Все! И так - последовательно для всех widget, добавляемых в проект и по мере их появления, до тех пор, пока вы не получите завершенное приложение. Все действительно очень просто.
Краткая справка по терминологии, используемой в графической системе Photon OS QNX:
widget - любой отдельный GUI-компонент системы Photon. В терминологии Windows это - окно (window). Термин widget вообще широко распространён в различных графических средах мира UNIX: Tcl/Tk, Qt, Motiff, wxWindow и др. В приложении для Photon отдельными widget'ами будут, например: кнопки, окна, текстовые поля (label) и др. В общем случае, понятие widget шире, чем "визуализируемый объект", некоторые widget Photon могут вообще никак не отображаться на экране, например: PhTimer - генерирующий периодические временные метки в программе, и никак не отображаемый на экране. Каждый widget в приложении имеет имя. При создании widget в PhAB ему присваивается имя, совпадающее с именем его класса (например: PtSlider, PtImageArea и т.п.), т.е., вообще говоря, имена widget могут совпадать, но это только до тех пор, пока этому widget'у не потребуется добавить callback - специфическую реакцию на событие. callback может быть только - "реакцией на событие, происшедшее в конкретном widget". Для этого программист должен переопределить для widget произвольно выбранное индивидуальное имя. Система Photon глубоко объектна по своей идеологии, хотя инструмент её реализации (классический C) и не поддерживает такую модель, да и авторы избегают термина "объект". В объектной терминологии всё поле изображения Photon состоит из множества объектов, каждый их которых является widget. Помимо предопределённых классов widget, разработчик может создавать свои собственные, новые (custom) классы widget, но это уже - "техника высшего пилотажа".
события widget - внешние воздействия (обычно, действия пользователя, но не только), воздействующие на widget (перетаскивание, минимизация-максимизация, нажатие кнопки). Обычно событие widget требует отработки соответствующей реакции в программном коде.
callback (callback-функция) - функция в программном коде, обеспечивающая обработку какого-либо события widget (callback-функция вызывается при наступлении события).
ресурс widget - какое либо из "свойств" widget (размер, цвет заголовка, ширина рамки, текстовая надпись и др.), которые могут считываться или устанавливаться программно. Полный перечень ресурсов зависит от класса, к которому принадлежит widget. Каждый ресурс имеет индивидуальное закреплённое (константное) имя вида Pt_ARG_POINTER или Pt_ARG_FILL_COLOR... Каждый ресурс, кроме того, имеет тип значения, которое он может принимать: численный, строчный, битовые флаги и т.д. (В объектной терминологии ресурсы - это "свойства" объекта, представленного экземпляром widget).
свойство ресурса - текущее значение, которое присвоено ресурсу widget. Почему авторы системы не пользуются "прямым" термином: значение ресурса? Наверное, потому, что ресурсы могут быть не только численными или строчными, и использование термина "значение" для множества битовых флагов, или ещё более сложного ресурса только затуманивало бы картину.
Общая и справочная информация о QNX 6.X и PhAB:
[1] - Олег Цилюрик, Дмитрий Алексеев "ОС QNX сегодня", "Компьютерное обозрение", Киев, 18-19/2002, http://itc.ua/article.phtml?ID=9884&pid=108
[2] - Олег Цилюрик, "Новое лицо QNX", "Программист", 5/2002.
[3] - Олег Цилюрик, Дмитрий Алексеев "Новое лицо операционной системы QNX", "Компьютеры + Программы", Киев, 7-8/2002.
[4] - Интерактивная HELP-подсистема OS QNX 6.2.
[5] - Олег Цилюрик, "Визуальные инкапсулированные компоненты в Photon QNX", "Программист", 5/2002.
1 Для QNX существует несколько развитых IDE от сторонних производителей, например, Eclipse от IBM, или CodeForge, достаточно известный в мире Linux.