< Previous PageNext Page > Hide TOC

Using Dynamic Libraries

When you need to use a dynamic library in your product, you have to install the library in your computer. You may use dynamic libraries as dependent libraries (by specifying them in your product’s link line) or as runtime loaded libraries (by loading them when they are needed, using dlopen).

This article describes the process of installing and using dynamic libraries. It’s based on the Ratings dynamic library and the StarMeals, StarMeals2, and Grades applications, which are included in this document’s companion-file package. This article also shows how to use dynamic libraries as dependent libraries or as runtime-loaded libraries. Finally, this article demonstrates how to interpose the functions exported by a dynamic library.

Contents:

Installing Dependent Libraries
Using Dependent Libraries
Using Runtime-Loaded Libraries
Interposing Functions in Dependent Libraries


Installing Dependent Libraries

Before you can use a dynamic library as a dependent library, the library and its header files must be installed on your computer. The standard locations for header files are ~/include, /usr/local/include and /usr/include. The standard locations for dynamic libraries are ~/lib, /usr/local/lib, and /usr/lib.

You may also place the .dylib file at a nonstandard location in your file system, but you must add that location to one of these environment variables:

For details on how to add paths to these environment variables, see “Opening Dynamic Libraries.” To learn about installing dependent libraries in a relocatable directory, see “Run-Path Dependent Libraries.”

If you don’t want to change the environment variables and you want to place the dynamic library in a nonstandard location, you must specify where in your file system you placed the library when you link your image. See the description of the gcc -dylib_file option in the gcc man page for details.

For example, in Mac OS X the executable code of applications can be packaged together with frameworks containing libraries created specifically for a particular application. These frameworks are known as private embedded frameworks. Applications that use private embedded frameworks, as well as the frameworks themselves, must be specially built. See “Creating a Framework” in Framework Programming Guide and “Loading Code at Runtime” in Mach-O Programming Topics for details.

“Using Dependent Libraries” requires that the Averages 1.1 and Ratings 1.1 dynamic libraries be installed on your computer. To install these libraries:

  1. Open this document’s companion-file package.

  2. In Terminal, execute these commands:

    [Averages/1.1]% make install
    [Ratings/1.1]% make install

    Note: To uninstall the libraries, execute these commands:

    [Averages/1.1]% make uninstall
    [Ratings/1.1]% make uninstall

Using Dependent Libraries

Using dynamic libraries as dependent libraries by linking your image against them provides several benefits, including producing smaller executable files and not having to get the address of the libraries’ exported symbols before using them in your code. However, you still have to make sure a weakly imported symbol exists before using it.

All you need to do to use a dynamic library as a dependent library is include the library’s headers in your source code and link the library against your application or library. The library’s headers describe the symbols you can use. You should not use any other symbols to access the library’s functionality. Otherwise, you may get unexpected results, or your image may stop working for its users when they update the dependent library in their computers.

Listing 1 shows the source code of a small program that uses Ratings 1.1, developed in “Creating Dynamic Libraries.”

Listing 1  Using Ratings 1.1 as a dependent library

/* File: StarMeals.c
 * Uses functions in libRatings.A.dylib.
 **************************************/
 
#include <stdio.h>
#include <string.h>
#include <Ratings.h>
 
#define MAX_NAMES 100
#define MAX_NAME_LENGTH 30
#define MAX_RATING_LENGTH 5
 
static char* name_list[MAX_NAMES];
static char* rating_list[MAX_NAMES];
static int names = 0;
 
void addNameAndRating(char* name, char* rating) {
    name_list[names] = strdup(name);
    rating_list[names] = (strlen(rating) > MAX_RATING_LENGTH)? "*****" : strdup(rating);
    names++;
}
 
void test_data(void) {
    addNameAndRating("Spinach", "*");
    addNameAndRating("Cake", "****");
    addNameAndRating("Steak", "****");
    addNameAndRating("Caviar", "*");
    addNameAndRating("Broccoli", "****");
    addNameAndRating("Gagh", "*****");
    addNameAndRating("Chicken", "*****");
}
 
