Создание динамических библиотек
Когда Вы создаете или обновляете динамическую библиотеку, необходимо тщательно рассмотреть, как она может использоваться разработчиками в их продуктах. Также важно дать этим разработчикам гибкость, позволяя их продуктам работать с ранее или более поздние версии библиотеки без них имеющий необходимость обновить их продукты.
Эта статья демонстрирует, как записать динамическую библиотеку так, чтобы это было просто в использовании разработчиками, хотящими использовать в своих интересах его в их собственной разработке. Эта статья также описывает, как обновить существующие библиотеки и управлять их информацией о версии для максимизации клиентской совместимости.
Создание библиотек
При создании динамической библиотеки необходимо выполнить эти задачи:
Определите цель библиотеки: Эта информация обеспечивает фокус, требуемый определить открытый интерфейс библиотеки.
Определите интерфейс библиотеки (заголовочные файлы): Это - интерфейс, через который клиенты библиотеки получают доступ к ее функциональности.
Реализуйте библиотеку (файлы реализации): Это - то, где Вы определяете государственные функции, которые используют клиенты библиотеки. Вы, возможно, также должны определить частные переменные, и функции должны были реализовать интерфейс, но не требуемые клиентами.
Установите информацию о версии библиотеки: информация о версии библиотеки разделена на три части: главный, незначительный, и совместимость. Когда Вы создаете, Вы указываете все части информации о версии библиотеки
.dylib
файл. Посмотрите Управляющую Клиентскую Совместимость С Зависимыми библиотеками для подробных данных.Протестируйте библиотеку: По крайней мере необходимо определить тест для каждой из государственных функций, которые библиотека представляет, чтобы гарантировать, чтобы они выполнили корректное действие, данное определенные тестовые вводы или после выполнения определенного набора операций.
Следующие разделы обеспечивают пример процесса, взятого для разработки простой динамической библиотеки, названной Оценками. Файлы, упомянутые в следующих разделах, включены в Ratings/1.0
в пакете сопутствующего файла этого документа.
Определение цели библиотеки
Цель библиотеки Ratings состоит в том, чтобы обеспечить оценки анализатор его клиентам. Оценки являются строками, составленными из звездочек (*), которые представляют уровни удовлетворенности для определенного элемента. Например, в приложении iTunes Apple, можно указать, нравится ли Вам определенная песня много с оценкой с пятью звездами (*****) или нисколько с оценкой одной звезды (*).
Первоначальная версия библиотеки обеспечивает способ для клиентов добавить оценки, получить количество оценок, они добавили, получают среднюю оценку и очищают набор оценки.
Определение интерфейса библиотеки
Перед реализацией библиотеки необходимо определить интерфейс, который клиенты библиотеки используют для взаимодействия с нею. Необходимо тщательно указать семантику каждой функции. Ясное определение приносит преимущества Вам, потому что оно ясно дает понять цель каждой государственной функции. Это также приносит преимущества разработчикам, пользующимся Вашей библиотекой, потому что это говорит им точно, что ожидать, когда они вызывают каждую функцию в своем коде.
Это - пример списка функций интерфейса с семантическим значением каждой функции:
void addRating(char *rating)
: Добавляет номинальное значение к набору, сохраненному библиотекой. Параметром оценки является строка; это не должен быть NULL. Каждый символ в строке представляет одну точку оценки. Например, пустая строка (""
) эквивалентно оценке 0,"*"
средние значения 1,"**"
средние значения 2, и т.д. Фактические символы, используемые в строке, являются несущественными. Поэтому" "
средние значения 1 и"3456"
средние значения 4. Каждый вызов к этой функции постепенно увеличивает значение, возвращенное функцией оценок.int ratings(void)
: Возвращает число номинальных значений в наборе. Эта функция не имеет никаких побочных эффектов.char *meanRatings(void)
: Возвращает среднюю оценку в наборе оценки как строка, с помощью одной звездочки на оценку точки. Эта функция не имеет никаких побочных эффектов.clearRatings(void)
: Очищает набор оценки. После вызывания этой функции (без последующих вызовов кaddRating
),ratings
функциональные возвраты0
.
Перечисление 1 показывает заголовочный файл, что клиенты библиотеки Ratings включают для доступа к ее интерфейсу.
Интерфейс перечисления 1 к оценкам 1.0
/* File: Ratings.h |
* Interface to libRatings.A.dylib 1.0. |
*************************************/ |
/* Adds 'rating' to the set. |
* rating: Each character adds 1 to the numeric rating |
* Example: "" = 0, "*" = 1, "**" = 2, "wer " = 4. |
*/ |
void addRating(char *rating); |
/* Returns the number of ratings in the set. |
*/ |
int ratings(void); |
/* Returns the mean rating of the set. |
*/ |
char *meanRating(void); |
/* Clears the set. |
*/ |
void clearRatings(void); |
Реализация библиотеки
Интерфейс, объявленный в Определении Интерфейса Библиотеки, реализован в Ratings.c
, показанный в Перечислении 2.
Реализация перечисления 2 оценок 1.0
/* File: Ratings.c |
* Compile with -fvisibility=hidden. // 1 |
**********************************/ |
#include "Ratings.h" |
#include <stdio.h> |
#include <string.h> |
#define EXPORT __attribute__((visibility("default"))) |
#define MAX_NUMBERS 99 |
static int _number_list[MAX_NUMBERS]; |
static int _numbers = 0; |
// Initializer. |
__attribute__((constructor)) |
static void initializer(void) { // 2 |
printf("[%s] initializer()\n", __FILE__); |
} |
// Finalizer. |
__attribute__((destructor)) |
static void finalizer(void) { // 3 |
printf("[%s] finalizer()\n", __FILE__); |
} |
// Used by meanRating, middleRating, frequentRating. |
static char *_char_rating(int rating) { |
char result[10] = ""; |
int int_rating = rating; |
for (int i = 0; i < int_rating; i++) { |
strncat(result, "*", sizeof(result) - strlen(result) - 1); |
} |
return strdup(result); |
} |
// Used by addRating. |
void _add(int number) { // 4 |
if (_numbers < MAX_NUMBERS) { |
_number_list[_numbers++] = number; |
} |
} |
// Used by meanRating. |
int _mean(void) { |
int result = 0; |
if (_numbers) { |
int sum = 0; |
int i; |
for (i = 0; i < _numbers; i++) { |
sum += _number_list[i]; |
} |
result = sum / _numbers; |
} |
return result; |
} |
EXPORT |
void addRating(char *rating) { // 5 |
if (rating != NULL) { |
int numeric_rating = 0; |
int pos = 0; |
while (*rating++ != '\0' && pos++ < 5) { |
numeric_rating++; |
} |
_add(numeric_rating); |
} |
} |
EXPORT |
char *meanRating(void) { |
return _char_rating(_mean()); |
} |
EXPORT |
int ratings(void) { |
return _numbers; |
} |
EXPORT |
void clearRatings(void) { |
_numbers = 0; |
} |
Следующий список описывает маркированные строки.
Этот комментарий только, чтобы напомнить разработчикам библиотеки компилировать этот файл с компилятором
-fvisibility=hidden
опция, так, чтобы только символы сvisibility("default")
атрибут экспортируется.Этот инициализатор определяется только для показа, в которой точке выполнения клиента инициализаторы библиотеки вызывает динамический загрузчик.
Этот финализатор определяется только для показа, в которой точке выполнения клиента финализаторы библиотеки вызывает динамический загрузчик.
_add
функция является примером внутренней функции. Клиенты не должны знать об этом и, поэтому, это не экспортируется. Кроме того, потому что внутренним вызовам доверяют, никакая проверка не выполняется. Однако нет никакого требования, чтобы внутреннее использование функционировало проверка отсутствия.addRating
функция является примером экспортируемой функции. Чтобы гарантировать, что только корректные оценки добавляются, входной параметр функции проверен.
Установка информации о версии библиотеки
Когда Вы компилируете исходные файлы библиотеки в a .dylib
файл, Вы устанавливаете информацию о версии, указывающую, могут ли клиенты использовать версии библиотеки ранее, или позже, чем версия они были соединены с. Когда клиент загружается в процесс, динамический загрузчик ищет .dylib
файл в путях поиска библиотеки и, если это находит его, сравнивает информацию о версии .dylib
файл с информацией о версии, зарегистрированной в клиентском изображении. Если клиент не совместим с .dylib
файл, динамический загрузчик не загружает клиент в процесс. В действительности процесс загрузки клиента прерывается, потому что динамический загрузчик был неспособен определить местоположение совместимой зависимой библиотеки.
Перечисление 3 показывает, что команда раньше генерировала версию 1.0 библиотеки Ratings.
Перечисление 3 , Генерирующее версию 1.0 Оценок динамическая библиотека
[Ratings/1.0]% make dylib |
clang -dynamiclib -std=gnu99 Ratings.c -current_version 1.0 -compatibility_version 1.0 -fvisibility=hidden -o libRatings.A.dylib |
Этот список указывает, где указаны основная версия, вспомогательная версия и версия совместимости:
Номер основной версии указан в имени файла библиотеки как в
-o libRatings.A.dylib
.Номер вспомогательной версии указан в
-current_version 1.0
.Число версии совместимости указано в
-compatibility_version 1.0
.
Тестирование библиотеки
Прежде, чем опубликовать динамическую библиотеку, необходимо протестировать ее открытый интерфейс, чтобы гарантировать, что она выполняет, когда Вы указали в документации интерфейса (см. Определение Интерфейса Библиотеки. Для обеспечения максимальной гибкости для клиентов необходимо удостовериться, что библиотекой можно пользоваться как зависимая библиотека (клиенты соединяются с ним, и библиотека загружается, когда клиент загружается), или как загруженная временем выполнения библиотека (клиенты не соединяются с ним и использование dlopen(3) OS X Developer Tools Manual Page
загрузить его).
Перечисление 4 показывает пример тестового клиента, пользующегося библиотекой Ratings как зависимой библиотекой.
Перечисление 4 , Тестирующее Оценки 1.0 как зависимая библиотека
/* Dependent.c |
* Tests libRatings.A.dylib 1.0 as a dependent library. |
*****************************************************/ |
#include <stdio.h> |
#include <string.h> |
#include "Ratings.h" |
#define PASSFAIL "Passed":"Failed" |
#define UNTST "Untested" |
int main(int argc, char **argv) { |
printf("[start_test]\n"); |
// Setup. |
addRating(NULL); |
addRating(""); |
addRating("*"); |
addRating("**"); |
addRating("***"); |
addRating("*****"); |
addRating("*****"); |
// ratings. |
printf("[%s] ratings(): %s\n", |
__FILE__, (ratings() == 6? PASSFAIL)); |
// meanRating. |
printf("[%s] meanRating(): %s\n", |
__FILE__, (strcmp(meanRating(), "**") == 0)? PASSFAIL); |
// clearRatings. |
clearRatings(); |
printf("[%s] clearRatings(): %s\n", |
__FILE__, (ratings() == 0? PASSFAIL)); |
printf("[end_test]\n"); |
return 0; |
} |
Следующая команда генерирует Dependent
клиентская программа. Обратите внимание на то, что libRatings.A.dylib
включен в строку компиляции.
clang Dependent.c libRatings.A.dylib -o Dependent |
Dependent
программа производит вывод, показывающий, приводит ли вызов каждой из экспортируемых функций библиотеки к ожидаемым результатам. Кроме того, initializer
и finalizer
функции, определяемые в библиотеке продолжают выходные линии, указывающие, когда они вызываются в связи с нормальным процессом программы. Этот вывод показан в Перечислении 5.
Результаты испытаний перечисления 5 для Оценок 1.0 как зависимая библиотека
> ./Dependent |
[Ratings.c] initializer() |
[start_test] |
[Dependent.c] ratings(): Passed |
[Dependent.c] meanRating(): Passed |
[Dependent.c] clearRatings(): Passed |
[end_test] |
[Ratings.c] finalizer() |
Заметьте это initializer
вызывается перед main
функция. Функция финализатора, с другой стороны, вызвана после выхода main
функция.
Тестирование библиотеки Ratings как загруженная временем выполнения библиотека требует другой тестовой программы, Время выполнения. Перечисление 6 показывает свой исходный файл.
Перечисление 6 , Тестирующее Оценки 1.0 как загруженная временем выполнения библиотека
/* Runtime.c |
* Tests libRatings.A.dylib 1.0 as a runtime-loaded library. |
***********************************************************/ |
#include <stdio.h> |
#include <stdlib.h> |
#include <dlfcn.h> |
#include <string.h> |
#include "Ratings.h" |
#define PASSFAIL "Passed":"Failed" |
#define UNTST "Untested" |
int main(int argc, char **argv) { |
printf("[start_test]\n"); |
// Open the library. |
char *lib_name = "./libRatings.A.dylib"; |
void *lib_handle = dlopen(lib_name, RTLD_NOW); |
if (lib_handle) { |
printf("[%s] dlopen(\"%s\", RTLD_NOW): Successful\n", __FILE__, lib_name); |
} |
else { |
printf("[%s] Unable to open library: %s\n", |
__FILE__, dlerror()); |
exit(EXIT_FAILURE); |
} |
// Get the symbol addresses. |
void (*addRating)(char*) = dlsym(lib_handle, "addRating"); |
if (addRating) { |
printf("[%s] dlsym(lib_handle, \"addRating\"): Successful\n", __FILE__); |
} |
else { |
printf("[%s] Unable to get symbol: %s\n", |
__FILE__, dlerror()); |
exit(EXIT_FAILURE); |
} |
char *(*meanRating)(void) = dlsym(lib_handle, "meanRating"); |
if (meanRating) { |
printf("[%s] dlsym(lib_handle, \"meanRating\"): Successful\n", __FILE__); |
} |
else { |
printf("[%s] Unable to get symbol: %s\n", |
__FILE__, dlerror()); |
exit(EXIT_FAILURE); |
} |
void (*clearRatings)(void) = dlsym(lib_handle, "clearRatings"); |
if (clearRatings) { |
printf("[%s] dlsym(lib_handle, \"clearRatings\"): Successful\n", __FILE__); |
} |
else { |
printf("[%s] Unable to get symbol: %s\n", |
__FILE__, dlerror()); |
exit(EXIT_FAILURE); |
} |
int (*ratings)(void) = dlsym(lib_handle, "ratings"); |
if (ratings) { |
printf("[%s] dlsym(lib_handle, \"ratings\"): Successful\n", __FILE__); |
} |
else { |
printf("[%s] Unable to get symbol: %s\n", |
__FILE__, dlerror()); |
exit(EXIT_FAILURE); |
} |
// Setup. |
addRating(NULL); |
addRating(""); |
addRating("*"); |
addRating("**"); |
addRating("***"); |
addRating("*****"); |
addRating("*****"); |
// ratings. |
printf("[%s] ratings(): %s\n", __FILE__, (ratings() == 6? PASSFAIL)); |
// meanRating. |
printf("[%s] meanRating(): %s\n", __FILE__, (strcmp(meanRating(), "**") == 0)? PASSFAIL); |
// clearRatings. |
clearRatings(); |
printf("[%s] clearRatings(): %s\n", __FILE__, (ratings() == 0? PASSFAIL)); |
// Close the library. |
if (dlclose(lib_handle) == 0) { |
printf("[%s] dlclose(lib_handle): Successful\n", __FILE__); |
} |
else { |
printf("[%s] Unable to open close: %s\n", |
__FILE__, dlerror()); |
} |
printf("[end_test]\n"); |
return 0; |
} |
Runtime
программа очень подобна Dependent
программа. Однако Время выполнения должно загрузиться libRuntime.A.dylib
использование dlopen(3) OS X Developer Tools Manual Page
функция. После этого это должно получить адрес каждой функции, экспортируемой использованием библиотеки dlsym(3) OS X Developer Tools Manual Page
перед использованием его.
Следующая команда генерирует Runtime
клиентская программа.
[Ratings/1.0]% make runtime |
clang Runtime.c -o Runtime |
Перечисление 7 показывает вывод, произведенный Временем выполнения.
Результаты испытаний перечисления 7 для Оценок 1.0 как загруженная временем выполнения библиотека
> ./Runtime |
[start_test] |
[Ratings.c] initializer() |
[Runtime.c] dlopen("./libRatings.A.dylib", RTLD_NOW): Successful |
[Runtime.c] dlsym(lib_handle, "addRating"): Successful |
[Runtime.c] dlsym(lib_handle, "meanRating"): Successful |
[Runtime.c] dlsym(lib_handle, "clearRatings"): Successful |
[Runtime.c] dlsym(lib_handle, "ratings"): Successful |
[Runtime.c] ratings(): Passed |
[Runtime.c] meanRating(): Passed |
[Runtime.c] clearRatings(): Passed |
[Runtime.c] dlclose(lib_handle): Successful |
[end_test] |
[Ratings.c] finalizer() |
Обратите внимание на то, что функция инициализатора библиотеки Ratings вызвана в main
функциональное выполнение перед вызовом к dlopen
возвраты, который отличается от его точки выполнения в Dependent
распечатка программ 5. finalizer
функция, однако, вызвана после main
вышел, та же точка, в которой это вызывают в выполнении Зависимого. Необходимо рассмотреть это при записи динамических инициализаторов библиотеки и финализаторов.
Обновление библиотек
Создание версий ранее опубликованной динамической библиотеки является хрупкой задачей. Если Вы хотите, чтобы существующие клиенты были в состоянии пользоваться библиотекой (т.е. загрузите новую версию .dylib
файл без перекомпиляции), необходимо гарантировать, что API, о котором знают те клиенты, неизменен, включая его семантическое значение.
Когда можно гарантировать, что API новой версии библиотеки совместим с API, о котором знают клиенты, соединенные с более ранними версиями, можно считать новую версию незначительной версией. Когда Вы хотите опубликовать незначительную версию динамической библиотеки пользователям клиентов библиотеки (например, пользователи программы, пользующейся Вашей библиотекой), единственный элемент информации о версии, который необходимо изменить, является текущей версией библиотеки. Основная версия (имя файла) и версия совместимости библиотеки должна остаться тем же. Когда конечные пользователи заменяют раннюю версию библиотеки с новой версией в их компьютерах, клиенты библиотеки используют новую версию без любых проблем. Управление Клиентской Совместимостью С Зависимыми библиотеками.
Внесение совместимых изменений
С точки зрения клиентского изображения существует две стороны к совместимости с динамическими библиотеками, которыми это пользуется: совместимость с версиями библиотеки позже, чем та, которую клиент знает о, известный как прямая совместимость и совместимость с версиями библиотеки ранее, чем та клиент, знакома с, известный как обратная совместимость. Вы поддерживаете прямую совместимость путем обеспечения, что API библиотеки остается совместимой через версии. Вы упрощаете обратную совместимость путем экспорта новых функций как слабых ссылок. В свою очередь, клиенты Вашей библиотеки должны гарантировать, чтобы слабо импортированные функции фактически существовали перед использованием их.
Библиотека Ratings, представленная в Создании Библиотек, имеет вторую версию, 1.1. Перечисление 8 показывает обновленный заголовок для библиотеки Ratings.
Интерфейс перечисления 8 к оценкам 1.1
/* File: Ratings.h |
* Interface to libRatings.A.dylib 1.1. |
*************************************/ |
#define WEAK_IMPORT __attribute__((weak_import)) |
/* Adds 'rating' to the set. |
* rating: Each character adds 1 to the numeric rating |
* Example: "" = 0, "*" = 1, "**" = 2, "wer " = 4. |
*/ |
void addRating(char* rating); |
/* Returns the number of ratings in the set. |
*/ |
int ratings(void); |
/* Returns the mean rating of the set. |
*/ |
char* meanRating(void); |
/* Returns the medianRating of the set. |
*/ |
WEAK_IMPORT |
char *medianRating(void); // 1 |
/* Returns the most frequent rating of the set. |
*/ |
WEAK_IMPORT |
char *frequentRating(void); // 2 |
/* Clears the set. |
*/ |
void clearRatings(void); |
Маркированные строки объявляют новые функции. Заметьте, что оба объявления включают weak_import
атрибут, сообщая разработчикам клиента, что слабо соединяется функция. Поэтому клиенты должны удостовериться, что функция существует прежде, чем вызвать его.
Перечисление 9 показывает новый файл реализации библиотеки.
Реализация перечисления 9 оценок 1.1
/* File: Ratings.c |
* Compile with -fvisibility=hidden. |
**********************************/ |
#include "Ratings.h" |
#include <Averages.h> |
#include <stdio.h> |
#include <string.h> |
#include <float.h> |
#define EXPORT __attribute__((visibility("default"))) |
#define MAX_NUMBERS 99 |
//#define MAX_NUMERIC_RATING 10 // published in Ratings.h |
static char *_char_rating(float rating) { |
char result[10] = ""; |
int int_rating = (int)(rating + 0.5); |
for (int i = 0; i < int_rating; i++) { |
strncat(result, "*", sizeof(result) - strlen(result) - 1); |
} |
return strdup(result); |
} |
EXPORT |
void addRating(char *rating) { |
if (rating != NULL) { |
int numeric_rating = 0; |
int pos = 0; |
while (*rating++ != '\0' && pos++ < 5) { |
numeric_rating++; |
} |
add((float)numeric_rating); // libAverages.A:add() |
} |
} |
EXPORT |
char *meanRating(void) { |
return _char_rating(mean()); // libAverages.A:mean() |
} |
EXPORT |
char *medianRating(void) { |
return _char_rating(median()); // libAverages.A:median() |
} |
EXPORT |
char *frequentRating(void) { |
int lib_mode = mode(); // libAverages.A:mode() |
return _char_rating(lib_mode); |
} |
EXPORT |
int ratings(void) { |
return count(); // libAverages.A:count() |
} |
EXPORT |
void clearRatings(void) { |
clear(); // libAverages.A:clear() |
} |
/* Ratings.c revision history |
* 1. First version. |
* 2. Added medianRating, frequentRating. |
* Removed initializer, finalizer. |
*/ |
Перечисление 10 показывает обновленный исходный код для Исполняемой программы, тестирующей библиотеку Ratings 1.1.
Перечисление 10 , Тестирующее Оценки 1.1 как загруженная временем выполнения библиотека
/* Runtime.c |
* Tests libRatings.A.dylib 1.1 as a runtime-loaded library. |
**********************************************************/ |
#include <stdio.h> |
#include <stdlib.h> |
#include <dlfcn.h> |
#include <string.h> |
#include "Ratings.h" |
#define PASSFAIL "Passed":"Failed" |
#define UNTST "Untested" |
int main(int argc, char** argv) { |
printf("[start_test]\n"); |
// Open the library. |
char *lib_name = "./libRatings.A.dylib"; |
void *lib_handle = dlopen(lib_name, RTLD_NOW); |
if (lib_handle) { |
printf("[%s] dlopen(\"%s\", RTLD_NOW): Successful\n", __FILE__, lib_name); |
} |
else { |
printf("[%s] Unable to open library: %s\n", |
__FILE__, dlerror()); |
exit(EXIT_FAILURE); |
} |
// Get the function addresses. |
void (*addRating)(char*) = dlsym(lib_handle, "addRating"); |
if (addRating) { |
printf("[%s] dlsym(lib_handle, \"addRating\"): Successful\n", __FILE__); |
} |
else { |
printf("[%s] Unable to get symbol: %s\n", |
__FILE__, dlerror()); |
exit(EXIT_FAILURE); |
} |
char* (*meanRating)(void) = dlsym(lib_handle, "meanRating"); |
if (meanRating) { |
printf("[%s] dlsym(lib_handle, \"meanRating\"): Successful\n", __FILE__); |
} |
else { |
printf("[%s] Unable to get symbol: %s\n", |
__FILE__, dlerror()); |
exit(EXIT_FAILURE); |
} |
void (*clearRatings)(void) = dlsym(lib_handle, "clearRatings"); |
if (clearRatings) { |
printf("[%s] dlsym(lib_handle, \"clearRatings\"): Successful\n", __FILE__); |
} |
else { |
printf("[%s] Unable to get symbol: %s\n", |
__FILE__, dlerror()); |
exit(EXIT_FAILURE); |
} |
int (*ratings)(void) = dlsym(lib_handle, "ratings"); |
if (ratings) { |
printf("[%s] dlsym(lib_handle, \"ratings\"): Successful\n", __FILE__); |
} |
else { |
printf("[%s] Unable to get symbol: %s\n", |
__FILE__, dlerror()); |
exit(EXIT_FAILURE); |
} |
char *(*medianRating)(void) = dlsym(lib_handle, "medianRating"); // weak import |
char *(*frequentRating)(void) = dlsym(lib_handle, "frequentRating"); // weak import |
// Setup. |
addRating(NULL); |
addRating(""); |
addRating("*"); |
addRating("**"); |
addRating("***"); |
addRating("*****"); |
addRating("*****"); |
// ratings. |
printf("[%s] ratings(): %s\n", __FILE__, (ratings() == 6? PASSFAIL)); |
// meanRating. |
printf("[%s] meanRating(): %s\n", __FILE__, (strcmp(meanRating(), "**") == 0)? PASSFAIL); |
// medianRating. |
if (medianRating) { |
printf("[%s] medianRating(): %s\n", __FILE__, (strcmp(medianRating(), "**") == 0? PASSFAIL)); |
} |
else { |
printf("[%s] medianRating(): %s\n", __FILE__, UNTST); |
} |
// frequentRating. |
if (frequentRating) { |
char* test_rating = "*****"; |
int test_rating_size = sizeof(test_rating); |
printf("[%s] frequentRating(): %s\n", __FILE__, strncmp(test_rating, frequentRating(), test_rating_size) == 0? PASSFAIL); |
} |
else { |
printf("[%s] mostFrequentRating(): %s\n", __FILE__, UNTST); |
} |
// clearRatings. |
clearRatings(); |
printf("[%s] clearRatings(): %s\n", __FILE__, (ratings() == 0? PASSFAIL)); |
// Close the library. |
if (dlclose(lib_handle) == 0) { |
printf("[%s] dlclose(lib_handle): Successful\n", __FILE__); |
} |
else { |
printf("[%s] Unable to open close: %s\n", |
__FILE__, dlerror()); |
} |
printf("[end_test]\n"); |
return 0; |
} |
Обновление информации о версии библиотеки
Перечисление 11 показывает, что команда раньше генерировала версию 1.1 библиотеки Ratings.
Перечисление 11 , Генерирующее версию 1.1 Оценок динамическая библиотека
[Ratings/1.1]% make dylib |
clang -dynamiclib -std=gnu99 Ratings.c -I<user_home>/include <user_home>/lib/libAverages.dylib -current_version 1.1 -compatibility_version 1.0 -fvisibility=hidden -o libRatings.A.dylib |
Заметьте, что на сей раз текущая версия библиотеки установлена в 1,1, но ее версия совместимости остается 1.0. Когда клиент соединяется против версии 1.1 этой библиотеки, версия совместимости кодируется в клиентском изображении. Поэтому динамический загрузчик загружает клиентское изображение, находит ли это версию 1.1 или 1.0 libRatings.A.dylib
. Т.е. клиент назад совместим с версией 1.0 библиотеки. Для получения дополнительной информации, на почему /usr/local/lib/libAverages.dylib
был добавлен к строке компиляции, посмотрите пользующиеся Зависимые библиотеки.
Тестирование новой версии библиотеки
Подобный созданию начальных версий библиотеки, обновляющие библиотеки требуют полного тестирования. Необходимо, по крайней мере, добавить тесты для каждой функции, которую Вы добавили к своим тестовым программам. При использовании слабых ссылок тестовые программы должны гарантировать существование новых функций прежде, чем вызвать их.
Ratings/1.1
каталог в пакете сопутствующего файла этого документа содержит исходные файлы для библиотеки Ratings 1.1. Перечисление 12 показывает обновленную версию Dependent
программа. Маркированные строки показывают, как проверить на присутствие функции перед использованием его.
Перечисление 12 , Тестирующее Оценки 1.1 как зависимая библиотека
/* Dependent.c |
* Tests libRatings.A.dylib 1.1 as a dependent library. |
*****************************************************/ |
#include <stdio.h> |
#include <string.h> |
#include "Ratings.h" |
#define PASSFAIL "Passed":"Failed" |
#define UNTST "Untested" |
int main(int argc, char **argv) { |
printf("[start_test]\n"); |
// Setup. |
addRating(NULL); |
addRating(""); |
addRating("*"); |
addRating("**"); |
addRating("***"); |
addRating("*****"); |
addRating("*****"); |
// ratings. |
printf("[%s] ratings(): %s\n", |
__FILE__, (ratings() == 6? PASSFAIL)); |
// meanRating. |
printf("[%s] meanRating(): %s\n", |
__FILE__, (strcmp(meanRating(), "**") == 0)? PASSFAIL); |
// medianRating. |
if (medianRating) { // 1 |
printf("[%s] medianRating(): %s\n", |
__FILE__, (strcmp(medianRating(), "**") == 0? PASSFAIL)); |
} |
else { |
printf("[%s] medianRating(): %s\n", __FILE__, UNTST); |
} |
// frequentRating. |
if (frequentRating) { // 2 |
char *test_rating = "*****"; |
int test_rating_size = sizeof(test_rating); |
printf("[%s] frequentRating(): %s\n", |
__FILE__, strncmp(test_rating, frequentRating(), |
test_rating_size) == 0? PASSFAIL); |
} |
else { |
printf("[%s] mostFrequentRating(): %s\n", |
__FILE__, UNTST); |
} |
// clearRatings. |
clearRatings(); |
printf("[%s] clearRatings(): %s\n", |
__FILE__, (ratings() == 0? PASSFAIL)); |
printf("[end_test]\n"); |
return 0; |
} |
Оценки 1.1 зависят от Средних чисел 1.1. Поэтому создавать библиотеку, а также тестовые программы, libAverages.A.dylib
должен быть установлен в ~/lib
. Для выполнения этого выполните эту команду после открытия пакета сопутствующего файла этого документа:
[Avarages/1.1]% make install |
Это команды, должен был скомпилировать библиотеку и тестовые программы от Ratings/1.1
каталог:
[Ratings/1.1]% make |
clang -dynamiclib -std=gnu99 Ratings.c -I<user_home>/include <user_home>/lib/libAverages.dylib -current_version 1.1 -compatibility_version 1.0 -fvisibility=hidden -o libRatings.A.dylib |
clang Dependent.c libRatings.A.dylib <user_home>/lib/libAverages.dylib -o Dependent |
clang Runtime.c -o Runtime |
Вывод продукты программы показан в Перечислении 13.
Результаты испытаний перечисления 13 для Оценок 1,1 используемых как зависимая библиотека
> ./Dependent |
[start_test] |
[Dependent.c] ratings(): Passed |
[Dependent.c] meanRating(): Passed |
[Dependent.c] medianRating(): Passed |
[Dependent.c] frequentRating(): Passed |
[Dependent.c] clearRatings(): Passed |
[end_test] |
Программы то использование dlopen
загрузить Вашу библиотеку не должно перестать работать, если они неспособны получить адрес для Ваших слабо экспортируемых функций с dlsym
. Поэтому программа, тестирующая Вашу библиотеку как загруженную временем выполнения библиотеку, должна допускать отсутствие слабо импортированных функций.
Перечисление 14 показывает обновленную версию Runtime
программа. Заметьте, что это использует dlsym для попытки, получают адреса новых функций, но не выходит с ошибкой, если они недоступны. Однако непосредственно перед тем, как программа использует новые функции, она определяет, существуют ли они фактически. Если они не существуют, это не выполняет тест.
Перечисление 14 , Тестирующее Оценки 1.1 как загруженная временем выполнения библиотека
/* Runtime.c |
* Tests libRatings.A.dylib 1.1 as a runtime-loaded library. |
**********************************************************/ |
#include <stdio.h> |
#include <stdlib.h> |
#include <dlfcn.h> |
#include <string.h> |
#include "Ratings.h" |
#define PASSFAIL "Passed":"Failed" |
#define UNTST "Untested" |
int main(int argc, char **argv) { |
printf("[start_test]\n"); |
// Open the library. |
char* lib_name = "./libRatings.A.dylib"; |
void* lib_handle = dlopen(lib_name, RTLD_NOW); |
if (lib_handle) { |
printf("[%s] dlopen(\"%s\", RTLD_NOW): Successful\n", __FILE__, lib_name); |
} |
else { |
printf("[%s] Unable to open library: %s\n", |
__FILE__, dlerror()); |
exit(EXIT_FAILURE); |
} |
// Get the function addresses. |
void (*addRating)(char*) = dlsym(lib_handle, "addRating"); |
if (addRating) { |
printf("[%s] dlsym(lib_handle, \"addRating\"): Successful\n", __FILE__); |
} |
else { |
printf("[%s] Unable to get symbol: %s\n", |
__FILE__, dlerror()); |
exit(EXIT_FAILURE); |
} |
char* (*meanRating)(void) = dlsym(lib_handle, "meanRating"); |
if (meanRating) { |
printf("[%s] dlsym(lib_handle, \"meanRating\"): Successful\n", __FILE__); |
} |
else { |
printf("[%s] Unable to get symbol: %s\n", |
__FILE__, dlerror()); |
exit(EXIT_FAILURE); |
} |
void (*clearRatings)(void) = dlsym(lib_handle, "clearRatings"); |
if (clearRatings) { |
printf("[%s] dlsym(lib_handle, \"clearRatings\"): Successful\n", __FILE__); |
} |
else { |
printf("[%s] Unable to get symbol: %s\n", |
__FILE__, dlerror()); |
exit(EXIT_FAILURE); |
} |
int (*ratings)(void) = dlsym(lib_handle, "ratings"); |
if (ratings) { |
printf("[%s] dlsym(lib_handle, \"ratings\"): Successful\n", __FILE__); |
} |
else { |
printf("[%s] Unable to get symbol: %s\n", |
__FILE__, dlerror()); |
exit(EXIT_FAILURE); |
} |
char* (*medianRating)(void) = dlsym(lib_handle, "medianRating"); // weak import |
char* (*frequentRating)(void) = dlsym(lib_handle, "frequentRating"); // weak import |
// Setup. |
addRating(NULL); |
addRating(""); |
addRating("*"); |
addRating("**"); |
addRating("***"); |
addRating("*****"); |
addRating("*****"); |
// ratings. |
printf("[%s] ratings(): %s\n", __FILE__, (ratings() == 6? PASSFAIL)); |
// meanRating. |
printf("[%s] meanRating(): %s\n", __FILE__, (strcmp(meanRating(), "**") == 0)? PASSFAIL); |
// medianRating. |
if (medianRating) { |
printf("[%s] medianRating(): %s\n", __FILE__, (strcmp(medianRating(), "**") == 0? PASSFAIL)); |
} |
else { |
printf("[%s] medianRating(): %s\n", __FILE__, UNTST); |
} |
// frequentRating. |
if (frequentRating) { |
char* mfr = "*****"; |
printf("[%s] frequentRating(): %s\n", __FILE__, strncmp(mfr, frequentRating(), sizeof(mfr)) == 0? PASSFAIL); |
} |
else { |
printf("[%s] mostFrequentRating(): %s\n", __FILE__, UNTST); |
} |
// clearRatings. |
clearRatings(); |
printf("[%s] clearRatings(): %s\n", __FILE__, (ratings() == 0? PASSFAIL)); |
// Close the library. |
if (dlclose(lib_handle) == 0) { |
printf("[%s] dlclose(lib_handle): Successful\n", __FILE__); |
} |
else { |
printf("[%s] Unable to open close: %s\n", |
__FILE__, dlerror()); |
} |
printf("[end_test]\n"); |
return 0; |
} |
Перечисление 15 показывает вывод, произведенный Временем выполнения.
Результаты испытаний перечисления 15 для Оценок 1,1 используемых как загруженная временем выполнения библиотека
[Ratings/1.1]% ./Runtime |
[start_test] |
[Runtime.c] dlopen("./libRatings.A.dylib", RTLD_NOW): Successful |
[Runtime.c] dlsym(lib_handle, "addRating"): Successful |
[Runtime.c] dlsym(lib_handle, "meanRating"): Successful |
[Runtime.c] dlsym(lib_handle, "clearRatings"): Successful |
[Runtime.c] dlsym(lib_handle, "ratings"): Successful |
[Runtime.c] ratings(): Passed |
[Runtime.c] meanRating(): Passed |
[Runtime.c] medianRating(): Passed |
[Runtime.c] frequentRating(): Passed |
[Runtime.c] clearRatings(): Passed |
[Runtime.c] dlclose(lib_handle): Successful |
[end_test] |
Гарантировать, что клиенты соединились с версией 1.0 libRatings.A.dylib
может использовать версию 1.1 библиотеки, можно скопировать Ratings/1.1/libRatings.A.dylib
к Ratings/1.0
. Когда Вы выполняете первое Dependent
программа, ее вывод является идентичным выводу, который она произвела, когда она использовала версию 1.0 библиотеки. Первое Dependent
программа ничего не знает о функциях, представленных в версии 1.1 библиотеки Ratings; поэтому, это не вызывает их.
Более интересный тест, однако, удостоверяется, что клиенты, соединенные с версией 1.1 библиотеки Ratings, могут работать, когда их копия библиотеки заменяется версией 1.0. Для тестирования этого выполните следующие команды в Терминале:
[ ]% cd <companion_dir>/Ratings/1.1 |
[Ratings/1.1]% make |
[Ratings/1.1]% cd ../1.0 |
[Ratings/1.0]% make |
[Ratings/1.0]% cp libRatings.A.dylib ../1.1 |
[Ratings/1.0]% cd ../1.1 |
Перечисление 16 показывает вывод, произведенный второй версией Dependent
, соединенный с Оценками 1.1, когда это загружает Оценки 1.0 вместо Оценок 1.1. Выделенные строки показывают, где клиентская программа не находила определенную функцию во время выполнения, потому что функция не существует в версии библиотеки Ratings, которой это пользуется.
Результаты испытаний перечисления 16 для Оценок 1,0 используемых как зависимая библиотека клиентом соединились против Оценок 1.1
[Ratings/1.1]% ./Dependent |
[Ratings.c] initializer() |
[start_test] |
[Dependent.c] ratings(): Passed |
[Dependent.c] meanRating(): Passed |
[Dependent.c] medianRating(): Untested |
[Dependent.c] mostFrequentRating(): Untested |
[Dependent.c] clearRatings(): Passed |
[end_test] |
[Ratings.c] finalizer() |
Вторая версия Runtime
тестовая программа производит подобный вывод при использовании Оценок 1.0, как показано в Перечислении 17.
Результатам испытаний перечисления 17 для Оценок 1,0 используемых как загруженная временем выполнения библиотека клиентом понравилось с Оценками 1.1 при использовании Оценок 1.0
[Ratings/1.1]% ./Runtime |
[start_test] |
[Ratings.c] initializer() |
[Runtime.c] dlopen("./libRatings.A.dylib", RTLD_NOW): Successful |
[Runtime.c] dlsym(lib_handle, "addRating"): Successful |
[Runtime.c] dlsym(lib_handle, "meanRating"): Successful |
[Runtime.c] dlsym(lib_handle, "clearRatings"): Successful |
[Runtime.c] dlsym(lib_handle, "ratings"): Successful |
[Runtime.c] ratings(): Passed |
[Runtime.c] meanRating(): Passed |
[Runtime.c] medianRating(): Untested |
[Runtime.c] mostFrequentRating(): Untested |
[Runtime.c] clearRatings(): Passed |
[Runtime.c] dlclose(lib_handle): Successful |
[end_test] |
[Ratings.c] finalizer() |