FAQ для начинающих программистов на Palm v0.02

Данный FAQ (или его подобие) составлен мною в процессе моего разбирательства с платформой PalmOS. Так как я, как и все новички в этом деле, столкнулся с тотальным отсутствием информации для новичков на русском языке, я решил немного восполнить этот пробел:).

Прошу учитывать, что я не пытался осветить какие-то очень специфичные вопросы (так как я с ними и не сталкивался:) - только то, что нужно было мне самому в процессе начального освоения этой платформы. Также стоит учесть, что основное моё занятие - написание игр, поэтому я рассматривал в основном вопросы, касающиеся этой области.

Выражаю благодарность участникам форума PalmOS на http://sources.ru, именно ваши советы помогли мне так быстро освоить эту платформу:).

Отдельное спасибо за помощь в составлении FAQ: Gold (Q19, поправка к Q7), borunov (Q24).


Содержание:

1. Можно-ли писать ПО для Palm, не имея реального устройства?
2. Вставил в ресурсы программы графику для 2bpp/4bpp, а она отображается как 1bpp. Что делать?
3. А как вообще поддержать все режимы цвета?
4. Как сохранить какие-либо настройки программы без использования внешних записей?
5. Как узнать, куда я ткнул стилусом?
6. Кто украл stdlib.h?! Как-же мне получить random-число?
7. А что насчёт синхронизации программы, для вывода анимации например?
8. Хочу для пробы написать простую игрушку. Как мне выводить графику?
9. Как можно сделать простое окно, наподобие about box (с одной кнопкой)?
10. Можно-ли, имея только SDK4.0, писать программы под более младшие версии PalmOS?
11. Что за ерунда - сквозь созданную форму просвечивает предыдущая?
12. Как узнать, что юзер нажал на button, который я положил на форму?
13. Как программно установить/сбросить Checkbox?
14. Помогите! Иконка приложения в PalmOS 2.0 сползает вниз!
15. Как-бы мне клавиши опросить?
16. А как обстоят на Palm дела со звуком?
17. Что нового в PalmOS 5, почему я не нашёл ROMa с ней для POSE?
18. Какова вообще специфика разработки ПО, и в частности игр, под Palm?
19. Сконвертированная из TrueColor картинка в 8bpp-режиме выглядит ужасно!
20. Как проще всего сделать Help/Instructions?
21. Хочу вывести 8bpp картинку, а она выводится как 1bpp (проект для CodeWarrior).
22. С выводом графики понятно, а как вывести текст или число?
23. Как мне вывести картинку с "дырками" (прозрачными пикселями)?
24. Хочу вывести надписи серым цветом под PalmOS <3.5...
25. Чьи это файлы: *.mcp, *.rcp, *.rsrc, ... ?
26. Как сделать таймер или что-то подобное?
27. Как выбрать другой шрифт для вывода текста?
28. Есть-ли у каждого экземпляра Palm`а свой уникальный номер, и как мне его получить?

N. Всё прочёл, ничего не понял. Так с чего-же мне начать?!
M. Мне-бы ссылочек...




1. Можно-ли писать ПО для Palm, не имея реального устройства?

Теоретически можно. На практике, эмулятор имеет существенные недостатки. Он не позволяет оценить реальную скорость работы программы, а также её внешний вид (как она будет выглядеть на реальном LCD). Звук практически не эмулируется. Отлаживать программу, использующую прямой доступ к железу, также нельзя. Зато отлаживать программы без такого доступа удобнее именно на эмуляторе (помимо прочего, он позволяет протестировать программу на большом количестве версий PalmOS).


2. Вставил в ресурсы программы графику для 2bpp/4bpp, а она отображается как 1bpp. Что делать?

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

UInt32 old_dpt;


// старт

UInt32 sup_dpt,new_dpt,romVer;
FtrGet(sysFtrCreator,sysFtrNumROMVersion,&romVer);
if(romVer>=0x03003000)
{
if(WinScreenMode(winScreenModeGet,NULL,NULL,&old_dpt,NULL)==errNone)
{
WinScreenMode(winScreenModeGetSupportedDepths,NULL,NULL,&sup_dpt,NULL);
new_dpt=1;                                // по умолчанию - доступен 1bpp
if((sup_dpt&8)!=0) new_dpt=8;                            // доступен 8bpp
if((sup_dpt&8)==0&&(sup_dpt&2)!=0) new_dpt=2;            // доступен 2bpp
WinScreenMode(winScreenModeSet,NULL,NULL,&new_dpt,NULL);
}
} else old_dpt=16523;


// выход

if(old_dpt!=16523) WinScreenMode(winScreenModeSet,NULL,NULL,&old_dpt,NULL);
Данный код проверяет: если версия PalmOS меньше чем 3.0 - значит доступна только 1bpp графика, и режим отображения не меняется вообще. Иначе - определяется доступная глубина цвета, и выбирается максимальная. Код проверен на всех PalmOS от 2.0 до 4.0. Режим 4bpp игнорируется (легко добавить и его).


3. А как вообще поддержать все режимы цвета?

Очень просто. Вместо Bitmap нужно всюду использовать BitmapFamily. Создаём в конструкторе ресурсов BitmapFamily. В ней создаём столько обьектов, сколько цветовых режимов желаем поддержать. Скажем, мы нарисовали графику для режимов 1,2,8bpp. Создаём три обьекта. Выбираем значение Depth для первого 1bpp, для второго 2bpp, и так по возрастающей. Назначаем каждому из обьектов соответствующий bitmap. Важный момент: если нужно поддерживать PalmOS менее 3.0 - нельзя выбирать Compression более чем ScanLine. Для FormBitmap нужно будет указывать BitmapResource не Bitmap, а созданной BitmapFamily (для этого нужно дать ей ID заведомо не совпадающий с обычными Bitmap`ами). Проиллюстрировать создание BitmapFamily может созданная wizard`ом иконка приложения.


4. Как сохранить какие-либо настройки программы без использования внешних записей?

Для этого существуют функции PrefGetAppPreferences(..)/PrefSetAppPreferences(..). Обьявляем структуру, описывающую сохраняемые данные, и идентификатор записи (вместо PROG впишите нужный набор букв, 4 символа):
#define pref_IDname PROG

typedef struct {
// любые типы, массивы, и т.д. - которые надо сохранить
int tipa;
} MyPreferenceType;
При старте программы загружаем данные, либо узнаём, что их нет, и нужно установить значения по default:
MyPreferenceType prefs;
UInt16 prefSize;
Int16 prefsVersion;
prefSize=sizeof(MyPreferenceType);

// читаем preferences
prefsVersion=PrefGetAppPreferences(pref_IDname,1,&prefs,&prefSize,true);

// нашлись или нет?
if (prefsVersion!=noPreferenceFound)
{
// не нашли - заполняем prefs по умолчанию
prefs.tipa=0;
}

// прочесть из prefs уже в структуры/массивы/переменные программы
some_var=prefs.tipa;
А при выходе из программы - сохраняем:
MyPreferenceType prefs;

// заполняем поля prefs
prefs.tipa=some_var;

// сохраняем prefs
PrefSetAppPreferences (pref_IDname,1,1,&prefs,sizeof(MyPreferenceType),true);
Визард CodeWarrior делает работу за вас - в создаваемом .h файле проекта уже есть структура g_prefs (Internal structures), идентификатор вставляется автоматом, а функции AppStart и AppStop основного файла уже содержат скелеты чтения/записи preferences.

Если всё это вам нужно для хранения регистрационной информации shareware-программы, то не стоит забивать себе голову безопасностью этой информации: всё равно сломают:) Надо понимать, что человек, вводящий эту информацию, менее надёжное звено, нежели ваша программа;)


5. Как узнать, куда я ткнул стилусом?

Проанализируйте свои ощущения. Если вы ощущаете некоторый дискомфорт вследствие давления маленького тупого твёрдого предмета в области тела - значит вы ткнули в себя. Более точно место тычка укажет вмятина красноватого цвета на коже;) В противном случае вы, скорее всего, ткнули в экран Palm`а. Значит, пришёл event, указывающий на этот прискорбный факт. Точнее, есть три типа event`а, которые указывают на: опускание стилуса на экран, перемещение стилуса, отрыв стилуса от экрана:
// стилус опущен, в pen_x и pen_y получаем координаты точки
if(event.eType==penDownEvent) { pen_x=event.screenX; pen_y=event.screenY; }

// стилус перемещается, в pen_x и pen_y получаем координаты новой точки
if(event.eType==penMoveEvent) { pen_x=event.screenX; pen_y=event.screenY; }

// стилус поднят, в pen_x и pen_y получаем координаты точки отрыва
if(event.eType==penUpEvent) { pen_x=event.screenX; pen_y=event.screenY; }
Разумеется, этот код должен находиться в event loop. Все получаемые координаты - в координатах окна, не экрана. Кроме этого, возможно получение дополнительной информации - читайте Palm OS Reference.pdf, страница 89 (105).


6. Кто украл stdlib.h?! Как-же мне получить random-число?

Действительно, привычные библиотеки С при написании под PalmOS отсутствуют. Неудивительно - ведь специфика платформы совершенно другая. Random-число можно получить с помощью API-вызова SysRandom(..). А именно, так:
// таким образом можно установить начало последовательности
// это лучше всего сделать при запуске программы
SysRandom(TimGetTicks());

// а это определение полностью заменит нам привычный random(..);
#define random(N)	((((Int32)SysRandom(0))*N)/((Int32)sysRandomMax+1))

7. А что насчёт синхронизации программы, для вывода анимации например?

Довольно легко можно сделать, чтобы event`ы приходили периодически. Смотрим, как сделана синхронизация в примерах игрушек из CW8 examples, делаем нечто подобное:
// здесь мы будем хранить следующее значение времени
UInt32 nextPeriodTime;
// частота прихода event`ов, чем меньше значение - тем чаще event`ы
#define advanceTimeInterval	4


