Table of Contents
Введение
Настоящая статья обсуждает аспекты запуска программ в среде PalmOS. Статья обсуждает нюансы использования функций SysAppLaunch, SysUIAppSwitch и некоторые внутренние механизмы PalmOS, исполюзующиеся для запуска.
Функции, использующиеся для запуска программ
В этой главе кратко обсуждаются функции PalmOS API, имеющие отношение к запуску программ.
PilotMain
Начнем с основы - с формата главной функции любой программы.
UInt32 PilotMain(UInt16 cmd, void *cmdPBP, UInt16 launchFlags);
Параметры:
- cmd - команда, посылаемая программе. В принципе 90% программ обрабатывают только команду sysAppLaunchCmdNormalLaunch, но в некоторых ситуациях приходится ловить и обрабатывать другие коды команд.
- cmdPBP - указатель на параметры команды. Параметры используются командами, такими как sysAppLaunchCmdDisplayAlarm для передачи дополнительной информации о цели вызова.
- launchFlags - самаый интересный параметр. его стоит рассмотреть отдельно.
Флаги sysAppLaunchFlag
#define sysAppLaunchFlagNewThread 0x01 // create a new thread for application sysAppLaunchFlagNewStack #define sysAppLaunchFlagNewStack 0x02 // create separate stack for application #define sysAppLaunchFlagNewGlobals 0x04 // create new globals world for application ID for Memory chunks #define sysAppLaunchFlagUIApp 0x08 // notifies launch routine that this is a UI app being #define sysAppLaunchFlagSubCall 0x10 // notifies launch routine that the app is calling it's // entry point as a subroutine call. This tells the launch // code that it's OK to keep the A5 (globals) pointer valid // through the call. // IMPORTANT: This flag is for internal use by // SysAppLaunch only!!! It should NEVER be set // by the caller. #define sysAppLaunchFlagDataRelocated 0x80 // global data (static ptrs) have been "relocated" // by either SysAppStartup or StartupCode.c // IMPORTANT: This flag is for internal use by // SysAppLaunch only!!! It should NEVER be set // by the caller. // The set of private, internal flags that should never be set by the caller #define sysAppLaunchFlagPrivateSet (sysAppLaunchFlagSubCall | sysAppLaunchFlagDataRelocated)
Флаги стоит рассматривать в двух контекстах - в контексте вызывающего приложения и в контексте вызываемого приложения. Некоторые флаги нельзя задавать при запуске приложения, другие флаги не рекомендуется задавать при запуске, а третьи флаги пользовательские программы никогда не получат.
- sysAppLaunchFlagNewThread - флаг, указывающий, что программа запущена в новом потоке. Ничего хорошего из указания этого флага при запуске не получится. В PilotMain мы этот флаг тоже не получим.
- sysAppLaunchFlagNewStack - флаг, указывающий, что программа будет работать на своем стеке. Для запускающего этот означает, что приложению будет выделен свой собственный стек. Для вызываемого отсутствие этого стека означает, что свободного места в чужом стеке может быть не очень много. Если программа активно задействует стек, то это может кончиться переполнением стека. Учтите, что алармы и нотификации всегда вызываются на стеке вызывающего.
- sysAppLaunchFlagNewGlobals - флаг, указывающий, что вызываемой программе будут выделены глобальные переменные и новый ID для блоков памяти. В результате программа получает возможность адресоваться к глобальным данным через регистр A5. Заметьте, что вообще говоря нет никакой связи между посылаемой командой cmd и наличием данного флага. Но, поскольку все привыкли, что sysAppLaunchCmdNormalLaunch всегда вызывается с доступными глобальными данными, то проверку на их реальное наличие не делают. Таким образом не стоит вызывать привычные команды без привычных, описанных в документации флагов. Новый ID блоков памяти означает, что все блоки памяти, выделенные с таким ID будут освобождены по выходу из программы. Учитывая, что новых ID может быть не больше 14, запуск приложений с этим флагом не рекомендуется. PalmOS SDK очень невнятно говорит про возможность использования этого флага.
- sysAppLaunchFlagUIApp - флаг, указывающий, что запускается приложение, использующее пользовательский интерфейс. Код функции SysAppStartup проверяет этот флаг и выгывает UIReset. Этот вызов уничтожает все окна и инициализирует все переменные состояния UI значениями по-умолчанию. С этим флагом вызываются все приложения, запускаемые по SysUIAppSwitch. Запуск приложения по SysAppLaunch с таким флагом опасен, поскольку сбрасывает все настройки UI текущего приложения.
- sysAppLaunchFlagSubCall - флаг, означающий что данное приложение вызвано из-под того же самого (например, найденная в MemoPad информация, при поиске из самого MemoPad). В этом случае глобальные переменные указывают на те же самые переменные, что и в родительском приложении. Этот флаг нельзя выставлять вызывающему приложению.
- sysAppLaunchFlagDataRelocated - внутренний флаг, выставляется в SysAppStartup, и, похоже, нужен только для PalmOS 1.0 и стартапа CodeWarrior. Я думаю, что причин проверять его нет.
SysAppLaunch
Err SysAppLaunch( UInt16 cardNo, LocalID dbID, UInt16 launchFlags, UInt16 cmd, MemPtr cmdPBP, UInt32 *resultP);
Эта функция используется для запуска приложений. PalmOS использует ее для запуска программ в новых потоках, для запуска программ с UI. Прикладному программисту стоит использовать ее только для sub-launch, для запуска прочих приложений, выполняющих отдельные задачи, в духе вызова функций.
Подобно вызову функции, вызов SysAppLaunch запускает программу и по ее окончании продолжает исполнение вызываемой функции. Исключением является запуск в новом потоке, но поскольку он недоступен программисту, то мы его не рассматриваем.
Параметры у функции очевидные:
- cardNo, dbID идентифицируют базу, которая рассматривается как программа
- launchFlags показывают флаги, используемые для запуска
- cmd и cmdPBP передаются в вызываемую программу.
- в resultP записывает код возврата из функции. Обратите внимание, что возвращаемый результат у PilotMain имеет тип Err, а возвращается он как UInt32
SysUIAppSwitch
Err SysUIAppSwitch(UInt16 cardNo, LocalID dbID, UInt16 cmd, MemPtr cmdPBP);
Функция SysUIAppSwitch используется для передачи управления другой программе с интерфейсом. Эта функция традиционно понимается неправильно. Она не запускает приложение немедленно. Вместо этого она сохраняет параметры во внутренних переменных PalmOS и добавляет в очередь сообщений событие sysEventAppStopEvent.
Что происходит дальше? Дальше функция возвращается в прикладную программу, которая продолжает исполнение. Когда исполнение дойдет до обработки события sysEventAppStopEvent, цикл обработки завершится. Программа вызовет StopApplication и выйдет из PilotMain. PalmOS после выхода из текущей программы считывает сохраненные значения следующей программы и запускает ее.
Обратите внимание на то, что программа запускается не сразу, только после выхода из текущей программы. Обычно вызов SysUIAppSwitch стоит последним в обработчике события, следующим извлеченным событием будет sysEventAppStopEvent, и выход произойдет автоматом. Но если программа использует нестандартный цикл выборки, то могут возникнуть проблемы.
Нажатие на кнопки Datebook / Memo / Address / ToDo / Home / Calc генерируют неявный вызов SysUIAppSwitch с программами, заданными своими создателями в свойствах. Так, нажатие на home вызывает запуск программы с кодом создателя из PrefGetPreference(prefLauncherAppCreator).
Параметры у функции понятны:
- cardNo, dbID идентифицируют базу, которая рассматривается как программа
- cmd и cmdPBP передаются в вызываемую программу.
Обратите внимание, что флаги не передаются. Они неявно формируются как sysAppLaunchFlagNewStack | sysAppLaunchFlagNewGlobals | sysAppLaunchFlagUIApp .
Механизм запуска графических приложений
В этой главе мы обсудим механизм работы потока, в котором исполняются приложения с UI. Описание базируется на коде PalmOS 4, но вполне применимо к версиям PalmOS 2-5.
Старт графического потока
Основой графического потока является приложение UIAppShell.prc. Это приложение запускается из функции SysUILaunch. Запуск происходит с помощью функции SysAppLaunch. Указывается флаг sysAppLaunchFlagNewThread, который указывает на необходимость запуска нового потока. Этот поток становится единственным доступным для прикладных программ.
Инициализация графического потока состоит из следующих этапов:
- Загрузка системных расширений с типом exte.
- Если при сбросе не была указана safe загрузка (нажатая стрелка вверх при сбросе), то выполняется загрузка системных расширений с типом extn.
- Если при сбросе не была указана safe загрузка, то всем программам рассылается код sysAppLaunchCmdSystemReset.
- Всем подписанным рассылается нотификация sysNotifyResetFinishedEvent
- Если при сбросе была указана safe загрузка, то сбросить приложение, запускаемое по умолчанию (PrefSetPreference(prefDefaultAppCreator, 0)).
Цикл запуска приложений
Самой важной функцией UIAppShell является запуск приложений. Именно UIAppShell отвечает за последовательный запуск программ, указанных при вызове SysUIAppSwitch.
Программа оперирует переменными PalmOS GNextUIAppCardNo, GNextUIAppDBID, GNextUIAppCmd, GNextUIAppCmdPBP. Это именно те самые переменные, в которые SysUIAppSwitch сохраняет переданные параметры.
Рассмотрим действия, выполняемые в цикле. Первая задача - выбор приложения, которое будет запущено первым.
- Если устройство залочено (PrefGetPreference(prefDeviceLocked)), то будет запущено приложение с создателем 'secr'.
- Если устройство стартует после hard reset, то запускается приложение с создателем в ресурсе типа 'tint' и номером sysHardResetAppCreatorConstId. Производители устройств записывают в этот ресурс создателя программы Welcome.
- Если PrefGetPreference(prefDefaultAppCreator) выдает ненулевого создателя, то будет запущена эта программа
- Во всех остальных случаях выбор запускаемой программы осуществляется в цикле
- Цикл запуска начинается здесь
- Если не указана запускаемая программа, то выбрать программу с создателем 'pref' - Preferences.
- Получить параметры текущей запускаемой программы из переменных PalmOS
- Запустить ее с флагами sysAppLaunchFlagNewStack | sysAppLaunchFlagNewGlobals | sysAppLaunchFlagUIApp и параметрами из системных переменных.
- Обратим внимание, что к моменту выхода из прикладной программы системные переменные могут измениться в результате вызова SysUIAppSwitch.Теперь мы имеем три набора cardNo/dbID - запомненные с предыдущего запуска, с текущего запуска и следующего запуска.
- Освободить текущий cmdPBP
- Если запуск вернул ненулевую ошибку (напомню - это не возврат PilotMain, а ошибка SysAppLaunch) и предыдущее приложение существует, то оно запускается с кодом sysAppLaunchCmdFailedAppNotify и по выходу исполнение продолжается.
- Если вызванное приложение не вызывало SysUIAppSwitch и системные переменные не изменились, то цикл выбирает следующее приложение самостоятельно
- Единственный случай, когда такая следующее приложение не изменилось - это запуск панели из программы. В этом случае возврат из панели осуществляется запуском предыдущего приложения с кодом sysAppLaunchCmdReturnFromPanel.
- Проверяется на существование приложения по умолчанию PrefGetPreference(prefLauncherAppCreator), которое и станет следующим
- Иначе следующим становится приложение с создателем 'lnch' - Launcher.
- Если же параметры следующего приложения существуют, то текущие параметры становятся предыдущими.
- Если запуск вернул ненулевую ошибку, то выдается сообщение FrmAlert(GenericLaunchErrAlert) и параметры следующего приложения обнуляются
- Переход на начало цикла
Частые вопросы
Как запускать панели?
Панели - это редкий случай возможности запуска приложения с интерфейсом и возврата обратно. Вызов панели реализуется следующим способом:
- Приложение, отвечающее за панель запускается через SysUIAppSwitch с кодом sysAppLaunchCmdPanelCalledFromApp
- Если панель вызвана с этим кодом, то вместо списка всех панелей в левом верхнем углу она рисует кнопку Done.
- Нажатие на кнопку Done попросту посылает sysEventAppStopEvent в очередь.
- Осуществляется выход в UIAppLaunch, который анализирует тип вышедшей программы 'panl' и тот факт, что параметры следующей запускаемой программы указывают на панель, запускает предыдущую программу с кодом sysAppLaunchCmdReturnFromPanel
- Прикладная программа обрабатывает код sysAppLaunchCmdReturnFromPanel совершенно аналогично “нормальному” запуску.
Что использовать - SysAppLaunch или SysUIAppSwitch?
SysAppLaunch запускает следующее приложение с возвратом обратно, а SysUIAppSwitch переключается на следующее приложение без возврата (исключение - механизм панелей). Если вам нужно запустить приложение с интерфейсом, то нужно использовать SysUIAppSwitch. Если вам нужно запросить у приложения данные, например запустить Pocket Tunes, то нужно использовать SysAppLaunch. Не пытайтесь поступать наоборот!
Какие флаги следует анализировать в PilotMain?
Все зависит от кода команды. В SDK описаны флаги, передаваемые при вызове стандартных кодов. Если же описания флагов нет, то стоит рассчитывать на худший случай.
Если мы получаем код, которые подразумевает показ UI (sysAppLaunchCmdNormalLaunch, sysAppLaunchCmdGoTo), то такой вызов был сделан с помощью SysUIAppSwitch, так что мы располагаем своим стеком и глобальными данными. Обратите внимание, что в случае с sysAppLaunchCmdGoTo есть одно исключение - подвызов с выставленным sysAppLaunchFlagSubCall. В этом случае переменные уже проинициализированы предыдущей копией программы.
Если мы получаем код, требующий только действия (например, sysAppLaunchCmdAlarmTriggered), то его нужно обрабатывать при отсутствии глобальным переменных и большого стека.
Какие флаги можно использовать при вызове SysAppLaunch?
Лучше всего не использовать никаких. Правильный принцип - посылаемая команда должна всегда передавать одни и те же флаги, не больше и не меньше. То есть для вызова с кодом sysAppLaunchCmdNormalLaunch не стоит использовать
flags ===== 0
, а для вызова sysAppLaunchCmdAlarmTriggered попросту излишне указывать
flags ===== sysAppLaunchFlagNewStack | sysAppLaunchFlagNewGlobals
, поскольку обработчик должен уметь обходиться и без глобальных переменных.
Почему нельзя запускать программы с кодом sysAppLaunchCmdNormalLaunch через SysAppLaunch?
- Обычный запуск ожидает корректно заданные графическое окружение. Это достигается указанием флага sysAppLaunchFlagUIApp. Но этот флаг сбрасывает настройки интерфейса текущего приложения после возврата в него
- Возможно переполнение числа ownerID для программ. Их число ограничено 14 и скрытые запуски программ могут их еще пооткусывать
- PalmOS < 3.5 вообще не уменьшает руками ownerID при выходе из вложенной программы
- Если вызываемое приложение вызовет FrmCloseAllForms (а такой вызов обцчно осуществляется в AppStop), то закроются ВСЕ формы, включая Ваши.
- PalmOS 5 использует кэш трансляции ресурсов. При вложенных вызовах приложений, использующих ресурсы могут начаться конфликты между кэшами.
- Наконец, PalmSource попросту не рекомендует это делать. Есть подозрение, что могут вылезти разнообразные тонкости и нюансы при таком запуске.
Если доводы вас не убедили - попробуйте запустить сами. Возможно у Вас такой запуск заработает.
Кое-какие детали такого запуска описаны здесь: http://forum.sources.ru/index.php?showtopic=47808
А как все-таки запустить калькулятор и вернуться в свое приложение?
Предлагается два неопробованных способа. Кто попробует - присылайте результаты.
- Временно прописать себя лончером. Тогда в результате выхода из запущенного приложения по нажатию на home пользователь попадет обратно в ваше приложение
- PalmOS 5 only. Повеситься на нотификацию sysNotifyAppQuittingEvent, отловить выход из другого приложения и переключиться обратно на свое.
Как можно залочить машинку для запуска одной программы?
Нужно прописать создателя программы в двух местах preferences: PrefSetPreference(prefDefaultAppCreator, 'хххх'); PrefSetPreference(prefLauncherAppCreator, 'хххх');
Первая строка будет запускать программу после ресета и в сомнительных случаях, а вторая будет запускать программу вместо лончера.
Также можно блокировать переключение на стандартные приложения по кнопкам: PrefSetPreference(prefHard1CharAppCreator , 'хххх');