Дистрибутив операционной системы QNX 6.X комплектуется, помимо прочего, собственным инструментом для разработки приложений на C/C++: Photon Application Builder - PhAB (о составе и особенностях системы мы писали ранее [1]-[3]). PhAB, по сути дела, не является IDE в привычном смысле (да и к QNX существуют несколько развитых IDE от сторонних производителей): он не содержит собственного редактора исходных текстов, символьного отладчика, и, самое главное - не генерирует некоторый "проект", в том смысле, как это делают например CodeForge, MS Visual CPP или Borland Builder. Это, скорее, построитель GUI-образов приложения: PhAB избавляет разработчика от рутинной работы по отслеживанию размеров, взаимных положений, цветов и мн. др. характеристик GUI компонентов приложения, и позволяет привязать обрабатывающий код (или другие widget) к GUI событиям (нажатия кнопок, перемещения окон, ввод с клавиатуры). Весь же программный код реакции на события разработчик прописывает традиционными методами на C/C++.
Примечание: Всё последующее изложение ориентировано, в первую очередь, на создание приложений именно на C++, потому, что: а). автор сам работает на C++, избегая традиционного C, и б). QSSL в своих разработках оперирует именно преимущественно C, и использованию C уделено достаточно внимания в документации QNX [4], в то время, как особенности использования C++ в PhAB освещены гораздо слабее
Примечание: Ниже везде описывается техника построения программных приложений для Photon с использованием построителя приложений PhAB. Тем не менее, существует совершенно другая техника написания приложений для Photon - чисто программная манипуляция widget-ами: создание, размещение, уничтожение и т.д. Она настолько "другая", что является сама по себе предметом для отдельного описания, и здесь не рассматривается.
Результатом работы генератора PhAB является достаточно понятная структура файлов заголовков, определений и т.д., и, главное, традиционных Makefile, которые могут далее обрабатываться традиционно средствами утилиты make. Показательным является, например, возможность переноса первоначально сгенерированного в PhAB приложения в CodeForge, генерации проекта на основании Makefile, и последующее его развитие в CodeForge.
Такое сочетание визуального проектирования GUI-компонент, с возможностями задания большого числа разнообразных механизмов связывания событий GUI с программным кодом, и с традиционным написанием программного кода даёт, как это не кажется странным, очень высокую итоговую результативность разработки. Другими несомненными достоинствами PhAB являются: простота адаптации и интуитивная понятность происходящего. В результате - программист с изрядным опытом C++, но никогда не сталкивавшийся с QNX, вполне готов создавать приложения в PhAB после 3-4-х часов знакомства с системой разработки.
Начинаем создание проекта. Первое, что нам нужно сделать - запустить Photon Application Builder. Обычно это не вызывает проблем: "Launch - Development - Builder". Однако, и с этим многие сталкивались на практике, после очередной неудачной установки программных пакетов в QNX, позиция "Builder" может "исчезнуть" из меню запуска приложений! Это связано с некоторыми дефектами системы установки программных пакетов в QNX, и, нужно надеяться, будет поправлено в скором времени в последующих версиях системы. Но нам то нужно делать приложения сегодня! Эти трудности не должны вас смущать: запускайте PhAB командной строкой /usr/photon/appbuilder/ab, например, из окна терминала. В любом случае, вы должны получить окно приложения PhAB, что-то подобное тому, что показано на рис.1 - все остальные манипуляции на протяжении последующего рассмотрения мы будем производить внутри этого приложения.
Первоначально мы должны создать для своего приложения главный widget-контейнер (widget в терминологии Photon, да и многих графических экранных систем в UNIX принято обобщённо называть любой отдельный графический компонент: кнопку, окно, диалог и т.д.). В терминологии PhAB требуемый нам widget-контейнер - это модуль (с вторичными, независимыми модулями мы ещё будем сталкиваться по тексту).
 Рис. 1