// инициализация 'таймера'

static void SyncNew(void)
{
nextPeriodTime=TimGetTicks()+advanceTimeInterval;
}


// сам 'таймер'

static Int32 SyncWait(void)
{
Int32 timeRemaining;
if(**игра на паузе**) return evtWaitForever; // можно и не делать:)
timeRemaining=nextPeriodTime - TimGetTicks();
if(timeRemaining<0) timeRemaining = 0;	
return timeRemaining;
}
Для того, чтобы воспользоваться этим кодом, нужно: вставить до начала цикла приёма event`ов вызов SyncNew();, а внутри цикла сделать так:
// вместо EvtGetEvent(&event,evtWaitForever);
EvtGetEvent(&event,SyncWait());

if(SyncWait()==0)
{
SyncNew();

// выполнить те действия, для которых всё и затевалось
// например, обновить состояние игры и экран
}
SyncNew(); должен вызываться до требуемых действий , иначе частота прихода event`ов будет плавать в зависимости от скорости их выполнения.


8. Хочу для пробы написать простую игрушку. Как мне выводить графику?

Для начала нужно оговориться, что выводить графику крайне рекомендуется стандартными средствами, не используя прямой доступ к железу (запись в видеопамять, ассемблерные вставки) - в целях обеспечения совместимости (которой, безусловно, иногда приходится жертвовать в погоне за скоростью). PalmOS предоставляет два способа вывода графики: с помощью WinDrawBitmap(..) и с помощью WinCopyRectangle(..). Оба метода имеют автоматический клиппинг для краёв. Первый метод наиболее подходит для вывода нечасто обновляющихся элементов изображения, а также для компрессированных картинок (медленнее).
MemHandle bitmapHnd;
BitmapPtr bitmapPtr;

