Устройство и функционирование Shared Library

Устройство и функционирование Shared Library

12.11.2002

Автор: Ронин Виктор
Источник: ladoshki.com

В
большинстве операционных систем есть необходимость создания динамически
линкующихся библиотек, так как это дает возможность убрать дублирование
кода между программами. В PalmOS для этой цели служат Shared Library.
Дальше в этой статье пойдет речь о организации и функционирование
Shared Librarys.

Перед тем, как перейти непосредственно к рассмотрению устройства
Shared Library, сделаем небольшой экскурс в структуру PalmOS, на уровне
ассемблерного кода. Вызов любой API функции в ассемблерном виде
выглядит так.

trap #15
SYSTEM_TRAP_NUMBER

Естественно, на месте SYSTEM_TRAP_NUMBER стоит номер какой-то
системной функции. В Palm’е они начинаются с номера 0xA000.
Когда процессор обнаруживает команду trap #15, то вызывается
прерывание #15. Обработчик данного прерывания, выбирает из
таблицы адрес соответствующего TRAP’а и переходит по выбранному
адресу.

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

Для каждой новой библиотеки система не может выделять новые номера
trap’ов и прописывать их обработчики. Это связано с тем,
что в таком случае таблица обработчиков trap’ов должна была бы
динамически изменяться, программы использующие Lib’ы должны были
динамически подставлять номер trap’ов, который им надо вызвать и д.р.
Как результат для библиотек были выделены фиктивные trap’ы начинающиеся
с 0xA800. Фиктивными они являются потому, что, на самом деле, адреса
обработчиков функций Lib’ы не хранят в той же таблице, что и адреса
обработчиков функций API, а также из-за того, что всем trap’ам с
номером большим 0xA800 присвоен один единственный обработчик, который
dispatch’ит вызов нужной библиотеке и нужной функции.

Для того, чтобы полностью разобраться как работает библиотека
рассмотрим полностью ее цикл жизни.

Начинается он с вызова
функции SysLibLoad, которая ищет соответствующую .prс, и если
prc найдена, то открывает в ней ’libr’ ресурс и передает
ему управления вместе с параметрами refNum и SysLibTblEntryPtr.
В системе храниться таблица для всех загруженных Shared Librarys,
которая индексируется по refNum и состоит из SysLibTblEntryType.
Так вот, функция SysLibLoad, а точнее функции SysLibInstall,
которую она вызывает, находит в таблице свободный refNum,
инициализирует начальными значениями структуру в этой таблице. После
чего refNum и указатель на структуру(SysLibTblEntryPtr) передаются параметрами в функцию, которая является точкой входа (Entry Point) в
библиотеку.

Вот стандартная Entry Point для функции:

Err SampleLibInstall(UInt16 refNum, SysLibTblEntryPtr entryP)
{
// Install pointer to our dispatch table
entryP->dispatchTblP = (MemPtr*)SampleLibDispatchTable();

// Initialize globals pointer to zero (we will set up our library
// globals in the library «open» call).
entryP->globalsP = 0;

return 0;
}

Кстати, исходники примера Shared Library
располагаются в примерах SDK 4.0. И поставляются вместе с
CodeWarrior’ом 8.0. Лежат они в директории ...CodeWarrior(CodeWarrior
Examples)Palm OS 4.0 SDKExamplesSampleLib

Собственно самая интересная сторока :
entryP->dispatchTblP = (MemPtr*)SampleLibDispatchTable();
Она устанавливает таблицу обработчиков каждой функции внутри библиотеки.

Функция SampleLibDispatchTable возвращает
таблицу dispatch’а функций внутри Lib’ы. Таблица представляет собой
массив 2-х байтных указателей (относительных) на обработчики функций. Она
оформляется в виде ASM’овской функции, для того чтобы она не
производила никаких действий над стеком. На самом деле 2-х байтные
указатели указывают не на сам обработчик, а на JMP на обработчик, так
как команде DC.W нельзя дать в виде аргумента функцию.

