Динамическое руководство по проектированию библиотеки
Динамические библиотеки, в дополнение к группировке общей функциональности, справка сокращает время запуска приложения. Однако, когда разработано неправильно, динамические библиотеки могут ухудшить производительность своих клиентов. (Динамический клиент библиотеки является приложением или библиотекой, или соединяющейся с библиотекой или загружающей библиотеку во время выполнения. Этот документ также использует изображение слова, чтобы относиться к клиентам динамической библиотеки.) Поэтому прежде, чем создать динамическую библиотеку, необходимо определить ее цель и ее надлежащее использование. Разработка маленького и эффективного интерфейса к функциональности библиотеки имеет большое значение для упрощения ее принятия в других библиотеках или приложениях.
Эта статья решает основные проблемы, к которым динамические разработчики библиотеки обращенным при разработке и реализации динамических библиотек. Фокус этой статьи должен показать Вам, как разработать библиотеки в пути, упрощающем их улучшение через версии и упрощающем для пользователей библиотек правильно взаимодействовать с библиотекой.
Разработка оптимальной динамической библиотеки
Динамические библиотеки содержат код, который может быть совместно использован многократными приложениями в компьютере пользователя. Поэтому они должны содержать код, который могут использовать несколько приложений. Они не должны содержать код, определенный для одного приложения. Это атрибуты оптимальной динамической библиотеки:
Фокусируемый: библиотека должна фокусироваться на немногих, высоко связанных целях. Высоко фокусируемую библиотеку проще реализовать и использовать, чем многоцелевая библиотека.
Простой в использовании: интерфейс библиотеки, символы, что клиенты использования библиотеки для взаимодействия с ним, должен быть немногими и простой понять. Простой интерфейс позволяет пользователям библиотеки понимать свою функциональность быстрее, чем они могли понять большой интерфейс.
Простой поддержать: разработчики библиотеки должны быть в состоянии внести изменения в библиотеку, улучшающие ее производительность и добавляющие опции. Ясное разделение между закрытыми и открытыми интерфейсами библиотеки дает свободу разработчиков библиотеки внести глубокие изменения во внутренние работы библиотеки с минимальным влиянием на его клиенты. Когда разработано должным образом, клиент создал с ранней версией библиотеки, может использовать последнюю версию неизменной библиотеки и получить преимущества от улучшений, которые это обеспечивает.
Управление клиентской совместимостью с зависимыми библиотеками
Клиент динамической библиотеки может пользоваться библиотекой двумя способами: как зависимая библиотека или как загруженная временем выполнения библиотека. Зависимая библиотека, с точки зрения клиента, является динамической библиотекой, с которой соединяется клиент. Зависимые библиотеки загружаются в тот же процесс, в который загружается клиент как часть его процесса загрузки. Например, когда приложение запускается, его зависимые библиотеки загружаются как часть процесса запуска, прежде чем будет выполнена основная функция. Когда динамическая библиотека загружается в рабочий процесс, его зависимые библиотеки загружаются в процесс, прежде чем управление будет передано к подпрограмме, открывшей библиотеку.
Время выполнения загрузилось, библиотека является динамической библиотекой, которую клиент открывает с dlopen(3) OS X Developer Tools Manual Page
функция. Клиенты не включают загруженные библиотеки времени выполнения в свою строку ссылки. Когда клиент загружается, динамический загрузчик, поэтому, не открывает эти библиотеки. Клиенты открываются, время выполнения загрузило библиотеку, когда они собираются использовать символ, это экспортирует. У клиентов нет неопределенных внешних ссылок на символы в загруженных библиотеках времени выполнения. Клиенты получают адрес всех символов, в которых они нуждаются от загруженных библиотек времени выполнения путем вызова dlsym(3) OS X Developer Tools Manual Page
функция с именем символа.
Клиент должен всегда быть совместим с его зависимыми библиотеками. Иначе, приложение не запускается, или время выполнения загрузило сбои библиотеки для загрузки.
При разработке динамической библиотеки Вам, вероятно, придется рассмотреть ее текущее техническое обслуживание. Когда-то Вам, вероятно, придется внести изменения в библиотеку, чтобы реализовать новые опции или исправить проблемы. Но также необходимо думать о существующих клиентах библиотеки. Существует два типа версий, которые можно сделать к библиотеке: версии, которые совместимы с текущими клиентами и не требуют никакого вмешательства разработчика клиента и версий, требующих, чтобы клиенты были соединены с новой версией. Прежний - незначительные версии, и последние являются главными версиями.
Следующие разделы исследуют проблемы совместимости для рассмотрения прежде, чем реализовать библиотеку, которая, возможно, должна быть обновлена в более позднее время.
Определение клиентской совместимости
Поскольку библиотека, от которой зависят существующие клиенты, пересмотрена, изменения в ней могут влиять на возможность клиентов использовать новые версии библиотеки. Градус, для которого клиент может использовать ранее или более поздние версии зависимой библиотеки, чем та, с которой он соединяется, вызывают клиентской совместимостью.
Некоторые изменения незначительны; они включают добавляющие символы, которые неизвестны клиентам. Другие изменения являются главными; такие изменения включают удаление символа, изменение размера или видимости символа и изменения семантики функции. Все символы, которые библиотека представляет клиентам, составляют ABI библиотеки (двоичный интерфейс приложения). API библиотеки (интерфейс программирования приложения) включает только функции, которые библиотека делает доступным для ее клиентов. Градус, до которого ABI библиотеки остается тем же с точки зрения клиентов, разработанных с помощью более ранней версии библиотеки, определяет устойчивость библиотеки. Обеспечение, что ABI библиотеки остается стабильными гарантиями, что клиенты могут использовать более новые версии неизменной библиотеки. Т.е. пользователи приложения, зависящего от библиотеки, это обновляется регулярно, видят, что производительность их приложения улучшается, поскольку они обновляют библиотеку (думайте о механизме Обновления программного обеспечения OS X), не получая новую версию приложения.
Например, предположите, что приложение соединено с первой версией динамической библиотеки и выпущено к конечным пользователям. Позже, разработчик библиотеки вносит незначительные изменения в библиотеку и выпускает ее. Когда конечные пользователи устанавливают новую версию библиотеки по их компьютерам, приложение может использовать новую версию, не требуя, чтобы конечные пользователи получили обновленный двоичный файл приложения от разработчика. Приложение может получить преимущества от улучшений эффективности, сделанных к реализации библиотеки. И, в зависимости от того, как библиотека была записана, приложение могло бы быть в состоянии использовать в своих интересах функции, представленные новой версией. Однако к этим функциям получает доступ приложение только через API, доступный в первой версии библиотеки. Любые интерфейсы, представленные в новой версии библиотеки, идут неиспользованные приложением.
Рисунок 1 иллюстрирует жизненный цикл Получения динамическая библиотека и один из ее клиентов.
Этот список описывает версии библиотеки и клиента:
Нарисуйте 1.0, начальная версия библиотеки. Это экспортирует две функции,
draw_line
иdraw_square
.Клиент 1.0 соединяется с Получением 1.0. Поэтому это может использовать эти два символа экспорт библиотеки.
Нарисуйте 1.1, имеет более быстрые версии
draw_line
иdraw_square
, но их семантика неизменна, поддерживая клиентскую совместимость. Это - совместимая, незначительная версия, потому что Клиент 1.0 может использовать, Рисуют 1.1.Нарисуйте 1.2, представляет
draw_polygon
функция. API новой версии библиотеки является надмножеством API предыдущей версии. Получение 1,1 подмножества API 1,2 версий неизменно. Поэтому Клиент 1.0 может использовать, Рисуют 1.2. Однако Клиент 1.0 не знает о существованииdraw_polygon
и, поэтому, это не использует его. Это - незначительная версия, потому что Клиент API 1.0 знает о, неизменно в Получении 1.2. Но это - также несовместимая версия, потому что изменился API. Клиенты, соединенные с этой версией библиотеки, не могут использовать более ранние версии.Клиент 1.1 соединяется с Получением 1.2 и использование
draw_polygon
. Клиент 1.1 не может использовать более ранние версии библиотеки, потому что она используетdraw_polygon
, функция, не экспортирующаяся теми версиями. Однако, если разработчик библиотеки добавляетweak_import
припишите определению символа, Клиент 1.1 был бы в состоянии использовать более ранние версии библиотеки путем обеспечения этогоdraw_polygon
существует в его пространстве имен перед использованием его. Если символ не определяется, клиент может использовать другие средние значения выполнения желаемой задачи, или это может не выполнить задачу. Посмотрите, что Символ Экспортирует Стратегии подробных данных.Нарисуйте 2.0, не экспортирует
draw_square
. Это - главная версия, потому что символ, экспортируемый в предыдущей версии библиотеки, не экспортируется в этой версии. Клиенты, соединенные с этой версией библиотеки, не могут использовать более ранние версии.
Клиенты должны быть в состоянии использовать все незначительные версии библиотеки, с которой они соединяются без пересоединения. В целом, для использования главной версии библиотеки клиент должен быть соединен с новой версией. Клиент, возможно, также должен быть изменен, чтобы использовать в своих интересах новые символы, адаптировать его использование символов, измененных, или не использовать символы, не экспортирующиеся новой версией.
Указание информации о версии
Имя файла динамической библиотеки обычно содержит имя библиотеки с lib
префикс и .dylib
расширение. Например, библиотека под названием Динамо имела бы имя файла libDynamo.dylib
. Однако, если библиотека может пройти через одну или более версий после того, как она выпущена, ее имя файла должно включать номер основной версии версии. Клиенты соединились с библиотеками, имя файла которых включает номер основной версии версии, никогда не используют новую главную версию библиотеки, потому что главные версии публикуются под различными именами файлов. Эта модель управления версиями препятствует тому, чтобы клиенты использовали версии библиотеки, API которых является несовместимым с API, известным клиентам.
То, когда Вы публикуете динамическую библиотеку, намеревалось иметь будущие версии, необходимо раскрыть номер основной версии библиотеки в его имени файла. Например, имя файла для первой версии библиотеки Draw, представленной в Определении Клиентской Совместимости, могло быть libDraw.A.dylib
. Буква A указывает номер основной версии для первоначальной версии. Можно использовать любую номенклатуру для основной версии. Например, библиотеку Draw можно было также назвать libDraw.1.dylib
, или libDraw.I.dylib
. Важная вещь состоит в том, что имена файлов последующих главных версий библиотеки имеют отличающийся (и предпочтительно инкрементный) номера основной версии. Продолжая пример библиотеки Draw, главную версию к библиотеке можно было назвать libDraw.B.dylib
, libDraw.2.dylib
, или libDraw.II.dylib
. Незначительные версии библиотеки выпущены под тем же именем файла, используемым предыдущей главной версией.
В дополнение к номеру основной версии библиотека имеет номер вспомогательной версии. Номер вспомогательной версии является инкрементным числом с помощью формата X[.Y[.Z]]
, где X число между 0 и 65535, и Y и Z являются числами между 0 и 255. Например, номер вспомогательной версии для первого выпуска библиотеки Draw мог быть 1.0. Для установки номера вспомогательной версии динамической библиотеки используйте clang -current_version <version_number>
опция.
Число версии совместимости подобно номеру вспомогательной версии; это установлено через компилятор -compatibility_version
параметр командной строки. Число версии совместимости выпуска библиотеки указывает, что самая ранняя вспомогательная версия клиентов, соединенных с тем выпуском, может использовать. Например, пример в Определении Клиентской Совместимости указывает, что Клиент 1.1 не может использовать версии библиотеки Draw ранее, чем 1,2, потому что они не экспортируют draw_polygon
функция. Для просмотра текущих версий библиотеки и версий совместимости используйте otool -L <library>
команда.
Прежде, чем загрузить динамическую библиотеку, динамический загрузчик сравнивает текущую версию .dylib
файл в файловой системе пользователя с версией совместимости .dylib
файл клиент был соединен с в файловой системе разработчика. Если текущая версия ранее (меньше), чем версия совместимости, зависимая библиотека не загружается. Поэтому процесс запуска (для клиентских приложений) или процесс загрузки (для клиентских библиотек) прерываются.
Указание интерфейса библиотеки
Самым важным аспектом для определения прежде, чем реализовать динамическую библиотеку является свой интерфейс ее клиентам. Открытый интерфейс влияет на несколько областей в использовании библиотеки ее клиентами, разработкой библиотеки и обслуживанием и производительностью приложений, в которых пользуются библиотекой:
Простота использования: библиотекой с некоторыми, но легко понятными общедоступными символами намного проще пользоваться, чем тот, экспортирующий все символы, которые это определяет.
Простота обслуживания: библиотеку, имеющую маленький набор общедоступных символов и соответствующий набор частных символов, намного проще поддержать, потому что существует немного клиентских точек входа для тестирования. Кроме того, разработчики могут изменить частные символы для улучшения библиотеки в более новых версиях, не влияя на функциональность клиентов, соединенных с более ранней версией.
Производительность: Разработка динамической библиотеки так, чтобы это экспортировало минимальное число символов, оптимизирует количество времени, которое динамический загрузчик занимает для загрузки библиотеки в процесс. Чем меньше экспортируемых символов, которые имеет библиотека, тем быстрее динамический загрузчик загружает ее.
Следующие разделы показывают, как определить, какой из символов библиотеки для экспорта, как назвать их, и как экспортировать их.
Решение, что символы экспортировать
Сокращение набора символов Ваш экспорт библиотеки делает библиотеку простой в использовании и простой поддержать. С сокращенным набором символов пользователи Вашей библиотеки представлены только символам, относящимся к ним. И с немногими общедоступными символами, Вы свободны внести существенные изменения во внутренние интерфейсы, такие как добавление или удаление внутренних символов, не влияющих на клиенты Вашей библиотеки.
Глобальные переменные никогда не должны экспортироваться. Обеспечение неконтролируемого доступа к глобальным переменным библиотеки оставляет библиотеку открытой для проблем вызванный клиентами, присваивающими несоответствующие значения тем переменным. Также трудно внести изменения в глобальные переменные от одной версии Вашей библиотеки другому, не делая более новые версии несовместимыми с клиентами, не соединенными с ними. Одна из основных функций динамических библиотек является фактом, что, когда реализовано правильно, клиенты могут использовать более новые версии их без пересоединения. Если клиенты должны получить доступ к значению, сохраненному в глобальной переменной, Ваша библиотека должна экспортировать функции средства доступа, но не саму глобальную переменную. Соблюдение этой инструкции позволяет разработчикам библиотеки изменять определения глобальных переменных между версиями библиотеки, не представляя несовместимые версии.
Если Вашей библиотеке нужна функциональность, реализованная функциями, это экспортирует, необходимо рассмотреть внутренние версии реализации функций, добавление функций обертки им и экспорта оберток. Например, Ваша библиотека может иметь функцию, параметры которой должны быть проверены, но Вы уверены, что библиотека всегда обеспечивает допустимые значения при вызове функции. Внутренняя версия функции могла быть оптимизирована путем удаления кода доступа из него, создания внутреннего использования более эффективным. Код доступа может тогда быть помещен в функцию обертки, поддержав процесс проверки для клиентов. Кроме того, можно далее изменить внутреннюю реализацию функции для включения большего количества параметров, например, при поддержании внешней версии то же.
Если функция неоднократно вызывается клиентами, наличие внутренних версий вызова функций обертки сокращает производительность приложения, особенно. Однако преимущества гибкого обслуживания для Вас и стабильного интерфейса для Ваших клиентов значительно перевешивают это незначительное влияние производительности.
Именование экспортируемых символов
Динамический загрузчик не обнаруживает конфликты имен между символами, экспортируемыми динамическими библиотеками, которые он загружает. Когда клиент содержит ссылку на символ, что два или больше из его экспорта зависимых библиотек, динамический загрузчик связывает ссылку на первую зависимую библиотеку, экспортирующую символ в списке зависимой библиотеки клиента. Список зависимой библиотеки является списком зависимых библиотек клиента в порядке, они были указаны, когда клиент был соединен с ними. Кроме того, когда dlsym(3) OS X Developer Tools Manual Page
функция вызывается, динамический загрузчик возвращает адрес первого символа, который это находит в указанном объеме (глобальная переменная, локальная, или затем) с соответствующим именем. Для получения дополнительной информации на поисковом символом объеме, посмотрите Используя Символы.
Чтобы гарантировать, чтобы у клиентов Вашей библиотеки всегда был доступ к символам Ваш экспорт библиотеки, символы должны иметь уникальные имена в пространстве имен процесса. Один путь для приложений для использования двухуровневых пространств имен. Другой должен добавить префиксы к каждому экспортируемому символу. Это - соглашение, используемое большинством платформ OS X, таких как Углерод и Какао. Для получения дополнительной информации о двухуровневом пространстве имен посмотрите Выполняющиеся Мужественные Файлы в Мужественных Темах Программирования.
Стратегии экспорта символа
После идентификации символов, которые Вы хотите представить пользователям Вашей библиотеки, необходимо разработать стратегию для экспорта их или для того, чтобы не экспортировать остальную часть символов. Этот процесс также известен как установка видимости символов — т.е. доступны ли они для клиентов. Общедоступные или экспортируемые символы доступны для клиентов; частные, скрытые, или неэкспортируемые символы не доступны для клиентов. В OS X существует несколько способов указать видимость символов библиотеки:
static
класс памяти: Это - самый простой способ указать, что Вы не хотите экспортировать символ.Экспортируемый список символов или неэкспортируемый список символов: список является файлом с именами символов для экспорта или список символов для хранения частным. Имена символа должны включать подчеркивание (
_
) префикс. Можно использовать только один тип списка при генерации динамического файла библиотеки.visibility
атрибут: Вы помещаете этот атрибут в определение символов в файлах реализации для установки видимости символов индивидуально. Это дает Вам более тонкую настройку, по которой символы являются общедоступными или частными.Компилятор
-fvisibility
параметр командной строки: Эта опция указывает во время компиляции видимость символов с неуказанной видимостью в файлах реализации. Эта опция, объединенная сvisibility
припишите, самый безопасный и удобный способ идентифицировать общедоступные символы.weak_import
атрибут: Размещение этого атрибута в объявлении символа в заголовочном файле говорит компилятору генерировать слабую ссылку на символ. Эту функцию вызывают слабым соединением; символы сweak_import
атрибут вызывают слабо соединенными символами. Когда версия зависимой библиотеки, найденной во время запуска или время загрузки, не экспортирует слабо соединенный символ, на который ссылается клиент, со слабым соединением запускаются клиенты. Важно поместитьweak_import
атрибут в заголовочных файлах, что исходные файлы клиентского использования библиотеки, так, чтобы разработчики клиента знали, что они должны гарантировать существование символа перед использованием его. Иначе, клиент отказал бы или функционировал бы неправильно, когда это пытается использовать символ. Посмотрите Используя Слабо Соединенные Символы для получения дальнейшей информации на слабо соединенных символах. Для получения дополнительной информации об определениях символа посмотрите Выполняющиеся Мужественные Файлы в Мужественных Темах Программирования.Компилятор
-weak_library
параметр командной строки: Эта опция говорит компилятору обрабатывать экспортируемые символы всей библиотеки как слабо соединенные символы.
Чтобы проиллюстрировать, как установить видимость символов библиотеки, давайте запустимся с динамической библиотеки, позволяющей ее клиентам устанавливать значение, сохраненное в глобальной переменной в библиотеке и получать значение. Перечисление 1 показывает код, составляющий библиотеку.
Перечисление 1 простая динамическая библиотека
/* File: Person.h */ |
char* name(void); |
void set_name(char* name); |
/* File: Person.c */ |
#include "Person.h" |
#include <string.h> |
char _person_name[30] = {'\0'}; |
char* name(void) { |
return _person_name; |
} |
void _set_name(char* name) { |
strcpy(_person_name, name); |
} |
void set_name(char* name) { |
if (name == NULL) { |
_set_name(""); |
} |
else { |
_set_name(name); |
} |
} |
Намерение разработчика библиотеки состоит в том, чтобы предоставить клиентам возможность установить значение _person_name
с set_name
функционируйте и позволять им получить значение переменной с name
функция. Однако библиотека экспортирует больше, чем name
и set_name
функции, как показано выводом nm
инструмент командной строки:
% clang -dynamiclib Person.c -o libPerson.dylib |
% nm -gm libPerson.dylib |
(undefined) external ___strcpy_chk (from libSystem) |
0000000000001020 (__DATA,__common) external __person_name // Inadvertently exported |
0000000000000e80 (__TEXT,__text) external __set_name // Inadvertently exported |
0000000000000e70 (__TEXT,__text) external _name |
0000000000000ec0 (__TEXT,__text) external _set_name |
(undefined) external dyld_stub_binder (from libSystem) |
Обратите внимание на то, что _person_name
глобальная переменная и _set_name
функция экспортируется вместе с name
и set_name
функции. Существует много опций удалить _person_name
и _set_name
от символов, экспортируемых библиотекой. Этот раздел исследует некоторых.
Право преимущественной покупки должно добавить статический класс памяти к определению _person_name
и _set_name
в Person.c
, как показано в Перечислении 2.
Модуль Лица перечисления 2, скрывающий символ со статическим классом памяти
/* File: Person.c */ |
#include "Person.h" |
#include <string.h> |
static char _person_name[30] = {'\0'}; // Added 'static' storage class |
char* name(void) { |
return _person_name; |
} |
static void _set_name(char* name) { // Added 'static' storage class |
strcpy(_person_name, name); |
} |
void set_name(char* name) { |
if (name == NULL) { |
_set_name(""); |
} |
else { |
_set_name(name); |
} |
} |
Теперь, nm
вывод, похож на это:
(undefined) external ___strcpy_chk (from libSystem) |
0000000000000e80 (__TEXT,__text) external _name |
0000000000000e90 (__TEXT,__text) external _set_name |
(undefined) external dyld_stub_binder (from libSystem) |
Это означает, что библиотека экспортирует только name
и set_name
. Фактически, библиотека также экспортирует некоторые неопределенные символы, включая strcpy
. Они - ссылки на символы, которые библиотека получает из ее зависимых библиотек.
Проблема с этим подходом состоит в том, что он скрывает внутреннее _set_name
функция от других модулей в библиотеке. Если разработчик библиотеки полагает что какой-либо внутренний вызов _set_name
не должен быть проверен, но хочет проверить все клиентские вызовы, символ должен быть видим к другим модулям в библиотеке, но не клиенту библиотеки. Поэтому static
класс памяти не является надлежащим, чтобы скрыть символы от клиента, но раскрыть их модулям всей библиотеки.
Вторая опция для представления только символы, предназначенные для клиентского использования, должны иметь экспортируемый файл символов, перечисляющий символы для экспорта; все другие символы скрыты. Перечисление 3 показывает export_list
файл.
Файл перечисления 3, перечисляющий имена символов для экспорта
# File: export_list |
_name |
_set_name |
Для компиляции библиотеки Вы используете clang -exported_symbols_list
опция указать файл, содержащий имена символов для экспорта, как показано сюда:
clang -dynamiclib Person.c -exported_symbols_list export_list -o libPerson.dylib |
Третья и самая удобная опция для представления только name
и set_name
должен установить атрибут видимости в их реализациях к "default"
и набор -fvisibility
параметр командной строки компилятора к скрытому при компиляции исходных файлов библиотеки. Перечисление 4 показывает как Person.c
файл заботится об установке visibility
атрибут для символов, которые будут экспортироваться.
Модуль Лица перечисления 4 с помощью видимости приписывает символам экспорта
/* File: Person.c */ |
#include "Person.h" |
#include <string.h> |
// Symbolic name for visibility("default") attribute. |
#define EXPORT __attribute__((visibility("default"))) |
char _person_name[30] = {'\0'}; |
EXPORT // Symbol to export |
char* name(void) { |
return _person_name; |
} |
void _set_name(char* name) { |
strcpy(_person_name, name); |
} |
EXPORT // Symbol to export |
void set_name(char* name) { |
if (name == NULL) { |
_set_name(""); |
} |
else { |
_set_name(name); |
} |
} |
Библиотека была бы тогда скомпилирована с помощью следующей команды:
% clang -dynamiclib Person.c -fvisibility=hidden -o libPerson.dylib |
-fvisibility=hidden
параметр командной строки говорит, что компилятор для установки видимости любых символов без видимости приписывает скрытому, таким образом скрывая их от клиентов библиотеки. Для получения дополнительной информации на visibility
атрибут и -fvisibility
параметр командной строки, посмотрите http://gcc .gnu.org/onlinedocs/gcc и clang
страница справочника.
После этих экспортирующих символ инструкций гарантирует, чтобы библиотеки экспортировали только символы, которые Вы хотите сделать доступным для Ваших клиентов, упрощая использование библиотеки ее клиентами и упрощая ее обслуживание его разработчиками. Документ, Как Записать Совместно используемые Библиотеки, обеспечивает глубокий анализ стратегий экспорта символа. Этот документ доступен в http://people .redhat.com/drepper/dsohowto.pdf.
Определение местоположения внешних ресурсов
Когда необходимо определить местоположение ресурсов библиотека или потребности программы во время выполнения — такие как платформы, изображения, и т.д. — можно использовать любой из следующих методов:
Исполнимо-относительное расположение. Указать путь к файлу относительно расположения основной исполнимой программы, не библиотеку ссылки, место
@executable_path
макрос в начале пути. Например, в пакете приложения, содержащем частные платформы (который, в свою очередь, содержите совместно использованные библиотеки), любая из библиотек может определить местоположение вызванного ресурса приложенийMyImage.tiff
в пакете путем указания пути@executable_path/../Resources/MyImage.tiff
. Поскольку@executable_path
решения к двоичному файлу вMacOS
каталог в комплекте приложений, путь файла ресурсов должен указатьResources
каталог как подкаталогMacOS
родительский каталог (Contents
каталог). Для детального обсуждения пакетов каталога см. Руководство по программированию Пакета.Относительное библиотекой расположение. Для указания пути к файлу относительно расположения самой библиотеки поместите
@loader_path
макрос в начале пути. Относительное библиотекой расположение позволяет Вам определять местоположение ресурсов библиотеки в иерархии каталогов независимо от того, где расположена основная исполнимая программа.
Зависимости библиотеки
При разработке динамической библиотеки Вы указываете ее зависимые библиотеки путем соединения исходного кода с ними. Когда клиент Вашей библиотеки пытается загрузить его, зависимые библиотеки Вашей библиотеки должны присутствовать в файловой системе для Вашей библиотеки для загрузки успешно. (См. Зависимые от предшествующего пути развития выполнением Библиотеки для приобретения знаний об установке зависимых библиотек в перемещаемом каталоге.) В зависимости от того, как клиент загружает Вашу библиотеку, некоторые или все ссылки Вашей библиотеки на символы, экспортируемые ее зависимыми библиотеками, разрешены. Необходимо рассмотреть использование dlsym(3) OS X Developer Tools Manual Page
функция для получения адреса символов, когда они необходимы вместо того, чтобы иметь ссылки, которые, вероятно, всегда придется разрешать во время загрузки. Посмотрите Используя Символы для подробных данных.
Более зависимые библиотеки, которые Ваша библиотека имеет, дольше, она берет для Вашей библиотеки для загрузки. Поэтому необходимо соединить библиотеку только с теми динамическими библиотеками, требуемыми во время загрузки. После компиляции библиотеки можно просмотреть ее зависимые библиотеки в редакторе оболочки с otool -L
команда.
Любыми динамическими библиотеками, которые редко использует Ваша библиотека или чья функциональность необходима только при выполнении определенных задач, нужно пользоваться как загруженные библиотеки времени выполнения; т.е. они должны быть открыты с dlopen(3) OS X Developer Tools Manual Page
функция. Например, когда модуль в Вашей библиотеке должен выполнить задачу, требующую использования независимой библиотеки, модуль должен использовать dlopen
для загрузки библиотеки пользуйтесь библиотекой, чтобы выполнить ее задачу и закрыть библиотеку с dlclose(3) OS X Developer Tools Manual Page
по окончании. Для получения дополнительной информации о загружающихся библиотеках во время выполнения посмотрите Открытие Dynamic Libraries.
Необходимо также свести число к минимуму внешних ссылок на символы в зависимых библиотеках. Эта практика оптимизирует далее время загрузки Вашей библиотеки.
Необходимо раскрыть пользователям библиотеки все библиотеки использование библиотеки и являются ли они зависимыми библиотеками. Когда пользователи Вашей динамической библиотеки соединяют свои изображения, статический компоновщик должен быть в состоянии найти зависимые библиотеки всей Вашей библиотеки, или через линейные тракты ссылки или через символьные ссылки. Кроме того, потому что Ваша динамическая библиотека загружается успешно, даже когда некоторые или все библиотеки, которые она открывает во время выполнения, не присутствуют во время загрузки, пользователи Вашей библиотеки должны знать, какие динамические библиотеки Ваша библиотека открывает во время выполнения и под который обстоятельства. Пользователи Вашей библиотеки могут использовать ту информацию при исследовании неожиданного поведения библиотекой.
Инициализаторы модуля и финализаторы
Когда динамические библиотеки загружаются, они, возможно, должны подготовить ресурсы или выполнить специальную инициализацию прежде, чем сделать что-либо еще. С другой стороны, когда библиотеки разгружены, они, возможно, должны выполнить некоторые процессы завершения. Эти задачи выполняются функциями инициализатора и функциями финализатора, также вызванными конструкторы и деструкторы.
Инициализаторы могут безопасно использовать символы от зависимых библиотек, потому что динамический загрузчик выполняет статические инициализаторы зависимых библиотек изображения прежде, чем вызвать статические инициализаторы изображения.
Вы указываете, что функция является инициализатором путем добавления constructor
припишите его определению. destructor
атрибут идентифицирует функции финализатора. Инициализаторы и финализаторы не должны быть экспортированы. Инициализаторы динамической библиотеки выполняются в порядке, с ними встречается компилятор. Это - финализаторы, с другой стороны, выполняются в обратном порядке, как встречено компилятором.
Например, Перечисление 5 показывает ряд инициализаторов и финализаторов, определенных тождественно в двух файлах File1.c
и File2.c
в динамической библиотеке под названием Inifi.
Перечисление 5 инициализатор Inifi и определения финализатора
/* Files: File1.c, File2.c */ |
#include <stdio.h> |
__attribute__((constructor)) |
static void initializer1() { |
printf("[%s] [%s]\n", __FILE__, __FUNCTION__); |
} |
__attribute__((constructor)) |
static void initializer2() { |
printf("[%s] [%s]\n", __FILE__, __FUNCTION__); |
} |
__attribute__((constructor)) |
static void initializer3() { |
printf("[%s] [%s]\n", __FILE__, __FUNCTION__); |
} |
__attribute__((destructor)) |
static void finalizer1() { |
printf("[%s] [%s]\n", __FILE__, __FUNCTION__); |
} |
__attribute__((destructor)) |
static void finalizer2() { |
printf("[%s] [%s]\n", __FILE__, __FUNCTION__); |
} |
__attribute__((destructor)) |
static void finalizer3() { |
printf("[%s] [%s]\n", __FILE__, __FUNCTION__); |
} |
Продолжая пример, Inifi динамическая библиотека является единственной зависимой библиотекой Испытательной программы, сгенерированной от Trial.c
файл, показанный в Перечислении 6.
Перечисление 6 Trial.c
файл
/* Trial.c */ |
#include <stdio.h> |
int main(int argc, char** argv) { |
printf("[%s] [%s] Finished loading. Now quitting.\n", __FILE__, __FUNCTION__); |
return 0; |
} |
Перечисление 7 показывает вывод, произведенный Испытательным приложением.
Порядок выполнения перечисления 7 инициализаторов и финализаторов динамической библиотеки
% clang -dynamiclib File1.c File2.c -fvisibility=hidden -o libInifi.dylib |
% clang Trial.c libInifi.dylib -o trial |
% ./trial |
[File1.c] [initializer1] |
[File1.c] [initializer2] |
[File1.c] [initializer3] |
[File2.c] [initializer1] |
[File2.c] [initializer2] |
[File2.c] [initializer3] |
[Trial.c] [main] Finished loading. Now quitting. |
[File2.c] [finalizer3] |
[File2.c] [finalizer2] |
[File2.c] [finalizer1] |
[File1.c] [finalizer3] |
[File1.c] [finalizer2] |
[File1.c] [finalizer1] |
Несмотря на то, что у Вас может быть столько статических инициализаторов и финализаторов в изображении, сколько Вы хотите, необходимо консолидировать инициализацию и код завершения в один инициализатор и один финализатор на модуль по мере необходимости. Можно также принять решение иметь один инициализатор и один финализатор на библиотеку.
В OS X v10.4 и позже, статические инициализаторы могут получить доступ к параметрам, данным текущей программе. Путем определения параметров инициализатора, поскольку Вы определили бы параметры к основной функции программы, можно получить число данных параметров, сами параметры и переменные окружения процесса. Кроме того, для принятия мер против инициализатора или финализатора, вызываемого дважды, Вы должны conditionalize Ваша инициализация и код завершения в функции. Перечисление 8 показывает определение статического инициализатора, имеющего доступ к параметрам и conditionalizes программы ее код инициализации.
Определение перечисления 8 статического инициализатора
__attribute__((constructor)) |
static void initializer(int argc, char** argv, char** envp) { |
static initialized = 0; |
if (!initialized) { |
// Initialization code. |
initialized = 1; |
} |
} |
Основанные на С++ библиотеки
Используя C++ для реализации динамической библиотеки представляет собой несколько проблем — в основном экспорт имен символа и создание и уничтожение объектов. Подробность следующих разделов, как экспортировать символы из основанной на С++ динамической библиотеки и как предоставить клиентам функции, создающие и уничтожающие экземпляры класса.
Экспорт символов C++
C++ использует искажение имени для кодирования размера и информации о типе на имя символа. Искажение имени в C++ делает символы экспорта стандартным способом через различные платформы невозможными. Когда динамическая библиотека компилируется, каждый символ переименован для включения той информации, но используемое кодирование не является стандартным между платформами. Динамический загрузчик использует сопоставление строк для определения местоположения символов во время выполнения; имя искавшего символа должно соответствовать точно имя цели. Когда имена символа были искажены, динамический загрузчик не имеет никакого способа знать, как было закодировано имя символа, который это ищет.
Для экспорта символов лица, не являющегося членом какой-либо организации, из основанной на С++ динамической библиотеки так, чтобы динамический загрузчик мог найти его необходимо добавить extern "C"
директива к объявлениям символов. Это ключевое слово говорит компилятору не искажать имя символа. Например, следующее объявление делает NewPerson
функционируйте доступные клиентам библиотеки:
extern "C" Person* NewPerson(void); |
Без этой директивы имя функции могло быть изменено на _Z9NewPersonv
, который лишил бы возможности динамический загрузчик находить NewPerson
символ во время выполнения.
Единственное лицо, не являющееся членом какой-либо организации, функционирует, который должен быть экспортирован, конструкторы и деструкторы, особенно в динамических библиотеках, которыми могут пользоваться клиенты как зависимые библиотеки вместо загруженных временем выполнения библиотек. Это вызвано тем, что у клиентов должен быть доступ к конструкторам и деструкторам класса так, чтобы они могли использовать new
и delete
операторы на классе.
Определение интерфейсов класса C++
Динамическая библиотека должна всегда публиковать свой открытый интерфейс клиентам через заголовочные файлы. (Несмотря на то, что клиенты могут пользоваться динамическими библиотеками без своих заголовочных файлов, делание так является очень трудным и является подверженным ошибке.) Заголовочный файл для класса, это доступно клиентам, должен включать объявления своих открытых методов. Динамические библиотеки, делающие класс доступным для его клиентов, должны включать виртуальное ключевое слово в объявление методов всего класса, за исключением его конструкторов и деструкторов. Например, Перечисление 9 показывает объявление для класса Лица.
Объявление перечисления 9 для класса Лица
class Person { |
private: |
char _person_name[30]; |
public: |
Person(); |
virtual void set_name(char person_name[]); |
virtual char* name(); |
}; |
Создание и уничтожение объектов C++
Когда основанная на С++ динамическая библиотека является зависимой библиотекой своего клиента, клиент может использовать new
и delete
операторы, чтобы создать и уничтожить экземпляры класса библиотека определяют. Однако клиенты, открывающие C ++-based library во время выполнения через dlopen(3) OS X Developer Tools Manual Page
не имейте доступа к конструкторам библиотеки, потому что конструкторы экспортируются с их искаженными именами, препятствуя тому, чтобы динамический загрузчик определил местоположение их. См. Перечисление 11 для подробных данных об искажении имени.
У клиентов, загружающих библиотеку во время выполнения для использования класса, должен быть способ создать и уничтожить объекты класса, не используя new
и delete
операторы. Библиотека предоставляет эту функциональность своим клиентам путем экспорта по крайней мере двух функций фабрики классов. Функции фабрики классов создают и уничтожают объекты класса от имени пользователя класса. Функции создателя создают объект класса и возвращают указатель на него. Функции деструктора избавляются от объекта, создаваемого функцией создателя для того же класса. В дополнение к функциям фабрики библиотека должна определить тип данных для каждой экспортируемой функции фабрики.
Например, Перечисление 10 показывает заголовок и файлы реализации Person
класс, экспортируемый библиотекой Person. Заголовочный файл включает объявление и определение типа пары функций фабрики, NewPerson
, и DeletePerson
:
Интерфейс перечисления 10 и реализация класса C++ в динамической библиотеке
/* File: Person.h */ |
class Person { |
private: |
char _person_name[30]; |
public: |
Person(); |
virtual void set_name(char person_name[]); |
virtual char* name(); |
}; |
// Constructor function and function type. |
extern "C" Person *NewPerson(void); |
typedef Person *Person_creator(void); |
// Destructor function and function type. |
extern "C" void DeletePerson(Person *person); |
typedef void Person_disposer(Person *); |
/* File: Person.cpp */ |
include <iostream> |
#include "Person.h" |
#define EXPORT __attribute__((visibility("default"))) |
EXPORT |
Person::Person() { |
char default_name[] = "<no value>"; |
this->set_name(default_name); |
} |
EXPORT |
Person* NewPerson(void) { |
return new Person; |
} |
EXPORT |
void DeletePerson(Person* person) { |
delete person; |
} |
void Person::set_name(char name[]) { |
strcpy(_person_name, name); |
} |
char* Person::name(void) { |
return _person_name; |
} |
Перечисление 11 показывает, как клиент мог бы пользоваться библиотекой Person.
Клиент перечисления 11, использующий класс C++, реализован в загруженной временем выполнения библиотеке
/* File: Client.cpp */ |
#include <iostream> |
#include <dlfcn.h> |
#include "Person.h" |
int main() { |
using std::cout; |
using std::cerr; |
// Open the library. |
void* lib_handle = dlopen("./libPerson.dylib", RTLD_LOCAL); |
if (!lib_handle) { |
cerr << "[" << __FILE__ << "] main: Unable to open library: " |
<< dlerror() << "\n"; |
exit(EXIT_FAILURE); |
} |
// Get the NewPerson function. |
Person_creator* NewPerson = (Person_creator*)dlsym(lib_handle, "NewPerson"); |
if (!NewPerson) { |
cerr << "[" << __FILE__ << "] main: Unable to find NewPerson method: " |
<< dlerror() << "\n"; |
exit(EXIT_FAILURE); |
} |
// Get the DeletePerson function. |
Person_disposer* DeletePerson = |
(Person_disposer*)dlsym(lib_handle, "DeletePerson"); |
if (!DeletePerson) { |
cerr << "[" << __FILE__ |
<< "] main: Unable to find DeletePerson method: " |
<< dlerror() << "\n"; |
exit(EXIT_FAILURE); |
} |
// Create Person object. |
Person* person = (Person*)NewPerson(); |
// Use Person object. |
cout << "[" << __FILE__ << "] person->name() = " << person->name() << "\n"; |
char new_name[] = "Floriane"; |
person->set_name(new_name); |
cout << "[" << __FILE__ << "] person->name() = " << person->name() << "\n"; |
// Destroy Person object. |
DeletePerson(person); |
// Close the library. |
if (dlclose(lib_handle) != 0) { |
cerr << "[" << __FILE__ << "] main: Unable to close library: " |
<< dlerror() << "\n"; |
} |
return 0; |
} |
Следующие команды компилируют библиотеку и клиентскую программу:
% clang++ -dynamiclib Person.cpp -fvisibility=hidden -o libPerson.dylib |
% clang++ Client.cpp -o client |
Для получения дальнейшей информации о том, как клиенты используют классы C++, реализованные в динамических библиотеках, посмотрите Используя Классы C++.
Библиотеки объективные на базе С
Существует несколько проблем для рассмотрения при разработке или обновлении динамической библиотеки Объективной на базе С:
Публикация открытого интерфейса класса Objective C или категории отличается от способа, которым символы экспортируются в C.
В Objective C каждый метод каждого класса доступен во время выполнения. Клиенты могут анализировать классы для обнаружения, какие методы доступны. Однако так, чтобы разработчики клиента не получают волнение предупреждений о реализациях отсутствующего метода, разработчики библиотеки должны опубликовать интерфейс к своим классам и категориям как протоколы разработчикам клиента.
Библиотеки объективные на базе С имеют доступ к большему количеству средств инициализации, чем доступные библиотекам на базе С.
Objective C имеет средство псевдонима класса, позволяющее разработчикам библиотеки переименовывать классы в версии, но позволять клиентам соединяться с той версией, чтобы продолжать использовать имена, используемые в более ранних версиях.
Следующие разделы исследуют эти области подробно.
Определение интерфейсов класса и категории
Поскольку у разработчиков клиента обычно нет доступа к реализации классов Objective C и категорий определенным в динамических библиотеках, разработчики библиотеки должны опубликовать открытые интерфейсы классов и категорий как протоколы в заголовочных файлах. Разработчики клиента компилируют свои продукты с помощью тех заголовочных файлов и в состоянии инстанцировать классов правильно путем добавления необходимых имен протокола к определениям переменной. Перечисление 12 показывает заголовок и файлы реализации Person
класс в библиотеке Objective-C–based. Перечисление 13 показывает заголовок и файлы реализации Titling
категория в той же библиотеке, добавляющей -setTitle
метод к Person
класс.
Заголовок перечисления 12 и файлы реализации Person
класс
/* File: Person.h */ |
#import <Foundation/Foundation.h> |
@protocol Person |
- (void)setName:(NSString*)name; |
- (NSString*)name; |
@end |
@interface Person : NSObject <Person> { |
@private |
NSString* _person_name; |
} |
@end |
/* File: Person.m */ |
#import <Foundation/Foundation.h> |
#import "Person.h" |
@implementation Person |
- (id)init { |
if (self = [super init]) { |
_person_name = @""; |
} |
return self; |
} |
- (void)setName:(NSString*)name { |
_person_name = name; |
} |
- (NSString*)name { |
return _person_name; |
} |
@end |
Заголовок перечисления 13 и файлы реализации Titling
категория к Person
класс
/* File: Titling.h */ |
#import <Foundation/Foundation.h> |
#import "Person.h" |
@protocol Titling |
- (void)setTitle:(NSString*)title; |
@end |
@interface Person (Titling) <Titling> |
@end |
/* File: Titling.m */ |
#import <Foundation/Foundation.h> |
#import "Titling.h" |
@implementation Person (Titling) |
- (void)setTitle:(NSString*)title { |
[self setName:[[title stringByAppendingString:@" "] |
stringByAppendingString:[self name]]]; |
} |
@end |
Перечисление 14 показывает, как клиент мог бы пользоваться библиотекой.
Клиент перечисления 14, пользующийся библиотекой Person
/* File: Client.m */ |
#import <Foundation/Foundation.h> |
#import <objc/runtime.h> |
#import <dlfcn.h> |
#import "Person.h" |
#import "Titling.h" |
int main() { |
@autoreleasepool { |
// Open the library. |
void* lib_handle = dlopen("./libPerson.dylib", RTLD_LOCAL); |
if (!lib_handle) { |
NSLog(@"[%s] main: Unable to open library: %s\n", |
__FILE__, dlerror()); |
exit(EXIT_FAILURE); |
} |
// Get the Person class (required with runtime-loaded libraries). |
Class Person_class = objc_getClass("Person"); |
if (!Person_class) { |
NSLog(@"[%s] main: Unable to get Person class", __FILE__); |
exit(EXIT_FAILURE); |
} |
// Create an instance of Person. |
NSLog(@"[%s] main: Instantiating Person_class", __FILE__); |
NSObject<Person,Titling>* person = [Person_class new]; |
// Use person. |
[person setName:@"Perrine LeVan"]; |
[person setTitle:@"Ms."]; |
NSLog(@"[%s] main: [person name] = %@", __FILE__, [person name]); |
// Close the library. |
if (dlclose(lib_handle) != 0) { |
NSLog(@"[%s] Unable to close library: %s\n", |
__FILE__, dlerror()); |
exit(EXIT_FAILURE); |
} |
} |
return(EXIT_SUCCESS); |
} |
Следующие команды компилируют библиотеку и клиентскую программу:
clang -framework Foundation -dynamiclib Person.m Titling.m -o libPerson.dylib |
clang -framework Foundation Client.m -o client |
Инициализация классов Objective C
Динамические библиотеки объективные на базе С предоставляют несколько средств инициализации для модулей, классов и категорий. Следующий список описывает те средства в порядке, они выполняются.
+load
метод: Инициализирует ресурсы, необходимые классу или категории. Время выполнения Objective C отправляетload
обменивайтесь сообщениями к каждому классу, который реализует библиотека; это тогда отправляетload
передайте к каждой категории реализации библиотеки. Порядок, в котором одноуровневые классы отправляютсяload
сообщение является неопределенным. Реализуйте+load
метод для инициализации ресурсов, необходимых классу или категории. Обратите внимание на то, что нет никакого соответствия, «разгружают» метод.Инициализаторы модуля: Инициализирует модуль. Динамический загрузчик вызывает все функции инициализатора (определенный с помощью
constructor
атрибут) в каждом из модулей библиотеки. Посмотрите Инициализаторы Модуля и Финализаторы для получения дополнительной информации об инициализаторах модуля.+initialize
метод: Инициализирует ресурсы, необходимые экземплярам класса, прежде чем будут созданы любые экземпляры. Время выполнения Objective C отправляетinitialize
обменивайтесь сообщениями к классу прежде, чем создать экземпляр класса. Обратите внимание на то, что нет никакого соответствия, «завершают» сообщение, отправленное в класс, когда библиотека разгружена, или процесс завершается.
Создание псевдонимов к классу
При переименовании класса в версии динамической библиотеки можно сократить нагрузку принятия для разработчиков клиента путем добавления псевдонима к новому имени в заголовочном файле библиотеки. Эта практика позволяет разработчикам клиента выпускать клиенты, использующие в своих интересах новую версию библиотеки быстро. Разработчики клиента могут позже обновить ссылки на класс на их досуге.
Контрольный список руководства по проектированию
Этот список обеспечивает сводку инструкций для улучшения определенных аспектов динамической библиотеки:
Простота использования
Сократите количество символов, которые экспортирует библиотека.
Обеспечьте уникальные имена для открытых интерфейсов.
Простота обслуживания
Средство доступа экспорта функционирует к переменным. Не экспортируйте переменные.
Реализуйте открытые интерфейсы как обертки к внутренним, закрытым интерфейсам.
Производительность
Минимизируйте число ссылок на символы в зависимых библиотеках. Использовать
dlsym(RTLD_GLOBAL, <symbol_name>)
получить адрес символов, экспортируемых зависимыми библиотеками, когда они необходимы.Минимизируйте число зависимых библиотек. Рассмотрите загружающиеся библиотеки с
dlopen
при необходимости. Не забудьте закрывать библиотеку сdlclose
когда сделано.Реализуйте открытые интерфейсы как обертки к внутренним, закрытым интерфейсам.
Совместимость
Символы экспорта как слабо соединенные символы.
Закодируйте номер основной версии библиотеки в его имени файла.