// получаем указатель на bitmap в ресурсах
bitmapHnd=DmGetResource(bitmapRsc,**имя bitmap`а, или ID**);
ErrFatalDisplayIf(!bitmapHnd,"Missing bitmap"); // не найден такой bitmap

if(bitmapHnd) // найден, выводим (в координаты coord_X, coord_Y)
{
bitmapPtr=(BitmapPtr)MemHandleLock(bitmapHnd);
WinDrawBitmap(bitmapPtr,coord_X,coord_Y);
MemHandleUnlock(bitmapHnd);
DmReleaseResource(bitmapHnd);
}
Главный недостаток WinDrawBitmap(..) - в невозможности вывести (быстро) часть картинки. Вывод частей картинки может быть необходим при большом (от десятка и до беспредела) количестве спрайтов, которые будет очень неудобно редактировать, если хранить (и выводить, хотя с этим проще) их по одному. WinCopyRectangle(..) позволяет выводить произвольную часть картинки в нужное место экрана, но занимает место в оперативной памяти. Идея такова: при старте программы создаём внеэкранное окно (OffscreenWindow), на которое выводим наш большой bitmap со всеми спрайтами. Когда нужно вывести какую-либо его часть - используем WinCopyRectangle(..). По завершении программы освобождаем занятую память. В целом, такой подход напоминает работу с surfaces в DDraw.
// глобальный указатель на offscreen-окно
WinHandle name_surf;
// размеры будущего offscreen-окна (не стоит делать больше, чем требуется)
#define w_wdt 160
#define w_hgt 160


static void prog_start(void) // вызываем на старте программы
{
UInt16 err;
name_surf=WinCreateOffscreenWindow(w_wdt,w_hgt,genericFormat,&err);
if(err!=errNone) ErrFatalDisplayIf(true,"No memory");
WinHandle oldw=WinSetDrawWindow(name_surf);

// выводим с помощью WinDrawBitmap(..) наш Bitmap на это окно
// (как это делается - в примере выше)

WinSetDrawWindow(oldw);
}


static void prog_kill(void) // это вызываем на выходе из программы
{
WinDeleteWindow(name_surf,false);
}


static void draw_sprite(int X,int Y)
{
WinHandle drw=WinGetDrawWindow();
RectangleType rect;
// эти параметры нужно модифицировать так, как требуется - например,
// находить их по номеру требуемого спрайта
rect.extent.x=16; // ширина выводимого спрайта
rect.extent.y=16; // высота выводимого спрайта
rect.topLeft.x=0; // смещение начала спрайта по X (внутри offscreen-окна)
rect.topLeft.y=0; // смещение начала спрайта по Y (внутри offscreen-окна)
// последний параметр (winPaint) - режим вывода, см. документацию
WinCopyRectangle(name_surf,drw,&rect,X,Y,winPaint);
}
Все функции рисования примитивов и работы с окнами описаны в Palm OS Reference.pdf в разделе Windows, страница 1009 (1025). PalmOS не позволяет масштабировать выводимую графику, и не имеет средств для рисования выпуклых фигур произвольной формы (залитых цветом полигонов) - это как раз те случаи, когда может потребоваться прямой доступ к железу.


9. Как можно сделать простое окно, наподобие about box (с одной кнопкой)?

Вообще, для подобных 'окон' есть Alert`ы. Но если нужна, например, modal`ная плашка с названием программы и текстом (about), которая имеет единственную, закрывающую её кнопку - это можно сделать очень просто. Создаём эту форму в редакторе ресурсов, в программе вызываем её в нужный момент так:
FormType *frmP;
frmP=FrmInitForm(NameForm); // Name - это имя нашей формы в редакторе ресурсов
FrmDoDialog(frmP);                    
FrmDeleteForm(frmP);
Обработчик для закрытия формы писать не нужно - любой button формы, если ему не сделать обработчик, закрывает её.


10. Можно-ли, имея только SDK4.0, писать программы под более младшие версии PalmOS?

Да, безусловно. Важно лишь учитывать, что некоторых функций в младших версиях PalmOS просто нет, и избегать их использования (читайте референс, графа Compatibility у каждой из функций). Некоторые функции были переименованы - это также нужно учитывать. Также, нельзя использовать некоторые методы сжатия картинок в ресурсах. Начиная с версии 2.0 поддерживается ScanLine-сжатие, с версии 3.5 появилось RLE-сжатие, с версии 4.0 введен метод сжатия PackBits. Неплохо-бы проверить программу на всех версиях PalmOS (с помощью POSE), чтобы избежать накладок. Одна из накладок, связанная с отображением иконки приложения, проявится в PalmOS 2.0. О том, как решить эту проблему - читайте в отдельном вопросе этого FAQ. Версию ОС, при необходимости, можно узнать так:
UInt32 romVer; //в romVer будет версия. Например, 0x03003000 - 3.3
FtrGet(sysFtrCreator,sysFtrNumROMVersion,&romVer);
Для пользователей CodeWarrior: при создании проекта визард спрашивает версию ОС, под которую разрабатывается проект - нужно указать её правильной, т.к. для RomVersionCompatible(..) он берёт именно это, указанное значение.


11. Что за ерунда - сквозь созданную форму просвечивает предыдущая?

Такая проблема может возникнуть, если в редакторе формы не поставить галку на 'Save behind'. В PalmOS 3.5 и выше форма будет выводиться нормально, а в более младших её фон будет считаться прозрачным, и предыдущее изображение будет 'просвечивать'.


12. Как узнать, что юзер нажал на button, который я положил на форму?

Узнать о нажатии на button, или любой другой control можно по пришедшему событию ctlSelectEvent, в функции FormHandleEvent (CW):
case ctlSelectEvent: // пришло событие выбора control`а

// определяем, какой из control`ов был нажат

switch (eventP->data.ctlSelect.controlID)
{
case FormOneButton:
// была нажата кнопка с именем One, на форме с именем Form
handled=true;
break;

case FormTwoCheckbox:
// был нажат checkbox с именем Two, на форме с именем Form
handled=true;
break;

default:
break;
}
О том, каким образом получаются имена control`ов в CodeWarrior - смотрите следующий вопрос.


13. Как программно установить/сбросить Checkbox?

Установить/сбросить галку на CheckBox, а также изменить значения других control`ов можно функцией FrmSetControlValue(..). Пример:
FormType *frmP=FrmGetActiveForm(); // считаем, что checkbox на текущей форме
UInt16 controlID=FrmGetObjectIndex(frmP,MainMyCheckbox); // узнаём ID control`а
FrmSetControlValue(frmP,controlID,0); // сбрасываем галку (1 для установки)
MainMyCheckbox - имя control`а. Редактор ресурсов CodeWarrior`а генерирует его следующим образом. Сначала идёт имя формы (Main). Потом имя, указанное при создании control`а (автоматом назначается UnnamedNNNN, в примере - My). Последним идёт тип control`а (в нашем случае - Checkbox).


14. Помогите! Иконка приложения в PalmOS 2.0 сползает вниз!

Эта проблема целый день морочила мне голову, пока в Palm OS Companion.pdf на странице 12 (26) я не обнаружил заметку следующего содержания: "For compatibility with 2.0 devices, this icon should be 22x32 pixels; if your application only runs on older devices, you can make this icon 22x22 pixels.". Иными словами, для решения проблемы следует 'большую' иконку (22х22) сделать размерами 22х32 - лишние десять пикселей должны быть внизу, пустыми. После этого иконка будет корректно отображаться во всех версиях PalmOS.


15. Как-бы мне клавиши опросить?

По нажатию любых клавиш (включая и Power) приходит событие keyDownEvent. С помощью функции KeyCurrentState() можно узнать, какие из клавиш нажаты в момент вызова функции:
if(event.eType == keyDownEvent)
{
UInt32 key_state=KeyCurrentState();

if(key_state & keyBitPower)    // Power key
if(key_state & keyBitPageUp)   // Page-up
if(key_state & keyBitPageDown) // Page-down
if(key_state & keyBitHard1)    // App #1
if(key_state & keyBitHard2)    // App #2
if(key_state & keyBitHard3)    // App #3
if(key_state & keyBitHard4)    // App #4
if(key_state & keyBitCradle)   // Button on cradle
if(key_state & keyBitAntenna)  // Antenna "key" 
if(key_state & keyBitContrast) // Contrast key

if(key_state & keyBitsAll)     // any key
}
Чтобы уменьшить количество ненужных event`ов (и сэкономить ресурсы процессора, как пишут в документации:), с версии PalmOS 2.0 можно замаскировать неиспользуемые клавиши - они не будут генерировать keyDownEvent. По завершении программы маску следует снять:
UInt32 new_mask=keyBitHard1 | keyBitHard4; // event`ы только от App #1 и App#4
UInt32 old_mask=KeySetMask(new_mask);

// восстановить маску можно и используя old_mask, и так:
KeySetMask(keyBitsAll);
Возможно, для игровых задач опрашивать клавиши будет удобнее не по keyDownEvent, а по таймеру - в момент отрисовки экрана. В этом случае стоит установить скорость автоповтора клавиш на минимум. Старую скорость следует запомнить, и восстановить на выходе из программы:
// узнаём старые значения

UInt16 old_delay,old_period,old_dtap;
bool old_queue;
KeyRates(false,&old_delay,&old_period,&old_dtap,&old_queue);

// устанавливаем новые значения

UInt16 initDelay=slowestKeyDelayRate; // минимальная задержка после нажатия
UInt16 period=slowestKeyPeriodRate; // минимальная скорость повтора
UInt16 queueAhead=false;
KeyRates(true,&initDelay,&period,&period,&queueAhead)

// восстанавливаем старые значения

KeyRates(true,&old_delay,&old_period,&old_dtap,&old_queue);
В любом случае, крайне неудобно будет то, что по клавишам App #N будет происходить переключение на различные приложения:) Чтобы избавиться от этого, нужно незамедлительно прерывать обработку event`а, если это был keyDownEvent от любой из опрашиваемых клавиш (просто continue; в цикле обработки event`ов).


16. А как обстоят на Palm дела со звуком?

Примерно как на PC-XT или Spectrum 48:) Т.е. есть только один канал, который может генерировать square-tone, с заданной частотой, длительностью и громкостью. Простые звуки можно извлекать так:
SndCommandType sndCmd;