static MemPtr	asm SampleLibDispatchTable(void)
{
LEA @Table, A0 // table ptr
RTS // exit with it

@Table:
// Offset to library name
DC.W @Name

//
// Library function dispatch entries
//
// ***IMPORTANT***
// The index parameters passed to the macro libDispatchEntry
// must be numbered consecutively, beginning with zero.
//
// The hard-wired values need to be used for offsets because the MW SDK tools do
// not support label subtraction.
//

// Standard traps
DC.W libDispatchEntry(0) // Open
DC.W libDispatchEntry(1) // Close
DC.W libDispatchEntry(2) // Sleep
DC.W libDispatchEntry(3) // Wake

// Start of the Custom traps
DC.W libDispatchEntry(4) // GetLibAPIVersion
DC.W libDispatchEntry(5) // SetCornerDiameter
DC.W libDispatchEntry(6) // GetCornerDiameter
DC.W libDispatchEntry(7) // DrawRectangle

// Standard library function handlers
@GotoOpen:
JMP SampleLibOpen
@GotoClose:
JMP SampleLibClose
@GotoSleep:
JMP SampleLibSleep
@GotoWake:
JMP SampleLibWake

// Customer library function handlers
@GotoGetLibAPIVersion:
JMP SampleLibGetLibAPIVersion
@GotoSetCornerDiameter:
JMP SampleLibSetCornerDiameter
@GotoGetCornerDiameter:
JMP SampleLibGetCornerDiameter
@GotoDrawRectangle:
JMP SampleLibDrawRectangle

@Name:
// This name identifies the library. Apps
DC.B sampleLibName
}

При любом вызове этой функции библиотеки из программы, будет вызван
trap 0xA8xx, где xx какое-то число. Управление получит обработчик trap
#15 он его передаст dispatch’еру вызовов к Shared Librarys (так как
trap > 0xA800). Далее этот dispatch’ер по refNum’у получит
SysLibTblEntryPtr из таблицы Shared Librarys. RefNum он получает из
стека, так как каждой функции из Shared Library первым параметром
должен передаваться ее refNum. После получения SysLibTblEntryPtr
dispatch’ер берет из него таблицу dispatch’а внутри библиотеки, и
получив разницу между номером Trap’а который был вызван и 0xA800
извлекает из этой таблицы адрес функции в библиотеки, после чего
передает ей управление.

По окончанию работы с библиотекой вызывает функция SysLibRemove,
которая отчищает запись в таблице Shared Librarys, соответствующую
переданному refNum’у.

Одно из очень важных замечаний. В библиотеки
обычно объявляется enum в котором хранятся номера Trap’ов, которые
должны использоваться для каждой функции

typedef enum {
sampleLibTrapGetLibAPIVersion = sysLibTrapCustom,
sampleLibTrapSetCornerDiameter,
sampleLibTrapGetCornerDiameter,
sampleLibTrapDrawRectangle,

sampleLibTrapLast
} SampleLibTrapNumberEnum;

extern Err SampleLibOpen(UInt16 refNum, UInt32 * clientContextP)
SAMPLE_LIB_TRAP(sysLibTrapOpen);

Причем эти значения используются при объявление
функций, чтобы при компиляции программы использующей Lib’у при вызове
функции Lib’ы подставлял нужный Trap. Так вот, очень важно проследить,
чтобы DC.W и JMP на функции в SampleLibDispatchTable были объявлены В
ТОМ ЖЕ ПОРЯДКЕ, что и в enum’е. Если вы перепутаете его, то управление
получит другая функции, для которой может быть слишком много параметров
или слишком мало... как результат будет много проблем и вероятней всего
Fatal Alert.

Еще одна проблема на которую я натолкнулся.
Это, то, что однажды я в JMP вместо названия функций написал элементы
enum’а, после чего естественно оно глючило при работе, однако не выдало
никаких ошибок при компиляции и линковке.

Еще одна особенность, то что функции Shared
Library не имею доступа к глобальным переменным обычным путем, а также
по в обычном виде Shared Library должна занимать 1 segment. Для работы
с глобальными переменными в Shared Library предусмотрено специальное
поле в SysLibTblEntryPtr. Оно называется globalsP. И в любой функции
его можно использовать, использовав функцию SysLibTblEntry(refNum), и
получив таким образом SysLibTblEntryPtr. Перед использованием globalsP
должен быть инициализирован (выделена память), после окончания
использования необходимо высвободить память.

Ну и напоследок пару слов некоторых define’ах в Lib’ах.

#ifdef BUILDING_SAMPLE_LIB
#define SAMPLE_LIB_TRAP(trapNum)
#else
#define SAMPLE_LIB_TRAP(trapNum) SYS_TRAP(trapNum)
#endif

тот define объявлен для того, чтобы при использование из программы функции библиотеки вызывались через trap’ы, а при использование внутри lib’ы они использовались обычные вызовы. Это осуществляется тем, что в библиотеке defin’ится BUILDING_SAMPLE_LIB. И второй define :

	#define SampleLibInstall		__Startup__

С помощью него обозначается какая функция будет
точкой входа, так как по умолчанию точкой входа считается функция с
названием __Startup__.