Mac OS X Panther for Unix Geeks

The Executable and Linking Format (ELF), developed by the Unix System Laboratories, is common in the Unix world. On ELF systems, there is no distinction between shared libraries and loadable modules; shared code can be used as a library for dynamic loading. ELF is the default binary format on Linux, Solaris 2. x , and SVR4. Since these systems cover a large share of the Unix base, most Unix developers have experience on ELF systems. Thus, it may come as a surprise to experienced Unix developers that shared libraries and loadable modules are not the same on Mac OS X. This is because the binary format used in Mac OS X is Mach-O , which is different from ELF.

Mach-O shared libraries have the file type MH_DYLIB and the .dylib (dynamic library) suffix and can be linked to with static linker flags. So, if you have a shared library named libcool.dylib , you can link to this library by specifying the - lcool flag. Although shared libraries cannot be loaded dynamically as modules, they can be loaded through the dyld API (see the manpage for dyld , the dynamic link editor). It is important to point out that shared libraries cannot be unloaded.

Loading a Bundle

You cannot link directly against a bundle. Instead, bundles must be dynamically loaded and unloaded by the dyld APIs. When porting Unix software, you'll often need to translate dlopen( ) function calls to dylib actions. You can implement a temporary fix by using the dlcompat library functions, included in Panther's libSystem.dylib; /usr/lib/libdl.dylib is provided as a symbolic link to libSystem.dylib .

The dlopen( ) , dlclose( ) , dlsym( ) , dlerror( ) functions provide interfaces to the dynamic linker using the native dyld( ) , NSModule( ) , and NSObjectFileImage( ) functions. This makes porting common Unix source code relatively painless. (See the manpages on these functions for more details.)

The ideal solution however, especially when writing new code for Mac OS X, is to use the dyld APIs. See The Apple Developer Connection's Technical Note TN2071 (http://developer.apple.com/technotes/tn2002/tn2071.html) for more details and examples demonstrating how to convert code to make use of the dyld APIs.

Another common porting problem on earlier versions of Mac OS X was the lack of the System V poll( ) system call function. Panther solves this problem by emulating the poll( ) function as an interface to the BSD native select( ) API.

Loadable modules, called bundles in Mac OS X, have the file type MH_BUNDLE. Most Unix-based software ports usually produce bundles with a .so extension, to maintain consistency across platforms. Although Apple recommends giving bundles a .bundle extension, it isn't mandatory.

You must use special flags with cc when compiling a shared library or a bundle on Darwin. One difference between Darwin and many other Unix systems is that no position-independent code (PIC) flag is needed, since it is the default for Darwin. Next, since the linker does not allow common symbols, the compiler flag - fno-common is required for both shared libraries and bundles. (A common symbol is one that is defined multiple times. You should instead define a symbol once and use C's extern keyword to declare it in places where it is needed.)

To build a shared library, use cc 's - dynamiclib option. Use the - bundle option to build a loadable module or bundle.

9.3.1 Building a Shared Library

Suppose you want to create a shared library containing one or more C functions, such as the one shown in Example 9-3.

Example 9-3. A simple C program

/* * answer.c: The answer to life, the universe, and everything. */ int get_answer( ) { return 42; }

If you compile the program containing the function into a shared library, you can test it with the program shown in Example 9-4.

Example 9-4. Compiling answer.c into a shared library

/* * deep_thought.c: Obtain the answer to life, the universe, * and everything, and act startled when you actually hear it. */ #include <stdio.h> int main( ) { int the_answer; the_answer = get_answer( ); printf("The answer is... %d\n", the_answer); fprintf(stderr, "%d??!!\n", the_answer); return 0; }

The makefile shown in Example 9-5 compiles and links the library, and then compile, link, and execute the test program.

Example 9-5. Sample makefile for creating and testing a shared library

# Makefile: Create and test a shared library. # # Usage: make test # CC = cc LD = cc CFLAGS = -O -fno-common all: deep_thought # Create the shared library. # answer.o: answer.c $(CC) $(CFLAGS) -c answer.c libanswer.dylib: answer.o $(LD) -dynamiclib -install_name libanswer.dylib \ -o libanswer.dylib answer.o # Test the shared library with the deep_thought program. # deep_thought.o: deep_thought.c $(CC) $(CFLAGS) -c deep_thought.c deep_thought: deep_thought.o libanswer.dylib $(LD) -o deep_thought deep_thought.o -L. -lanswer test: all ./deep_thought clean: rm -f *.o core deep_thought libanswer.dylib

The preceding makefile made use of the ld flag - install_name , which is the Mach-O analog of - soname , used for building shared libraries on ELF systems. The - install_name flag is used to specify where the executable, linked against it, should look for the library. The - install_name in the makefile shown in Example 9-5 specifies that the deep_thought executable is to look for the library libanswer.dylib in the same directory as the executable itself. The command otool can be used to verify this:

$ otool -L deep_thought deep_thought: libanswer.dylib (compatibility version 0.0.0, current version 0.0.0) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 71.0.0)