sndCmd.cmd=sndCmdFrqOn; // только для PalmOS 3.0 и выше!
sndCmd.param1=440; // частота в герцах
sndCmd.param2=100; // длительность в миллисекундах
sndCmd.param3=10;  // громкость, от 0 до sndMaxAmp

SndDoCmd(NULL,&sndCmd,0);
Следует учитывать, что для версий PalmOS ниже 3.0 доступна только команда sndCmdFreqDurationAmp (её параметры аналогичны sndCmdFrqOn). Она работает только синхронно (прерывает программу). sndCmdFrqOn и sndCmdNoteOn (аналогично sndCmdFrqOn, но param1 - номер midi-клавиши) работают асинхронно. Но на POSE правильно услышать результаты их работы невозможно - sndCmdFrqOn работает синхронно с неустранимой дополнительной задержкой после звука; sndCmdNoteOn не работает вообще.

В случае, если нужно проигрывать звук для игр - нужно использовать в качестве максимального значения громкости prefGameSoundVolume (отдельная настройка для громкости в играх). Получить это значение можно так:
UInt16 GameAmp=PrefGetPreference(prefGameSoundVolume);
Функции операционной системы позволяют играть лишь простые звуки, либо - с версии PalmOS 3.5 - стандартные (хотя это ещё вопрос;) midi-файлы (*.smf). Последние представляют собой просто одноголосые мелодии, записанные в определённом формате. Подробнее про работу со звуком можно прочесть в Palm OS reference.pdf, страница 765 (781). Добиться полифонии на Palm можно лишь псевдомногоголосием (быстро меняя частоты и громкости), либо используя звуковой канал в роли DAC (таким образом можно сделать проигрывание wave-файлов).