int main(int argc, char* argv[]) {
    int test_mode = 0;
    if (argc == 2) {
        if (strcmp(argv[1], "test") == 0) {
            test_mode = 1;
            printf("[start_test]\n");
            test_data();
        }
    }
    else {
        printf("Enter meal names and ratings in the form <name> <rating>.\n");
        printf("No spaces are allowed in the name and the rating.\n");
        printf("The rating can be * through *****.\n");
        printf("To finish, enter \"end\" for a meal name.\n");
        while (names < MAX_NAMES) {
            char name[MAX_NAME_LENGTH];
            char rating[MAX_RATING_LENGTH + 1];
            printf("\nName and rating: ");
            scanf("%s", &name);
            if (strcmp(name, "end") == 0) {
                break;
            }
            scanf("%s", rating);
            addNameAndRating(name, rating);
        }
        printf("\n");
    }
 
    if (names) {
        // Print data entered and call libRatings.addRating().
        printf("This is the data you entered:\n");
        for (int i = 0; i < names; i++) {
            printf("%s (%s)\n", name_list[i], rating_list[i]);
            addRating(rating_list[i]);
        }
 
        // Print statistical information.
        printf("\nThe mean rating is %s\n", meanRating()); // 1
        if (medianRating) {                                // 2
            printf("The median rating is %s\n", medianRating());
        }
        if (frequentRating) {                              // 3
            printf("The most frequent rating is %s\n", frequentRating());
        }
 
        //printf("\n");
    }
 
    if (test_mode) {
        printf("[end_test]\n");
    }
    return 0;
}

This list describes the highlighted lines:

To compile the StarMeals.c file, use the command shown in Listing 2.

Listing 2  Compiling and linking StarMeals using only the link line

[StarMeals]% make
gcc -std=gnu99 StarMeals.c ~/lib/libRatings.dylib ~/lib/libAverages.dylib -o StarMeals

Notice that the exact location of the library StarMeals directly depends (libRatings.dylib) is provided at the link line. The pathname ~/lib/libRatings.dylib is actually a symbolic link to ~/lib/libRatings.A.dylib. At link time, the static linker resolves the link and stores the library’s actual filename in the image it generates. With this approach, the dynamic linker always uses the library’s complete name when it looks for an image’s dependent libraries.

The static linker must also have access to the library the Ratings library depends on (libAverages.A.dylib), either through the link line (as Listing 2 shows) or by having a symbolic link to that library in the current working directory, as shown in Listing 3. This requirement stems from the fact that the static linker attempts to resolve all the dependent library’s undefined external symbols. Any unresolved undefined external symbol causes the static linker to cancel the link operation. The location of libAverages.A.dylib at launch time doesn’t necessarily have to be the same location of the library at link time.

Listing 3  Compiling and linking StarMeals using the link line and a symbolic link

[StarMeals]% ln -s ~/lib/libAverages.A.dylib
[StarMeals]% gcc -std=gnu99 StarMeals.c ~/lib/libRatings.A.dylib -o StarMeals

Listing 4 shows the output StarMeals produces when run in test mode:

Listing 4  Test output of the StarMeals application

> ./StarMeals test
start_test
This is the data you entered:
Spinach (*)
Cake (****)
Steak (****)
Caviar (*)
Broccoli (****)
Gagh (*****)
Chicken (*****)
 
The mean rating is ***
The medianRating is ****
The most frequent rating is ****
[end_test]

Using Runtime-Loaded Libraries

An image that uses dynamic libraries as runtime-loaded libraries is smaller and loads faster than the image using the same libraries as dependent libraries. The static linker doesn’t add information about the runtime-loaded libraries to the image. And the dynamic loader doesn’t have to load the library’s dependent libraries when the image is loaded. However, this flexibility comes at a price. Before an image can use a dynamic library that is not one of its dependent libraries, it must load the library with dlopen and get the address of each symbol it needs with dlsym. The image must also call dlclose when it’s done using the library.

The StarMeals2 program provides the same functionality that StarMeals provides. But StarMeals2 uses the Ratings 1.1 dynamic library as a runtime loaded library. Listing 5 shows the program’s source code.

Listing 5  Using Ratings 1.1 as a runtime loaded library

/* File: StarMeals2.c
 * Uses functions in libRatings.A.dylib.
 **************************************/
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
#include <Ratings.h>
 
#define MAX_NAMES 100
#define MAX_NAME_LENGTH 30
#define MAX_RATING_LENGTH 5
 
static char* name_list[MAX_NAMES];
static char* rating_list[MAX_NAMES];
static int names = 0;
 
void addNameAndRating(char* name, char* rating) {
    name_list[names] = strdup(name);
    rating_list[names] =
        (strlen(rating) > MAX_RATING_LENGTH)?
        "*****" : strdup(rating);
    names++;
}
 
void test_data(void) {
    addNameAndRating("Spinach", "*");
    addNameAndRating("Cake", "****");
    addNameAndRating("Steak", "****");
    addNameAndRating("Caviar", "*");
    addNameAndRating("Broccoli", "****");
    addNameAndRating("Gagh", "*****");
    addNameAndRating("Chicken", "*****");
}
 