The - install_name flag is often used with @execution_path to specify a relative pathname of the library. The pathname of the library is relative to the executable. For example, change the makefile in Example 9-5 by adding an install target:

install: libanswer.dylib cp libanswer.dylib ../lib/.

Then add install to the all target's dependency list and change the libanswer target to the following:

libanswer.dylib: answer.o $(LD) -dynamiclib -install_name \ @execution_path/../lib/libanswer.dylib \ -o libanswer.dylib answer.o

The deep_thought executable built using this makefile will then look for the libanswer.dylib in the ../lib directory. Output from otool shows this change:

$ otool -L deep_thought deep_thought: @execution_path/../lib/libanswer.dylib (compatibility version 0.0.0, current version 0.0.0) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 71.0.0)

The - install_name flag is often used with @execution_path when building a private framework associated with an application, since private frameworks are located within the application's contents.

9.3.2 Dynamically Loading Libraries

You can turn answer.o into a bundle, which can be dynamically loaded using the commands shown in Example 9-6.

Example 9-6. Commands for converting answer.o into a bundle

cc -bundle -o libanswer.bundle answer.o

You do not need to specify the bundle at link time. Instead, use the dyld functions NSCreateObjectFileImageFromFile and NSLinkModule to load the library. Then, you can use NSLookupSymbolInModule and NSAddressOfSymbol to access the symbols that the library exports. Example 9-7 loads libanswer.bundle and invokes the get_answer function. Example 9-7 is similar to Example 9-4, but many lines (shown in bold) have been added.

Example 9-7. Dynamically loading a bundle and invoking a function

/* * deep_thought_dyld.c: Obtain the answer to life, the universe, * and everything, and act startled when you actually hear it. */ #include <stdio.h> #import <mach-o/dyld.h> int main( ) { int the_answer; int rc; // Success or failure result value NSObjectFileImage img; // Represents the bundle's object file NSModule handle; // Handle to the loaded bundle NSSymbol sym; // Represents a symbol in the bundle int (*get_answer) (void); // Function pointer for get_answer /* Get an object file for the bundle. */ rc = NSCreateObjectFileImageFromFile("libanswer.bundle", &img); if (rc != NSObjectFileImageSuccess) { fprintf(stderr, "Could not load libanswer.bundle.\n"); exit(-1); } /* Get a handle for the bundle. */ handle = NSLinkModule(img, "libanswer.bundle", FALSE); /* Look up the get_answer function. */ sym = NSLookupSymbolInModule(handle, "_get_answer"); if (sym == NULL) { fprintf(stderr, "Could not find symbol: _get_answer.\n"); exit(-2); } /* Get the address of the function. */ get_answer = NSAddressOfSymbol(sym); /* Invoke the function and display the answer. */ the_answer = get_answer( ); printf("The answer is... %d\n", the_answer); fprintf(stderr, "%d??!!\n", the_answer); return 0; }

For more information on these functions, see the NSObjectFileImage , NSModule , and NSSymbol manpages. To compile the code in Example 9-7, use the following command:

cc -O -fno-common -o deep_thought_dyld deep_thought_dyld.c

9.3.3 Two-Level Namespaces

In Mac OS X 10.0, the dynamic linker merged symbols into a single (flat) namespace. So, if you link against two different libraries that both define the same function, the dynamic linker complains because the same symbol was defined in both places. This approach prevented collisions that were known at compile time. However, a lack of conflict at compile time does not guarantee that a future version of the library won't introduce a conflict.

Suppose you linked your application against Version 1 of libfoo and Version 1 of libbar . At the time you compiled your application, libfoo defined a function called logerror ( ) , and libbar did not. But when Version 2 of libbar came out, it included a function called logerror( ) . Since the conflict was not known at compile time, your application doesn't expect libbar to contain this function. If your application happens to load libbar before libfoo , it will call libbar 's logerror( ) method, which is not what you want.

So, Mac OS X 10.1 introduced two-level namespaces, which the compiler uses by default. (Neither Mac OS X 10.2 or 10.3 introduced any changes to two-level namespaces.) With this feature, you can link against Version 1 of libfoo and libbar . The linker creates an application that knows logerror( ) lives in libfoo . So, even if a future version of libbar includes a logerror( ) function, your application will know which logerror( ) it should use.

If you want to build an application using a flat namespace, use the - flat_ namespace linker flag (see the ld manpage for more details).

Категории