17. Что нового в PalmOS 5, почему я не нашёл ROMa с ней для POSE?

POSE не поддерживает, и не может поддерживать PalmOS 5, так как она использует совершенно иную, новую аппаратную базу, и - как следствие - является несовместимой с предыдущими версиями PalmOS. В новых моделях Palm, рассчитаных на PalmOS 5, применяются процессоры типа Intel Strong ARM, Texas Instruments OMAP, Motorola Dragonball MX. Все они работают на частотах около 200 мегагерц. Возможность запускать программы, созданные для старых версий PalmOS, всё-же есть - для этого используется, по сути, эмулятор младших Palm - PACE (Palm Application Compatibility Environment). Для отладки ПО под PalmOS 5, насколько я знаю, существует PalmOS 5 Simulator. Его, и информацию по разработке под PalmOS 5, ищите на http://palmos.com.


18. Какова вообще специфика разработки ПО, и в частности игр, под Palm?

Она вытекает из технических возможностей этой платформы. Это, прежде всего, процессор, совместимый с Motorola 68000 (использовался в огромном количестве компьютеров и игровых платформ 90-х годов). В худшем случае на частоте 16mhz, в лучшем - 33mhz. Для сравнения, в Sega Genesis, младших Amiga, младших AtariST процессор работал на частоте 6-8mhz. Но динамичная графика там обеспечивалась не столько процессором, сколько мощными видеоконтроллерами. В целом, по вычислительной мощности (учитывая то, что программы пишутся на языках высокого уровня) Palm можно сравнить с 386-486SX PC. Для логических игр мощности вполне хватает, чтобы не писать их на ассемблере. Для динамических игрушек-же практически не обойтись без прямой работы с железом (и вытекающих отсюда проблем с совместимостью), и написания всей или части программы на ассемблере. Зато подобные средства позволяют добиваться неплохих результатов (до 30-40fps во всяких бегалках-леталках), хотя и не таких, как на тех-же видеоприставках.

