Создание динамических библиотек

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

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

Создание библиотек

При создании динамической библиотеки необходимо выполнить эти задачи:

Следующие разделы обеспечивают пример процесса, взятого для разработки простой динамической библиотеки, названной Оценками. Файлы, упомянутые в следующих разделах, включены в 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;
}

Следующий список описывает маркированные строки.

  1. Этот комментарий только, чтобы напомнить разработчикам библиотеки компилировать этот файл с компилятором -fvisibility=hidden опция, так, чтобы только символы с visibility("default") атрибут экспортируется.

  2. Этот инициализатор определяется только для показа, в которой точке выполнения клиента инициализаторы библиотеки вызывает динамический загрузчик.

  3. Этот финализатор определяется только для показа, в которой точке выполнения клиента финализаторы библиотеки вызывает динамический загрузчик.

  4. _add функция является примером внутренней функции. Клиенты не должны знать об этом и, поэтому, это не экспортируется. Кроме того, потому что внутренним вызовам доверяют, никакая проверка не выполняется. Однако нет никакого требования, чтобы внутреннее использование функционировало проверка отсутствия.

  5. 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()