Для создания главного модуля приложения воспользуемся меню "File-New" - нам будет представлен на выбор список из 13 типовых видов окна главного модуля приложения. Выберем тип, например Plain, позже мы можем его поменять и произвольно расширить.
Примечание: PhAB обеспечивает работу с модулями следующих типов: window, dialod, menu, icon, picture. В качестве главного модуля приложения используются первые два.
Рис. 2
Выбрав тип модуля приложения, мы получим изображение этого модуля на экране, и ему по умолчанию будет присвоено имя base (имя видно на рис.2). Имя любого widget в проекте можно посмотреть или сменить на закладке "Resources" (видна на рис.1), отметив требуемый widget в окне проекта. При создании нового widget в проекте (сверх начального окна приложения), ему будет присвоено стандартное имя, совпадающее с наименованием типа widget. Эти начальные имена могут быть сохранены за widget только в том случае, когда для него не определяется какая-либо специфическая реакция на события GUI (пассивный widget). В противном случае, вы обязаны присвоить widget произвольное уникальное имя на закладке "Resources" (мы к этому будем ещё неоднократно возвращаться).
После определения главного модуля приложения, целесообразно сразу же определить некоторые его параметры, воспользовавшись меню "Application-Startup Info Modules".
Получившееся окно определения параметров приложения показано на рис.2. В левой части расположены флажки "Enable Window State Arguments", "Enable Window Dimension Arguments", "Enable Window Position Arguments" - все они разрешают (или запрещают) управлением состоянием (развёрнуто - свёрнуто и т.д.), размером и положением главного окна приложение при его старте указанием соответствующих ключей в командной строке запуска приложения (см.[4]). Они первыми бросаются в глаза, но являются не столь значимыми. Важной возможностью этой формы является задание имени функции, инициализирующей приложение в целом ("Initialization Function" - она вызывается первой, до создания окна приложения, и получает те же параметры "argv" и "argc", что и функция main в традиционных консольных приложениях). Ещё одна возможность - определение функции инициализации главного окна ("Setup Function"), которая может вызываться непосредственно перед, или сразу после отображения окна на экране (подобную возможность вызова "до" и "после" мы скоро увидим ещё раз при рассмотрении callback). В конечном счёте, вы можете вызывать инициализирующую функцию дважды: и до и после - но это уже экзотика. Синтаксис написания имён соответствующих функций в точности совпадает с синтаксисом записи callback -функций, и будет рассмотрен ниже. Один из самых важных, но малоприметных флажков формы - это "Scan Source Files for Prototypes" (он определяет, будет ли использоваться файл proto.h в вашем проекте). Очень важно! Для C++ этот флажок должен быть снят (по умолчанию он - установлен), для C от обычно установлен. Это место для многих явилось долговременным препятствием использования C++ в PhAB приложениях.
 Рис. 3
Но все выполняемые до сих пор операции не приближают нас к созданию нового приложения. Фактическое создание проекта в PhAB происходит при первоначальном выборе из меню: "File-Save As …" для нового проекта. При этом PhAB предлагает вам определить имя нового проекта (и, тем самым, его местоположение в файловой системе), как это показано на рисунке - новый проект назван "ххх", и он будет помещён во вновь созданный каталог $HOME/xxx (в показанном на рисунке случае - пользователь root, везде в дальнейшем изложении я буду считать этот каталог - текущим).
 Рис. 4
Далее мы должны произвести начальную генерацию проекта, воспользовавшись для этого меню "Application-Build+Run" (для первой фазы этого процесса - генерации - можно воспользоваться "Application-Generate"). Окно генерации и сборки проекта показано на рис.4, и мы ещё к нему будем неоднократно обращаться. Пока только обратим возможности на некоторые опциональные (вы их можете всегда сменить) параметры: "Version": позволяет собрать "Debug" или "Release" задачу, а "Action": использовать "Make shared" или "Make static" сборку проекта, а также "Clean" - очистить каталог проекта от временных файлов.
Нам предстоит выполнить "Generate" для вновь созданного проекта. Необходимость "Generate" будет неоднократно возникать позже при любых изменениях во внешнем виде GUI: новые widget, изменения размеров, положений (но необязательна при коррекции C++ программного кода, не затрагивающей widget, или взаимодействия widget с кодом). На рис.4 видно, что в случаях, требующих выполнения перегенерации проекта, операция "Make" недоступна. При первичной генерации проекта в открывшемся окне выбора платформы вам предлагается сделать выбор - рис.5. (Напомню, что QNX реализует поддержку до десятка процессорных платформ [3]. В моём случае система инсталлирована с поддержкой только платформы x86, как впрочем, скорее всего, и у вас. В окне выбора согласитесь с предложенным "qcc". Не прельщайтесь выбором "default" - вам придётся повторить весь процесс создания проекта с самого начала.)
Теперь, выбрав процессорную платформу, выполняет, наконец, последовательно "Generate" и "Make" - после генерации файлов проекта и их компиляции вы должны получить работающее бинарное приложение с именем xxx (совпадающим с именем проекта) в каталоге ./src/ntox86 (последняя часть пути определяется той процессорной платформой, которую мы выбрали ранее). Проверьте ваше новое приложение, выбрав "Run" в меню построения проекта (рис.4). Заметьте, что мы уже имеем рабочее приложение для Photon, ещё не написав ни одной строчки программного кода!
 Рис. 5