int main(int argc, char* argv[]) {
    int test_mode = 0;
    if (argc == 2) {
        if (strcmp(argv[1], "test") == 0) {
            test_mode = 1;
            printf("[start_test]\n");
            test_data();
        }
    }
    else {
        printf("Enter restaurant names and ratings in the form <name> <rating>.\n");
        printf("No spaces are allowed in the name and the rating.\n");
        printf("The rating can be * through *****.\n");
        printf("To finish, enter \"end\" for a restaurant name.\n");
        while (names < MAX_NAMES) {
            char name[MAX_NAME_LENGTH];
            char rating[MAX_RATING_LENGTH + 1];
            printf("\nName and rating: ");
            scanf("%s", &name);
            if (strcmp(name, "end") == 0) {
                break;
            }
            scanf("%s", rating);
            addNameAndRating(name, rating);
        }
        printf("\n");
    }
 
    if (names) {
        // Open Ratings library.
        void* lib_handle = dlopen("libRatings.A.dylib", RTLD_LOCAL|RTLD_LAZY);
        if (!lib_handle) {
            printf("[%s] Unable to load library: %s\n", __FILE__, dlerror());
            exit(EXIT_FAILURE);
        }
 
        // Print data entered and call libRatings.A:addRating().
        void (*addRating)(char*) = dlsym(lib_handle, "addRating");
        if (!addRating) {       // addRating is guaranteed to exist in libRatings.A.dylib
            printf("[%s] Unable to get symbol: %s\n", __FILE__, dlerror());
            exit(EXIT_FAILURE);
        }
        printf("This is the data you entered:\n");
        for (int i = 0; i < names; i++) {
            printf("%s (%s)\n", name_list[i], rating_list[i]);
            addRating(rating_list[i]);
        }
 
        // Print statistical information.
        char* (*meanRating)(void) = dlsym(lib_handle, "meanRating");
        if (!meanRating) {      // meanRating is guaranteed to exist in libRatings.A.dylib
            printf("[%s] Unable to get symbol: %s\n", __FILE__, dlerror());
            exit(EXIT_FAILURE);
        }
        printf("\nThe mean rating is %s\n", meanRating());
 
        char* (*medianRating)(void) = dlsym(lib_handle, "medianRating");
        if (medianRating) {     // Backwards compatibility with Ratings 1.0
            printf("The median rating is %s\n", medianRating());
        }
        char* (*frequentRating)(void) = dlsym(lib_handle, "frequentRating");
        if (frequentRating) {   // Backwards compatibility with Ratings 1.0
            printf("The most frequent rating is %s\n", frequentRating());
        }
 
        // Close Ratings library
        if (dlclose(lib_handle) != 0) {
            printf("[%s] Problem closing library: %s", __FILE__, dlerror());
        }
    }
 
    if (test_mode) {
        printf("[end_test]\n");
    }
    return 0;
}

Listing 6 shows to compile the StarMeals2 application.

Listing 6  Compiling and linking StarMeals2

[StarMeals2]% make
gcc -std=gnu99 StarMeals2.c -I<home_dir>/include -o StarMeals2

The static linker doesn’t complain about the unresolved external references in libRatings.A.dylib because it’s not included at the link line. The dynamic linker resolves these references when StarMeals2 uses dlopen to load libRatings.A.dylib.

Interposing Functions in Dependent Libraries

Sometimes you need to perform operations before or after a function is called to gather statistical data or to modify its inputs our outputs. For example, you may want to find out how many times a program calls a specific function to determine whether an algorithm should be optimized. However, you may not always have access to the function’s source code to make the modifications. Interposition is a mechanism through which you can define your own version of a function that’s defined in an image’s dependent libraries. In your version, you may or may not call the original function.

Note: In Mac OS X you can interpose only dependent libraries. Symbols in runtime loaded libraries cannot be interposed.

To call an interposed function from a custom definition, you use the dlsym(RTLD_NEXT, "<function_name>") call to get the address of the “real” function. For example, Listing 7 shows how you may write a custom version of a function defined in a dynamic library.

Listing 7  Interposing a function

char* name(void) {
    static int name_calls = 0;
    printf("[STATS] name() has been called %i times\n", name_calls);
    char* (*next_name)(void) = dlsym(RTLD_NEXT, "name");
    return next_name();
}

You may use interposition to adapt an existing dynamic library to your particular needs without changing its API. For example, this document’s companion package includes the implementations of two dynamic libraries called Ratings and RatingsAsGrades. The Ratings library implements a star-based rating system (it can be used to tally restaurant and hotel ratings; for example, *** and *****). The RatingsAsGrades library implements a letter-based grading system, which can be used to tally student grades; for example A and C. Instead of writing a new algorithm to manage letter grades, the RatingsAsGrades library leverages the functionality of the Ratings library. Listing 8 shows the interface and implementation of the RatingsAsGrades library.