Что касается оперативной памяти, то её использование несколько отличается от других платформ. Ввиду отсутствия необходимости загружать файлы с носителя в оперативную память снижаются требования программ к её обьёму (который, впрочем и невелик:). Не стоит пугаться малого обьёма - его вполне достаточно, если использовать его разумно. Некоторое кажущееся неудобство может доставить сегментация, с ограничением на размер сегмента 64KB. Я не считаю наличие этого ограничения проблемой - под MS DOS на PC оно также присутствовало, что не помешало появлению огромного числа программ и игр.

LCD-экран Palm также вносит определённую специфику в разработке. Во-первых, нужно учитывать все варианты отображения графики - режимы 1bpp, 2bpp, 4bpp, 8bpp и выше (иначе на некоторых устройствах программой невозможно будет воспользоваться). Во-вторых, малое разрешение и пропорции экрана 1:1 (160х160 пикселей, 320х320 в последних моделях). Как и у всех LCD-устройств, у Palm имеется такое явление, как большая инерционность экрана (в основном, у старых монохромных моделей). Это создаёт определённые проблемы при написании игр с динамичной графикой.

Наиболее яркая особенность Palm - это стилус. Не стоит рассматривать его как 'однокнопочную мышь', это совсем иное. Главное отличие от мыши - невозможность получить координаты стилуса, если он не опущен на экран (т.е. подсветку пунктов меню или всплывающие подсказки под стилусом не сделать, в отличии от курсора мыши;). Также, фиксированная, а не относительная система координат - это значит, что заменить стилусом мышь в Quake-подобной игре не получится.

Последняя особенность Palm`а - звук. Со звуком тут бедно (не в пример handheld-игровым консолям). Имеется только один канал, square-tone. Можно задавать частоту, длительность и громкость звука. Тем не менее, при желании (и, скорее всего, прямой работе с железом) можно добиться неплохих результатов, как-то: проигрывать wave-файлы, mod-музыку, либо синтезированную псевдомногоголосную (за счёт быстрой смены частот/громкости). Но, скорее всего, асинхронно играть звук не получится (да и загрузка процессора немалая).

В целом, Palm - достаточно неплохая по возможностям платформа, позволяющая создавать игры технически не худшие, чем на карманных приставках типа GameBoy, GameGear, NeoGeo Pocket.


19. Сконвертированная из TrueColor картинка в 8bpp-режиме выглядит ужасно!

Чтобы уменьшить искажения цвета при вставке цветного изображения в ресурсы для Palm-программы, нужно заранее сконвертировать его в 256-цветное, используя default-палитру PalmOS. По умолчанию PalmOS использует палитру, соответствующаю стандартной web-palette с небольшим расширением. Эта палитра представляет собой все сочетания из 6 градаций яркости каждой составляющей (R,G,B). Расширение - это еще 10 добавочных градаций серого.

Далеко не каждая картинка сможет сконвертироваться более-менее приемлимо, поэтому лучше изначально рисовать графику в цветах этой палитры. Если-же требуется перевести в 8bpp, к примеру, фотографию, и сохранить её 'родную' палитру - стоит воспользоваться возможностями, появившимися в PalmOS 3.5 (и выше) - заданием произвольной палитры для окна. Это делает функция WinPalette(..). Установить текущему окну палитру выбранного bitmap`а можно так (pBmp - указатель на bitmap):
RGBColorType *pCTable=ColorTableEntries(BmpGetColortable(pBmp));  
WinPalette(winPaletteSet,0,BmpColortableSize(pBmp),pCTable);  
Подробности читайте в Palm OS Reference.pdf, страница 1059 (1075).


20. Как проще всего сделать Help/Instructions?

Для этого имеется функция FrmHelp(..);. Создаём в ресурсах String, содержащую текст (можно использовать переводы строк, по необходимости). Когда нужно отобразить текст - вызываем FrmHelp(**идентификатор строки**);. Текст будет показан в modal`ном окне с названием Tips и кнопкой Done для выхода. Если текст не помещается в окне - появятся также кнопки для его прокрутки. При выводе автоматически выполняется перенос по словам.


21. Хочу вывести 8bpp картинку, а она выводится как 1bpp (проект для CodeWarrior).

Для редактора ресурсов, входящего в CodeWarrior, необходимо создавать BitmapFamily для любых картинок, содержащих более 2-х цветов (т.к. редактор ресурсов не позволяет задавать одиночным Bitmap`ам значение глубины цвета). Т.е., даже если требуется хранить всего-лишь один Bitmap с глубиной цвета 8bpp - придётся создать для него BitmapFamily, иначе он будет отображаться как 1bpp. При создании окна нужно не забыть установить для него требуемую глубину цвета (см. вопрос номер 2).


22. С выводом графики понятно, а как вывести текст или число?