Собственно, вся дальнейшая работа над приложением - это придание ему функциональности, для чего мы и должны написать соответствующие участки программного кода, и связать его с событиями GUI (нажатия кнопок, ввод текстовых полей, выбор из селективного списка и т.д.). Логика поведения модели (отбросив все детали) предельно проста:
Каждый widget (будь то сложное окно-контейнер, или простейшая кнопка) может генерировать реакцию на события GUI образов: нажатия кнопок, перемещения окон и изменения размеров… Часто события, вызывающие реакцию - это действия пользователя, но не всегда, более того, это только малая их часть. Событиями являются и фазы "внутренней жизни" widget: запуск приложения, момент прорисовки окна на экране, истечение времени тайм-аута и т.д. (хотя для таких "внутренних" событий в их длинных цепочках всё равно "спусковым механизмом" где-то в начале цепочки является действие пользователя).
С помощью PhAB визуального построителя мы можем определить реакцию в программе, которую вызовет событие widget. Это достигается регистрацией "callback" при создании приложения. Определяются callback на закладке "Callback" построителя (рис.1) при выборе определяемого widget (мы уже ранее сталкивались с аналогичной закладкой "Resources" для widget).
Существуют callback нескольких классов, но основные из них, которые нас будут интересовать, 2: "widget" - открытие другого окна по событию, и "code" - функция в программном коде, которая вызывается по наступлению события (другие классы, могут быть построены как комбинация этих).
callback класса "widget" может определять порождение другого окна (по имени этого widget) при наступлении события, которое, в свою очередь, может по своему созданию вызывать порождать следующий widget и т.д. создавая целую цепочку порождаемых widget. В частном случае, такой callback может порождать новую копию того же widget, который реагирует callback-ом на событие, на манер рекурсивных вызовов функций, при этом может создаваться целая цепочка однотипных GUI-компонентов. Естественно, и в этом есть определённая опасность, что, как и при рекурсии, необходимо обеспечить завершение этого процесса, например, по числу созданных копий.
Другой класс - callback типа "code" (наверное, самый широко используемый) - это вызов программной функции по событию, с передачей функции в качестве параметров характерных особенностей события. После выполнения очередной перегенерации PhAB добавит в программный файл "заглушку" такой функции с заказанным вами при определении callback имени. Это существенно важно, потому, что параметры callback-функций (которые несут информацию о событии) имеют достаточно сложную структуру. (Заметим здесь, кстати, что при удалении или переопределении callback в проекте, когда необходимость в функции с заданным именем отпадает, PhAB не может удалить текст callback-функций из программного кода, и, в отличие от создания, вы должны будете удалить его вручную).
Для одного и того же события вы можете определить несколько реакция на одно событие, часть из которых могут быть класса "widget", а другие - класса "code". Тогда все они отработаются поочерёдно при наступлении события (а иначе не было бы никакой возможности псевдо - рекурсивного создания widget, как описывалось выше: кто-то должен считать созданные компоненты). Более того, вы можете переопределять последовательность срабатывания этих callback в списке.
Для многих событий вы можете определить срабатывание callback непосредственно "до" или "после" наступления события ("для многих": здесь означает те "внутренние" события, определяемые фазами жизни widget, например, "прорисовка на экране"; с другой стороны - абсурдно было бы пытаться определить callback, срабатывающий "перед" нажатием пользователем кнопки…).
С другой стороны, каждый widget имеет набор так называемых "ресурсов" - текущих свойств widget-та. Конкретный набор ресурсов определяется видом widget: ресурсы кнопки существенно отличаются от ресурсов текстового поля ввода. Ресурс определяет то, как widget выглядит на экране: цвет, размер, текст, отображаемый в поле ввода-вывода и т.д.
Пользователь из программного кода всегда имеет возможность прочитать или изменить значение любого ресурса любого (созданного, прорисованного на экране!) widget-а. Для этого используется большая группа функций GetResource(s) - SetResource(s), составляющие основу программирование под Photon. Использование этих функций, само по себе, столь объёмный предмет, что я не буду на нём останавливаться вообще, тем более, что он исчерпывающе описан в [4].
Все widget, которые вы добавляете в приложение Photon, упорядочиваются древовидную иерархию, которая, в том числе, определяет и иерархию видимости widget. Изменить положение каждого widget в иерархии можно просто "перетаскивая" его в дереве на закладке "Module Tree". Этот процесс настолько визуальный, что "лучше один раз увидеть, чем сто раз услышать".
Собственно, освоившись с логикой модели приложения PhAB, строить приложения становится весьма просто (я не говорю о написании вашего целевого программного кода, а только о следующих вещах: добавление нового widget к приложению, связывание реакций этого widget с вашим программным кодом, изменение свойств и вида widget из программного кода). Весь процесс происходит как многократная последовательность (для каждого нового widget) следующих шагов:
1. На закладке "Widgets" PhAB выбираем требуемый тип добавляемого widget-а, там их огромное множество. Выбранный widget мы "перетаскиваем" (мышью) в окно того контейнера, где ему надлежит быть по логике приложения. Вы можете таким же образом (мышью) подкорректировать (сейчас или позже) положение и размер устанавливаемого widget. Но точное местоположение и размер (с точностью до 1-го пикселя) лучше устанавливать с помощью наборных полей "X" "Y" "W" "H" в левой части окна PhAB (видны на рис.1).
2. Для установленного widget на закладке "Callback" (рис.6) для требуемых событий устанавливаем требуемые реакции (чаще всего класса "code"). Общий синтаксис записи имени требуемой функции реакции на событие (то же относится и к функциям инициализации приложения и его главного окна, см. выше) выглядит так Class::Function@File.Type, где любой компонент записи кроме "Function" может быть опущен, и где:
Function - имя функции обработчика callback. После перегенерации проекта в ваш программный код будет добавлена заготовка функции с таким именем и требуемым списком параметров.
File - имя файла исходного кода программы, в который будет помещён обработчик. Если исходный файл с таким именем не существует в проекте, то он будет создан. Если вы не указываете имя файла, то PhAB создаст файл с именем, совпадающим с именем функции обработчика, и поместит его туда (но эта идея "на каждое событие - отдельный файл программы обработчика" не кажется мне самым удачным).
Type - расширение имени файла ("c" или "cpp"). Если тип не указан - предполагается C. Это поле определяет, синтаксис какого языка (C или C++) задаст PhAB компилятору gcc. Во избежание недоразумений, особенно при работе с C++, лучше это поле указывать явно.
Class - используется только с С++, и определяет тот класс, функция-член которого Function будет использована в качестве обработчика. Как вы понимаете, в качестве таких функций могут быть использованы только static-члены, которые должны быть определены вне зависимости от создания конкретных объектов этого класса (в Java для различения используется крайне удачная терминология: "члены объекта" и "члены класса").
 Рис. 6