Listing 8  RatingsAsGrades Interposing Ratings

/* File: RatingsInterposed.h
 * Interface to the RatingsAsGrages dynamic library.
 **************************************************/
 
/* Adds 'grade' to the set.
 *      grade: Non-NULL string. Contains zero or
 *             one of these characters:
 *             A, B, C, D, F.
 *             Examples: "", "A", "B".
 */
void addRating(char* grade);
 
/* Returns the median grade.
 */
char* medianRating(void);
 
/* Returns the most frequent grade.
 */
char* frequentRating(void);
 
/* File: RatingsAsGrades.c
 * Compile with -fvisibility=hidden.
 ***********************************/
 
#include "RatingsAsGrades.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
 
#define EXPORT __attribute__((visibility("default")))
#define MAX_STAR_RATING_LEN 6
 
static char* _ratingAsGrade(char* rating) {
    int rating_length = strlen(rating);
    if (rating_length > MAX_STAR_RATING_LEN - 1) {
        rating_length = MAX_STAR_RATING_LEN - 1;
    }
    char grade;
    switch (rating_length) {
        case 5:
            grade = 'A';
            break;
        case 4:
            grade = 'B';
            break;
        case 3:
            grade = 'C';
            break;
        case 2:
            grade = 'D';
            break;
        case 1:
            grade = 'F';
            break;
        default:
            grade = '\0';
    }
    char char_grade[2] = { grade, '\0' };
    return strdup(char_grade);
}
 
// Interpose libRatings.B.dylib:addRating.
EXPORT
void addRating(char* grade) {
    char rating[MAX_STAR_RATING_LEN] = { '\0' };
    switch (*grade) {
        case 'A':
            strcat(rating, "*");
        case 'B':
            strcat(rating, "*");
        case 'C':
            strcat(rating, "*");
        case 'D':
            strcat(rating, "*");
        case 'F':
            strcat(rating, "*");
        default:
            ;
    }
    void (*next_addRating)(char*) =
dlsym(RTLD_NEXT, "addRating");
    if (next_addRating) {
        next_addRating(rating);
    }
    else {
        printf("[%s] Fatal problem: %s", dlerror());
    }
}
 
// Interpose libRatings.B.dylib:medianRating.
EXPORT
char* medianRating(void) {
    char medianGrade[2] = { '\0' };
    char* (*next_medianRating)(void) =
dlsym(RTLD_NEXT, "medianRating");
    if (next_medianRating) {
        strcpy(medianGrade,
_ratingAsGrade(next_medianRating()));
    }
    else {
        printf("[%s] Fatal problem: %s", dlerror());
        exit(EXIT_FAILURE);
    }
    return strdup(medianGrade);
}
 
// Interpose libRatings.B.dylib:frequentRating.
EXPORT
char* frequentRating(void) {
    char frequentGrade[2] = { '\0' };
    char* (*next_frequentRating)(void) =
        dlsym(RTLD_NEXT, "frequentRating");
    if (next_frequentRating) {
        strcpy(frequentGrade,
            _ratingAsGrade(next_frequentRating()));
    }
    else {
        printf("[%s] Fatal problem: %s", dlerror());
        exit(EXIT_FAILURE);
    }
    return strdup(frequentGrade);
 
}

Notice how the addRating, medianRating, and frequentRating functions, modify the input and output of the definitions they shadow.

The companion-files package includes the source code of the Grades application. This application uses the RatingsAsGrades library to tally the grades of students.

Follow these instructions to build and run the Grades application:

  1. Open this document’s companion-files package.

  2. In Terminal, perform these commands:

    [Ratings/1.1]% make install
    [Grades]% make

Listing 9 shows the output of the Grades application when ran in test mode.

Listing 9  Test output of the Grades application

[Grades]% ./Grades test
[start_test]
This is the data you entered:
Eloise  (F)
Karla   (B)
Iva     (B)
Hilaire (F)
Jewel   (B)
Simone  (A)
Yvette  (A)
Renee   (A)
Mimi    (A)
 
The median grade is B
The most frequent grade is A
[end_test]


< Previous PageNext Page > Hide TOC


© 2009 Apple Inc. All Rights Reserved. (Last updated: 2009-02-26)


Did this document help you?
Yes: Tell us what works for you.
It’s good, but: Report typos, inaccuracies, and so forth.
It wasn’t helpful: Tell us what would have helped.