Для вывода текстовых строк предназначена функция WinDrawChars(const char *chars, Int16 len, Coord x,Coord Y);. Её параметры: *chars - указатель на строку; len - длина строки (функция выводит только указанное количество символов, не использует нулевой терминатор); x,y - координаты в текущем окне. Текст будет выведен в текущее окно, установленными для этого окна цветами textcolor и backcolor. Подробности на стр. 1030 (1046) Palm OS Reference.pdf. Для выяснения длины выводимой строки можно воспользоваться функцией StrLen(const Char *string);.
const char *text="Test string"; // текст для вывода
WinDrawChars(text,StrLen(text),0,0); // вывести в левый верхний угол окна
Текст можно выводить также инвертированными цветами - функция WinDrawInvertedChars(...); и с подчёркиванием - WinSetUnderlineMode(...). В качестве её параметра можно передавать: noUnderline, grayUnderline, solidUnderline, colorUnderline (не подчёркивать, подчёркивать прерывистой линией, полной линией, линией с цветом foreground color окна).

Специальных функций для вывода чисел не предусмотрено, однако есть функции конвертации числа в строку, hex-строку, и строки в число. Это функции StrIToA, StrIToH, StrAToI - подробности ищите на странице 810 (826) Palm OS Reference.pdf. Пример:
char numstr[20]; // строка для хранения числа, заведомо больше количества разрядов
char *numptr=numstr; // получаем указатель на строку
numptr=StrIToA(text,12345); // переводим число в строку (в примере - число 12345)
WinDrawChars(numptr,StrLen(numptr),0,0); // выводим число

23. Как мне вывести картинку с "дырками" (прозрачными пикселями)?

Наиболее универсальный (и наименее удобный) способ - работающий во всех версиях PalmOS - раздельное хранение маски (силуэта) и собственно изображения. Сначала устанавливаем операцию стирания - WinSetDrawMode(winErase); - и выводим маску; потом устанавливаем операцию наложения - WinSetMode(winOverlay); - и выводим само изображение. Подобный подход применяется в example-игрушках, идущих в поставке CodeWarrior`а (SubHunt, например - для изображения взрыва).

Другой вариант - использование возможности задавать с помощью конструктора ресурсов прозрачный цвет для 8bpp Bitmap`ов. Выводить такие Bitmap`ы нужно с помощью WinDrawBitmap (не с помощью WinCopyRectangle, т.к. он копирует уже выведенную графику, и не знает про прозрачные цвета). Данный способ применим в PalmOS 3.5 и выше.


24. Хочу вывести надписи серым цветом под PalmOS <3.5...

Прямой возможности для этого не существует. Сама по себе поддержка серого цвета появилась в PalmOS 3.0, однако возможности для его полноценного использования были реализованы лишь в версии PalmOS 3.5.

Для использования серого цвета в ОС 3.0..3.4X нужно устанавливать экран в требуемый режим с помощью WinScreenMode(..) (несмотря на предупреждение в документации, она работает начиная с ОС 3.0 - т.к. ранее она называлась ScrDisplayMode и использовала тот-же trap). Для установки требуемых цветов возможно использовать недокументированную функцию WinSetColors (newForeRGB, oldForeRGB, newBackRGB, oldBackRGB). При этом возможны различные проблемы, связанные с некорректной обработкой цвета фона.


25. Чьи это файлы: *.mcp, *.rcp, *.rsrc, ... ?

*.mcp - Metrowerks Codewarrior Project (проект).
*.rcp - PilRC, Pilot Resource Compiler (ресурсы).
*.rsrc - Constructor for PalmOS (ресурсы).


26. Как сделать таймер или что-то подобное?

Прямой возможности для этого не существует. Но есть две неявных возможности. Первая - заставить систему через определённые промежутки времени генерировать nilEvent:
static void EventLoop(void) 
{ 
Word err; 
EventType event; 

UInt16 ticks_for_wait=SysTicksPerSecond(); // период равен секунде
// если нужно сделать период равным 0.1 секунды - делим значение на 10, и т.п.
 
do { 
     EvtGetEvent(&event,ticks_for_wait); // вместо evtWaitForever
      if(!SysHandleEvent (&event)) 
       if(!MenuHandleEvent (NULL,&event,&err)) 
        if(!ApplicationHandleEvent(&event)) 
         FrmDispatchEvent(&event); 
   } while(event.eType!=appStopEvent); 
}
В обработчике событий нужно будет добавить case nilEvent:. Этот метод был использован в вопросе номер 7.

Другой путь реализации таймера - использование Alarm Manager`а. Все подробности работы с ним можно узнать из Palm OS Companion.pdf, стр. 261 (275), в разделе "Alarms".


27. Как выбрать другой шрифт для вывода текста?

Для выбора шрифта служит функция FntSetFont(FontID);. В качестве FontID можно использовать следующие имена, определённые для стандартных шрифтов:

stdFont - обычный шрифт;
boldFont - жирный шрифт;
largeFont - крупный шрифт (только для японских моделей Palm);
largeBoldFont - крупный жирный шрифт (только для PalmOS 3.0 и выше);

Выбор несуществующего шрифта для PalmOS младше 3.0 выбор несуществующего шрифта приведёт к зависанию/глюкам; в 3.0 и выше автоматически будет выбираться stdFont. Также, начиная с версии PalmOS 3.0, можно использовать собственные шрифты. Для этого предназначены функции FontSelect(..) - диалог выбора нужного шрифта; и FntDefineFont(..) - определение нового шрифта. О последней можно узнать подробнее на стр. 646 (662) Palm OS Reference.pdf.