После выполнения очередного "Generate" из окна построения приложения (рис.4) в ваш программный код будет добавлен шаблон функции Function с требуемым набором параметров.
3. Дописываем в Function требуемый по логике приложения программный код. Из этого кода вы можете "добираться" к указателю вызвавшего реакцию или любого другого widget в приложении (как это делать я опишу чуть ниже), и изменять его ресурсы, используя GetResource(s) - SetResource(s) (а вот как это делать - см. [4]).
4. Делаем "Generate" - "Make" - "Run" из окна построения приложения (рис.4) и проверяем, достигли ли мы желаемого эффекта.
Примечание: созданное приложение может запускаться не только по "Run" из окна построения приложения, но и обычной командой запуска по имени приложения из окна pterm. Есть принципиально важное отличие, делающее необходимым это примечание: GUI приложение, построенное PhAB может использовать вывод (ввод) в(из) стандартные потоки вывода (ввода): SYSOUT, SYSERR (SYSIN). Это настолько мощная возможность (сравните с Windows GUI), что её нужно постоянно иметь в виду: для вывода отладочной информации, для ведения log-файлов перенаправлением потока вывода. SYSIN может использоваться при связывании его, например с последовательными терминальными линиями /dev/ser для ввода со специального оборудования: сканеров штрих-кодов, весового оборудования и т.д. Все эти возможности, в большинстве случаев, можно использовать при запусках приложения только из окна pterm (или использовании утилиты ditto).
Всё! И так последовательно для всех widget, добавляемых в проект и по мере их появления - до тех пор, пока вы не получите завершённое приложение. Всё действительно очень просто.
Более тонкие вопросы: Общая логика построения сколь угодно сложного GUI приложения в точности соответствует многократно повторённому процессу, схематично набросанному выше. Но на каждом из таких шагов приходится решать ряд "тонких" вопросов (к счастью, единообразных на каждой итерации построения приложения). Часть из них мы и рассмотрим ниже.
1. Общий вид функций callback. При определении функции реакции (назовём её OnXXX) на событие (рис.6) PhAB автоматически добавит вам в указанный файл программного кода функцию вида:
int OnXXX( PtWidget_t *widget,
ApInfo_t *apinfo,
PtCallbackInfo_t *cbinfo ) {
/* eliminate 'unreferenced' warnings */
widget = widget,
apinfo = apinfo,
cbinfo = cbinfo;
return( Pt_CONTINUE );
}
Если мы говорим в этих заметках о написании именно C++ кода, то можете смело вытирать 3 строки фиктивных присвоений после комментария об "unreferenced" предупреждениях компилятора. Далее, мне больше импонирует строка возврата в C++ синтаксисе: return Pt_CONTINUE. Но наибольший интерес для нас в этом рассмотрении должны представлять 2 параметра: widget - указатель на widget, в котором произошло событие, и cbinfo - структура, содержащая детальную информацию о характере события. Точный вид cbinfo (тип PtCallbackInfo_t* для него является только "базой") зависит от вида отлавливаемого события: для событий клавиатуры - там вы наёдёте код клавиши, для событий мыши - координаты и т.д. Обычно в этом месте возникает одна сложность: не забудьте предварительно преобразовать cbinfo к подтипу, характерному для вашего события. Детальная структура cbinfo достаточно разнообразна для разных событий, но исчерпывающе описана в [4].
2. Как "добраться" к widget из программного кода? Это - основополагающий вопрос, особенно поначалу, при построении GUI приложений. Это - как с переменными в вашем программном коде: как только вы идентифицировали переменную её полным именем (простым или составным: X, p->X, a.X, a.b->c.X и т.д.), то вы можете считывать её значение или произвольно его изменять. Первый способ простейшей идентификации окна мы уже видели выше - callback функции в качестве первого параметра передаётся указатель widget породившего событие окна. Дальше вы можете выполнять нечто подобное (не задавайтесь вопросом "что происходит" относительно выражений в примерах, примеры - условные, я показываю только "как это можно сделать"):
PtSetResource( widget, Pt_ARG_POINTER, this, 0 );
PtSetResource( widget, Pt_ARG_FILL_COLOR, Pg_RED, 0 );
Или:
PtArg_t arg[ 3 ];
PtSetArg( &arg[ 0 ], Pt_ARG_ONOFF_STATE, 1, 0 );
PtSetArg( &arg[ 1 ], Pt_ARG_TEXT_STRING, "текст", 0 );
PtSetArg( &arg[ 2 ], Pt_ARG_FILL_COLOR, PgRGB( 254, 127, 127 ), 0 );
PtSetResources( widget, 3, arg );
Я здесь умышленно показал 2 принципиально разных способа изменения ресурсов widget, чтоб заинтриговать перечитать раздел [4] об чтении и изменении ресурсов … но я же обещал выше, что не буду объяснять этот предмет…
Но основной способ состоит в использовании предопределённых имён, сгенерированных для нас PhAB. Если мы создаём widget и присваиваем ему имя "www" (помните, присвоение имён на закладке "Resources"?), то PhAB автоматически сгенерирует для нас 2 имени (это не относится к widget с предопределёнными именами, присвоенными после создания автоматически и не изменёнными пользователем): ABW_xxx и ABN_xxx. ABW_xxx (эти имена ещё называются в терминологии PhAB "манифесты") - это указатель на размещённый (на экране) widget с именем "www", а ABN_xxx - это целочисленный идентификатор этого widget в системе окон приложения. Может быть и ещё одно имя для этого widget (internal link) вида ABM_xxx - имя модуля, но о нём я отдельно расскажу ниже. Что нам это даёт? Использование ABW_xxx крайне просто, и не вызывает вопросов, например:
PtSetResource( ABW_xxx, Pt_ARG_TEXT_STRING, "новый текст", 0 );
С ABN_xxx несколько замысловатее. Как вы понимаете, ABW_xxx - это сугубо статичесое имя (указатель), привязанное к конкретному окну в момент его создания (см. ниже). Существуют группа функций, осуществляющие поиск окна по имени в иерархии, например, ниже показано, как динамически ищется окно с именем ABN_xxx внутри контейнера widget (внутри контейнера вы можете разместить множество по разному именованных окон):
PtWidget_t *pF = ApGetWidgetPtr( widget, ABN_xxx );
Или, это иногда сильно экономит код, можно указать одну и ту же функцию обработчика callback для целой группы однотипных widget, например, группы кнопок, отличающихся цветом (и, соответственно, именем):
int OnXXX( PtWidget_t *widget,
ApInfo_t *apinfo,
PtCallbackInfo_t *cbinfo ) {
if ( ApName( widget ) == ABN_btn_red ) { . . . }
return Pt_CONTINUE;
}
Могут быть и более изощрённые способы. Например, для крупного проекта может быть целесообразным создать C++ класс, однозначно связанный со своим widget контейнером, содержащем, в свою очередь, множество widget компонентов. А все события в контейнере и содержащемся в нём компонентах будут полностью автономно обрабатываться функциями членами программного класса. Тогда и программный класс и все widget за пределами класса могут быть вообще неименованными - все внешние события GUI отлавливаются внутри класса и отображаются на widget-ы (хотя, конечно, внутри класс должен оперировать теми же ABW_xxx и ABN_xxx). Как такое сделать рассказано в [5], этот же материал в чуть более ранней редакции размещён и на сайте QNX.ORG.RU в разделе "Разработчику" - "Статьи".
3. Как динамически отобразить widget контейнер на экране? PhAB позволяет создавать неограниченное число новых widget контейнеров, называемых "модули" (меню "Window"). Таким же модулем, по существу, является и главное окно приложения (обычно с именем base), с той лишь разницей, что главное окно автоматически отображается при старте приложения, а прочие определённые модули должны отображаться действиями пользователя при наступлении определённых условий. Это один из самых мощных механизмов PhAB!
Примечание: например, часто возникает потребность в создании приложения, вообще не отображающего главного окна, а "выбрасывающего" окна (всплывающие окна) при наступлении некоторого события. Причём, не исключено, что события могут перекрываться во времени, т.е. окон может потребоваться несколько. Совсем недавно мне пришлось сделать классический фрагмент такого типа: приложение GUI должно считывать штрих-код со стандартного сканера с последовательного порта, после чего показывать GUI окно оператору. Как-то вскорости я опишу, как это сделать в 2-3 оператора кода.
Примечание: по этой же причине из функции инициализации приложения (рис.2) нельзя обратиться к главному окну приложения ABW_base и предустановить его ресурсы - это закончится аварийным завершением приложения. К моменту инициализации приложения структуры главного окна приложения ещё не существует, и ABW_base - в лучшем случае NULL. Но это действие вполне можно осуществлять уже из функции инициализации окна (тот же рис.2), вызываемой непосредственно вслед за функцией инициализации приложения.
Для манипулирования окнами модулей создаёся "Internal Link" (в том же меню "Window"), после создания которой в приложении появляется новое доступное имя ABM_next. Вот это имя и может быть использовано для динамического создания окна модуля на экране:
PtWidget_t *pM = ApCreateModule( ABM_next, NULL, NULL );
Обратите внимание, функция ApCreateModule() визуализирует окно модуля, и только после этого в программу возвращается указатель структуры widget, эквивалентный ABW_xxx для статических widget на экране. Далее, уже используя этот указатель как идентификатор контейнера, вы можете получать доступ ко всем размещённым в контейнере widget, например, по его имени:
PtWidget_t *pС = ApGetWidgetPtr( pM, ABN_component );
Обратите ещё внимание: подобную операцию создания одного и того же модуля можно выполнять многократно. При этом вы получите отображение несколько идентичных копий GUI контейнеров на экране, но доступ к каждой из копий будете иметь через различные указатели widget, возвращаемые при каждом последовательном вызове ApCreateModule().
Модули, определяемые в проекте, в том числе и модуль главного окна, находят своё отображение в файле /src/ablinks.h и /src/abevents.h (см. ниже).
4. Работа с widget в потоках (thread). На месте этом спотыкается практически каждый, приступая к работе с библиотеками Photon, поэтому его стоит назвать. Использование threads характерно вообще для real-time OS QNX, и особенно плодотворно для описания событийных процессов в структуре GUI приложения. Но сложность состоит в том, что библиотеки графических примитивов Photon - не реентерабельны, и простой вызов PtGetResource (PtSetResource) из потока может привести (скорее всего) к аварийному завершению приложения. Как делу помочь? Нужно "обложить" участок кода в потоке, манипулирующий с графическими библиотеками, функциональными "скобками": PtEnter() - PtLeave() (монопольный захват графической библиотеки и её освобождение). Вот пример:
int flags = PtEnter( 0 );
pFrame = ApCreateModule( ABM_component, NULL, NULL );
PtLeave( flags );
При этом необходимо блюсти определённую осторожность: если вы захватите библиотеку PtEnter(), но забудете освободить её PtLeave(), то, скорее всего, приведёте к гибели своё приложение. К тому же результату, зачастую, приведёт и захват библиотеки из главного (main) потока приложения.
5. Структура файлов проекта. Можно было бы вовсе не останавливаться на этом вопросе (просто рассмотрите структуру вновь созданного PhAB проекта - там всё просто), если бы он не требовался для следующего пункта нашего рассмотрения. Поэтому, сделаем это крайне поверхностно. После создания проекта вы будете иметь его структуру, подобную следующей (конкретные имена файлов программных кодов в /src, описаний widget в /wgt и рабочих файлов проекта в /src/gcc_ntox86 будут зависеть от проекта, ниже показан вид одного из "живых" проектов с именем face):
/face
abapp.dfn
abapp.wsp
/wgt
base.wgtw
turnic.wgtw
control.wgtd
diags.wgtd
pic.wgtp
/src
abmulticpu
abplatform
ablinks.h
ablibs.h
abevents.h
abwidgets.h
abimport.h
abdefine.h
abvars.h
abHfiles
abSfiles
abOfiles
abWfiles
indHfiles
indOfiles
indSfiles
Usemsg
Makefile
AbLfiles
abmain.cc
face.cpp
config.cpp
channel.cpp
utils.cpp
channel.h
utils.h
config.h
\gcc_ntox86
face
proto.h
Makefile
face.o
config.o
channel.o
utils.o
Большинство обязательных файлов проекта имеют специальные текстовые форматы, генерируемые PhAB, и крайне поучительно внимательно рассмотреть их подробнее. Но оставим это индивидуально любопытству каждого, я же только назову некоторые из файлов.
Каталог /wgt содержит описания внешнего вида widget, используемых приложением - один файл на один модуль. Их расширение говорит о типе описываемого модуля: *.wgtw - window, *.wgtd - dialog, *.wgtm - menus - многоуровневые текстовые меню, *.wgtp - picture (база данных widget, в том числе - изображений), *.wgti - "иконки", используемые приложением для отображения в "shelf" и "launcher" и т.д. Эти файлы генерируются PhAB (модифицируются при визуальной коррекции GUI), несмотря на то, что они имеют текстовый формат, никогда не следует редактировать их вручную.
Каталог /src - исходные описания проекта (в том числе, и исходные тексты программ). abmain.cc - это файл, содержащий головную программу запуска приложения (генерируется PhAB). Как будет отмечено далее, "его имя начинается с ab", что означает - не модифицируйте его, не пытайтесь включить в своё приложение традиционную функцию main(). Файл abvars.h содержит ABN_* идентификаторы всех известных в приложении widget, abdefine.h - определяет их (widget) ABW_* имена, а abevents.h - описывает все объявленные реакции на события (callback). Если вы работаете с C++ проектом, и сняли флажок "Scan Source Files for Prototipes" меню "Application - Application Startup Information" (как об этом говорилось выше), то PhAB запрещено автоматически генерировать файл прототипов объявляемых функций - /src/ gcc_ntox86/proto.h (он все равно генерируется, но в этом случае - "пустой"). Для прототипов при этом будет использоваться файл abimport.h.
В документации [4] сказано: "все файлы, имена которых начинаются с "ab", являются собственными файлами PhAB, и не должны подвергаться модификации. Попытка модификации этих файлов может привести к потере работоспособности приложения, или к некорректному его поведению".
Самые интересные с позиции пользователя должны быть файлы: indHfiles, indOfiles и indSfiles - они позволяют подключать к проекту произвольные программные файлы пользователя. Их использование мы и рассмотрим ниже.
6. Подключение к проекту собственных файлов программного кода. Все исходные файлы, имена которых фигурировали при объявлениях callback функций - известны PhAB и будут корректно включены в проект. В конечном итоге - включите в каждый программный файл хотя бы один callback, и ваш проект будет корректно собираться. Но это не кажется самым удачным решением, напротив: хорошо бы собрать все callback в 1 или 2 программных файла, обеспечивающих взаимодействие с GUI (интерфейс к GUI), с тем, чтобы все остальные N исходных файлов проекта содержали "чистый" прикладной программный код, вообще ничего не зная об GUI. Для обеспечения этой возможности разработчики PhAB и предусмотрели текстовые файлы indHfiles, indSfiles и indOfiles, файлы соответственно: определений (хэдеров), исходных описаний и объектных файлов. Все они в новом созданном проекте имеют "пустой" вид, строки вида, соответственно:
MYHDR =
MYSRC =
MYOBJ =
В них вы можете добавить список (через "пробел") используемых файлов соответствующего назначения. Вопрос состоит только в том, относительно какой из рабочих директорий используются indHfiles, indSfiles и indOfiles, и, соответственно, относительно какой директории указывать путевое имя подключаемых файлов (при несоответствующем указании вы, зачастую, даже не сможете выполнить "Generate" для проекта). Рассмотрим их поочерёдно, предполагая, для начала, что все *.h и *.cpp файлы, которые мы желали бы подключить к проекту, размещены в каталоге /src (там, где и все "родные" исходные файлы проекта PhAB).
Файлы определений (хэдеры) используются из включающих их *.cpp файлов в каталоге /src, т.е. должны объявляться в indHfiles как файлы из текущей директории (для конкретности указываются имена файлов из проекта, структура которого была приведена ниже - можно провести перекрёстное сравнение):
MYHDR = Channel.h Config.h Utils.h
Но исходные *.cpp файлы используются при обработке Makefile из каталога выбранной платформы (\src\gcc_ntox86), и, поскольку они находятся в \src, то должны быть описаны в indSfiles так:
MYSRC = ../Channel.cpp ../Config.cpp ../Utils.cpp
Наконец, полученные в результате компиляции объектные файлы, будут размещены в \src\gcc_ntox86, и, поскольку эта директория совпадает с текущей директорией Makefile, в indOfiles они снова должны быть описаны так:
MYOBJ = Channel.o Config.o Utils.o
Примечание: вот эти премудрости указания местоположения натолкнули на очень разумные возможности по произвольному размещению исходных файлов, общих для группы проектов PhAB (повторное использование кода). Несколько проектов из родственных прикладных областей (например, XXX, YYY и ZZZ) размещаем при создании PhAB внутри общего каталога, скажем, PRJ. В этом же каталоге PRJ помещаем и общие прикладные *.h и *.cpp файлы (я, для примера, укажу только одну пару: file.h и file.cpp). Дальше я пользую 2 способа:
1. Описываем в indHfiles, indSfiles и indOfiles, соответственно:
MYHDR = file.h
MYSRC = ../../../file.cpp
MYOBJ = file.o
2. В /src делаем ссылки
#link ../../file.h file.h
#link ../../file.cpp file.cpp
А уже с файлами-ссылками в /src поступаем традиционным образом. Второй способ хорош, когда необходимо включить большую группу файлов "общего использования", например, из ранее подготовленных проектов, и эту группу удобно разместить в отдельном каталоге и пользоваться его абсолютным именем:
#link $HOME/OBJECTY/file.cpp file.cpp
7. Использование библиотек. Иногда необходимо использовать в проекте библиотеки QNX, не подключаемые по умолчанию PhAB при построении проекта (противное было бы неразумно, учитывая множество библиотек, присутствующих в QNX). Один из самых частых примеров - библиотека socket - TCP/IP сокетов. Для включения библиотеки в проект нужно добавить её имя к переменным, описанным в Makefile: LDFLAGS - если вы хотели бы, чтоб библиотека подключалась к приложению динамически, SDFLAGS - статически.
Можно, с другой стороны, создавать полностью приложение PhAB в виде DLL-библиотеки, несмотря на то, что сам PhAB, вообще-то говоря, ничего не знает о DLL, но именно поэтому, вся инициализация при этом должна быть выполнена вручную:
добавить ключи: -shared к CFLAGS, и -shared -Wl, -Bsymbolic к LDFLAGS или SDFLAGS Makefile проекта;
после выполнения dlopen() из вызывающего приложения, инициализирующая функция определённая в DLL-приложении обязана выполнить ApAddContext() прежде, чем будет выполнено любое обращение к графическим библиотекам Photon;
при завершении вызвавшее приложение обязано вызвать cleanup-функцию DLL и выполнить ряд обязательных завершающих действий
Детальная последовательность действий при построении PhAB приложения в виде DLL достаточно полно и понятно описана в [4].
8. Ещё несколько советов. После завершения некоторого очередного этапа по развитию вашего проекта XXX, выполняйте, находясь в каталоге /XXX/src/gcc_ntox86, команду:
#strip XXX
Это позволяет (иногда до нескольких раз) уменьшить размер результирующего исполнимого файла приложения XXX. При чём, это не зависит от того, какой вид приложения вы собираете "Debug" или "Release". При выполнении этой команды из состава приложения вырезается "мусор", оставленный там в процессе построения.
Поскольку, проекты PhAB переносятся с компьютера на компьютер, пересылаются электронной почтой и т.д. не без участия на промежуточных ступенях OS Windows, то не копируйте и не переносите проекты в "сыром" виде - простым копированием. Дело в том, что при этом теряется информация "user-group" о файлах проекта, а все файлы приобретают статус "исполнимый" (X). Это вносит неудобства, и просто может сделать дальнейшую работу над проектом, после такого переноса, невозможной (без соответствующей модификации chmod). Перед копированием PhAB проекта его удобно упаковать (с сохранением всей учётной информации), например:
#tar -czvf XXX xxx.tgz
А при восстановлении проекта - распаковать:
#tar -zxvf xxx.tgz
В заключение: В заключение этого предельно короткого введения в использование PhAB, хотелось бы высказать следующие соображения. Во-первых, чтоб не создавать превратного представления, необходимо отчётливо определиться, что PhAB ни в малой степени не есть IDE, из числа предназначенных для того, чтоб позволить начинающему программисту визуальным путём строить приложения типа "кое-как, но работает". К последним, например, я отнёс бы Delphi или Borland C Builder (выбор для сравнения - дело индивидуальное, это моё субъективное восприятие). PhAB, напротив - это инструмент, "заточенный" под отработку сколь угодно сложных приложений, но при условии использования его программистом, уже виртуозно владеющим C/C++. Это - построитель GUI интерфейсов, связывающий их с программным кодом, но: … весь программный код вам писать традиционным способом, более того, работая в этом коде со специфическими структурами данных, переданных вам событиями Photon.
Во-вторых, всё, что рассказано выше - это только самое поверхностное прикосновение к технике изготовления приложений в PhAB, ни в коей степени не могущее заменить детальное изучение документации [4], которая и сама по себе является излишне лаконичной. А дальше … эксперименты, эксперименты, эксперименты…
Благодарности: Настоящее описание просто не имело бы шансов появиться на свет, по крайней мере, в представленном виде, если бы не развёрнутое обсуждение предмета на форуме русскоязычных пользователей QNX: http://qnx.org.ru/forum , и неоднократно высказанное там же пожелание составить такое руководство. Таким образом, все участники названного форума, в той или иной мере, причастны к созданию этого текста.
Общая информация о QNX 6.X:
[1] - Олег Цилюрик, Дмитрий Алексеев "ОС QNX сегодня", журнал "Компьютерное обозрение", Киев, №18-19, 15 - 21 мая 2002г, Web-версия
[2] - Олег Цилюрик, "Новое лицо QNX", журнал "Программист", №5, Москва, май 2002г.
[3] - Олег Цилюрик, Дмитрий Алексеев "Новое лицо операционной системы QNX", журнал "Компьютеры + Программы", Киев, №7/8, август 2002г.
[4] - Интерактивная HELP-подсистема OS QNX 6.2.
[5] - Олег Цилюрик, "Визуальные инкапсулированные компоненты в Photon QNX", журнал "Программист", №5, Москва, май 2002г.
На любые возникшие по материалу вопросы автор с готовностью ответит по электронной почте: Олег Цилюрик, фирма "LOT Ltd.", г. Харьков, Украина, olej@lot.kharkov.ua.
|