28. Есть-ли у каждого экземпляра Palm`а свой уникальный номер, и как мне его получить?

Уникальный номер для каждого устройства (серийный номер программно) Документация - раздел System Features (Retrieving the ROM Serial Number) Palm OS Companion.pdf, стр. 304. Там-же пример. В Palm OS Companion.pdf в разделе System Features, подразделе Retrieving the ROM Serial Number (стр. 304) сказано: "Некоторые модели Palm`ов, начиная с Palm III, имеют 12-значный серийный номер, уникальный для каждого устройства (ранние модели не имеют такого номера).". Там-же есть и пример получения серийного номера. Вот его сокращённый (без комментариев о принципе получения контрольной суммы) вариант:
// x,y - координаты сообщения на экране
// noNumberMessage - cтрока с сообщением об отсутствии серийного номера

static void DrawSerialNumOrMessage(Int16 x,Int16 y,char *noNumberMessage)
{
char *bufP; UInt16 *bufLen; Err retval;
Int16 count; UInt8 checkSum; char checksumStr[2];

retval=SysGetROMToken(0,sysROMTokenSnum,(UInt8**)&bufP,&bufLen);

if((!retval)&&(bufP)&&((UInt8)*bufP!=0xFF)) // правильный номер
{
checkSum=0;
for(count=0;count>7); }
checkSum=((checkSum>>4)&0x0F)+(checkSum&0x0F)+2;

WinDrawChars(bufP,bufLen,x,y); // вывели номер
x+=FntCharsWidth(bufP,bufLen);

checksumStr[0]='-';
checksumStr[1]=((checkSum<10)?(checkSum+'0'):(checkSum-10+'A'));
WinDrawChars(checksumStr,2,x,y,); // вывели контрольную сумму
}
else // нет серийного номера
if(noNumberMessage)
WinDrawChars(noNumberMessage,StrLen(noNumberMessage),x,y);
}
Не забывайте, что функция SysGetROMToken существует только в версиях PalmOS 3.0 и выше.

Для модели Visor получение серийного номера невозможно (хотя SysGetROMToken и работает) - вместо номера он возвращает строку из 12 байт со значением 0xFF.



N. Всё прочёл, ничего не понял. Так с чего-же мне начать?!

Для начала нужно определиться с инструментарием. Выбор есть - кросс-компиляторы C\C++: PRC-Tools, Falch.net, CodeWarrior for PalmOS (бесплатный только первый, он-же самый сложный в освоении); кроссассемблер PilA. Далее, понадобится SDK под нужную версию PalmOS, эмулятор POSE, образы ROM с нужными версиями PalmOS. Желательно, но не обязательно, иметь под рукой как можно больше различных моделей реального Palm (особенно при работе с железом напрямую). Необходимая документация входит в состав упомянутых пакетов, также она доступна на сайте разработчика PalmOS. Все нужные ссылки можно найти в конце данного FAQ (кроме ссылок на ROMs - ищите их сами, либо скачайте с помощью POSE и линк-кабеля с реального Palm).

Чтобы освоить PalmOS, рекомендую хорошенько вникнуть в примеры, прилагающиеся к компиляторам, почитать документацию (она на английском, но довольно подробная). Наиболее часто открываемый мною файл - Palm OS Reference.pdf, API-рефренс PalmOS`а. PDF для рефренса, конечно, не очень удобен (медленно работает поиск, нет быстрого алфавитного указателя), но существует его *.chm-версия (идёт вместе с Falch.net). При наличии должного энтузиазма, нормального знания языка (и программирования, и английского:) и опыта работы с другими платформами достаточно двух-трёх дней, чтобы понять основы и начать писать простые и не очень простые программы.


M. Мне-бы ссылочек...

Англоязычные ресурсы:

http://palmos.com - сайт разработчиков PalmOS. Здесь-же можно скачать SDK, POSE, документацию.
http://metrowerks.com - разработчики пакета CodeWarrior for PalmOS.
http://codewarrioru.com - сайт, посвящённый пакету CW. В разделе 'PalmOS' есть неплохая серия уроков.
http://falch.net - собственно, сайт Falch.net.
http://prc-tools.sourceforge.net - а здесь живёт PRC-Tools.

Русскоязычные ресурсы:

http://enlight.ru/pilot - FAQ по общим вопросам о Palm, форум, и клуб Питерских пользователей Palm.
http://palmos.sources.ru - различные исходники (в основном под prc-tools), советы. Можно скачать SDK 3.5, 4.0.
http://sources.ru - здесь есть форум, посвящённый программированию под PalmOS.
http://palmware.ru - разный софт для Palm.
http://palmq.ru - "всё самое интересное о карманных компьютерах Palm".
http://palmclub.ru - Московский Palm-клуб.
http://palm.opennet.ru - 'Palm для админа'. Всякое разное, форум.
http://starostin.palmclub.ru - проект 'Йцукен'. Внешние устройства для КПК своими руками.
http://artem-sv30.narod.ru - несколько статей по Palm-программированию, в частности - 'IR-порт в играх'.


Written by Shiru Otaku^IIpr, last updated 28'03'2003.
mailto: shiru at mail dot ru