# The Kernel Kit: Images

Declared in: be/kernel/image.h

Library: libroot.so

## Overview

This isn't about graphics. An image is compiled code. There are three types of images:

- An app image is an application. Every application has a single app image.

- A library image is a dynamically linked library (a "shared library"). Most applications link against the system libraries (libroot.so, libbe.so, and so on) that Be provides.

- An add-on image is an image that you load into your application as it's running. Symbols from the add-on image are linked and references are resolved when the image is loaded. An add-on image provides a sort of "heightened dynamic linking" beyond that of a DLL.

The following sections explain how to load and run an app image, how to create a shared library, and how to create and load an add-on image.

### Loading an App Image

Loading an app image is like running a "sub-program." The image that you load is launched in much the same way as had you double-clicked it in the Browser, or launched it from the command line. It runs in its own team--it doesn't share the address space of the application from which it was launched--and, generally, leads its own life.

Any application can be loaded as an app image; you don't need to issue special compile instructions or otherwise manipulate the binary. The one requirement of an app image is that it must have a `main()` function; hardly a restrictive request.

To load an app image, you call the `load_image()` function, the protocol for which is:

       thread_id load_image(int32 argc,
                const char **argv,
                const char **env)

The function's first two arguments identify the app image (file) that you want to launch--we'll return to this in a moment. Having located the file, the function creates a new team, spawns a main thread in that team, and then returns the `thread_id` of that thread to you. The thread that's returned is the executable's main thread. It won't be running: To make it run you pass the `thread_id` to `resume_thread()` or `wait_for_thread()` (as explained in the major section "Threads and Teams").

The argc/argv argument pair is copied and forwarded to the new thread's `main()` function:

- The first string in the argv array must be the name of the image file that you want to launch; `load_image()` uses this string to find the file. You then install any other arguments you want in the array, and terminate the array with a `NULL` entry. argc is set to the number of entries in the argv array (not counting the terminating `NULL`). It's the caller's responsibility to free the argv array after `load_image()` returns (remember--the array is copied before it's passed to the new thread).

- envp is an array of environment variables that are also passed to `main()`. Typically, you use the global `environ` pointer (which you must declare as an `extern`--see the example, below). You can, of course, create your own environment variable array: As with the argv array, the envp array should be terminated with a `NULL` entry, and you must free the array when`load_image()` returns (that is, if you allocated it yourself--don't try to free `environ`).

The following example demonstrates a typical use of `load_image()`. First, we include the appropriate files and declare the necessary variables:

       #include <image.h>  /* load_executable() */
       #include <OS.h>     /* wait_for_thread() */
       #include <stdlib.h> /* malloc() */

       char **arg_v; /* choose a name that doesn't collide with argv */
       int32 arg_c; /* same here vis a vis argc */
       thread_id exec_thread;
       int32 return_value;

Install, in the `arg_v` array, the "command line" arguments. Let's pretend we're launching a program found in /boot/home/apps/adder that takes two integers, adds them together, and returns the result as `main()`'s exit code. Thus, there are three arguments: The name of the program, and the values of the two addends converted to strings. Since there are three arguments, we allocate `arg_v` to hold four pointers (to accommodate the final `NULL`). Then we allocate and copy the arguments.

       arg_c = 3;
       arg_v = (char **)malloc(sizeof(char *) * (arg_c + 1));

       arg_v[0] = strdup("/boot/home/apps/adder");
       arg_v[1] = strdup("5");
       arg_v[2] = strdup("3");
       arg_v[3] = NULL;

Now that everything is properly set up, we call `load_image()`. After the function returns, it's safe to free the allocated `arg_v` array:

       exec_thread = load_image(arg_c, arg_v, environ);
       free(arg_v);

At this point, `exec_thread` is suspended (the natural state of a newly-spawned thread). In order to retrieve its return value, we use`wait_for_thread()` to tell the thread to run:

       wait_for_thread(exec_thread, &return_value);

After `wait_for_thread()` returns, the value of `return_value` should be 8 (i.e. 5 + 3).

### Creating a Shared Library

The primary documentation for creating a shared library is provided by MetroWerks in their CodeWarrior manual. Beyond the information that you find there, you should be aware of the following amendments and caveats:

- You mustn't export your library's symbols through the `-export all` linker flag. Instead, use the `__declspec()` directive to export each symbol. The macro is described below. If you're compiling for the PPC, you must also export `#pragma` symbols; to do this from the BeIDE, go to the Linker/PEF portion of the Settings window and set "Export Symbols" to "Use #pragma".

- The loader looks for libraries by following the `LIBRARY_PATH` environment variable. The default library path looks like this:

          $ echo $LIBRARY_PATH
          %A/lib:/boot/home/config/lib:/boot/beos/system/lib

where "%A" means the directory that contains the app that the user is lauching.

#### Exporting and Importing Symbols

If you're developing a shared library you should explicitly export every global symbol in your library by using the `__declspec()` macro. To export a symbol, you declare it thus...

       __declspec(dllexport) type name

...where "_declspec(dllexport)" is literal, and type and name declare the symbol. Some examples:

       __declspec(dllexport) char *some_name;
       __declspec(dllexport) void some_func() {...}
       class __declspec(dllexport) MyView {...}

To import these symbols, an app that wants to use your library must "reverse" the declaration by replacing `dllexport` with `dllimport`:

       __declspec(dllimport) char *some_name;
       __declspec(dllimport) void some_func();
       class __declspec(dllimport) MyView;

The trouble with this system is that it implies two sets of headers, one for exporting (for building your library) and another for importing (that the library client would use). The Be libraries use macros, defined in be/BeBuild.h, that throw the import/export switch so the header files can be unified. For example, here's the macro for libbe:

       #if _BUILDING_be
       #define _IMPEXP_BE     __declspec(dllexport)
       #else
       #define _IMPEXP_BE __declspec(dllimport)
       #endif

When libbe is being built, a private compiler directive defines `_BUILDING_be` to be non-zero, and `_IMPEXP_BE` exports symbols. When a developer includes BeBuild.h, the `_BUILDING_be` variable is set to zero, so `_IMPEXP_BE` is set to import symbols.

You may want to emulate this system by defining macros for your own libraries. This implies that you have to define a compiler switch (analogous to _BUILDING_be) yourself. Set the switch to non-zero when you're building your library, and then set it to zero when you publish your headers for use by library clients.

### Creating and Using an Add-on Image

An add-on image is indistinguishable from a shared library image. Creating an add-on is exactly like creating a shared library, a topic that we breezed through above, but with a couple of minor tweaks:

- The loader looks for add-ons by following the paths in the `ADDON_PATH` environment variable. The default `ADDON_PATH` looks like this:

          $ echo $ADDON_PATH
          %A/add-ons:/boot/home/config/add-ons:/boot/beos/system/add-ons

- You have to export your add-on symbols, and you also must `extern` "C" them. This ensures that the symbol names won't be mangled by the compiler.

#### Exporting Add-on Symbols

To export your add-on's symbols, declare them thus:

       extern "C" __declspec(dllexport) void some_func();
       extern "C" __declspec(dllexport) int32 some_global_data;

To extern a C++ class takes more work. You can't extern the class directly; typically what you do is create (and extern) a C function that covers the class constructor:

       extern "C" __declspec(dllexport) MyClass *instantiate_my_class();

`instantiate_my_class()` is implemented to call the MyClass constructor:

       MyClass *instantiate_my_class()
       {
         return new MyClass();
       }

#### Loading an Add-on Image

To load an add-on into your application, you call the `load_add_on()` function. The function takes a pathname (absolute or relative to the current working directory) to the add-on file, and returns an `image_id` number that uniquely identifies the image across the entire system.

For example, let's say you've created an add-on image that's stored in the file /boot/home/add-ons/adder. The code that loads the add-on would look like this:

       /* For brevity, we won't check errors.  */
       image_id addon_image;

       /* Load the add-on. */
       addon_image = load_add_on("/boot/home/add-ons/adder");

Unlike loading an executable, loading an add-on doesn't create a separate team, nor does it spawn another thread. The whole point of loading an add-on is to bring the image into your application's address space so you can call the functions and fiddle with the variables that the add-on defines.

#### Symbols

After you've loaded an add-on into your application, you'll want to examine the symbols (variables and functions) that it has brought with it. To get information about a symbol, you call the `get_image_symbol()` function:

       status_t get_image_symbol(image_id image,
                char *symbol_name,
                int32 symbol_type,
                void **location)

The function's first three arguments identify the symbol that you want to get:

- The first argument is the`image_id` of the add-on that owns the symbol.

- The second argument is the symbol's name. This assumes, of course, that you know the name, and that the add-on has declared the name as `extern`. In general, using an add-on implies just this sort of cooperation.

- The third argument is a constant that gives the symbol's symbol type. There are three types, as given below. If the executable format doesn't distinguish between text and data symbols, then you can use any of these three types--they'll all be the same. If the format does distinguish between text and data, then you can either ask for the specific type, or you can ask for `B_SYMBOL_TYPE_ANY`.

Constant | Meaning
`B_SYMBOL_TYPE_DATA` | Global data (variables)
`B_SYMBOL_TYPE_TEXT` | Functions
`B_SYMBOL_TYPE_ANY` | The symbol lives anywhere

The function returns, by reference in its final argument, a pointer to the symbol's address. For example, let's say the `adder` add-on code looks like this:

       extern "C" int32 a1 = 0;
       extern "C" int32 a2 = 0;
       extern "C" int32 adder(void);

       int32 adder(void)
       {
          return (a1 + a2);
       }

To examine the variables (`a1` and `a2`), you would call `get_image_symbol()` thus:

       int32 *var_a1, *var_a2;

       get_image_symbol(addon_image, "a1", B_SYMBOL_TYPE_DATA, &var_a1);
       get_image_symbol(addon_image, "a2", B_SYMBOL_TYPE_DATA, &var_a2);

Here we get the symbol for the `adder()` function:

       int32 (*func_add)();
       get_image_symbol(addon_image, "adder", B_SYMBOL_TYPE_TEXT, &func_add);

Now that we've retrieved all the symbols, we can set the values of the two addends and call the function:

       *var_a1 = 5;
       *var_a2 = 3;
       int32 return_value = (*func_add)();

## Image Functions

### get_image_info(), get_next_image_info(), image_info

          status_t get_image_info(image_id image, image_info *info)

          status_t get_next_image_info(team_id team,
             int32 *cookie,
             image_info *info)

          struct {} image_info

These functions copy, into the info argument, the `image_info` structure for a particular image. The `get_image_info()` function gets the information for the image identified by image.

The `get_next_image_info()` function lets you step through the list of a team's images through iterated calls. The team argument identifies the team you want to look at; a team value of 0 means the team of the calling thread. The cookie argument is a placemark; you set it to 0 on your first call, and let the function do the rest. The function returns `B_BAD_VALUE` when there are no more images to visit:

       /* Get the image_info for every image in this team. */
       image_info info;
       int32 cookie = 0;

       while (get_next_image_info(0, &cookie, &info) == B_OK)
          ...

The `image_info` structure is defined as:

          typedef struct {
                image_id id;
                 image_type  type;
                int32  sequence;
                int32  init_order;
                B_PFV init_routine;
                B_PFV term_routine;
                dev_t device;
                ino_t node;
                 char  name[MAXPATHLEN];
                 void  *text;
                  void  *data;
                int32  text_size;
                 int32  data_size;
             } image_info

The fields are:

- `id.` The image's `image_id` number.

- `type`. A constant (listed below) that tells whether this is an app, library, or add-on image.

- `sequence` and `init_order`. These are zero-based ordinal numbers that give the order in which the image was loaded and initialized, compared to all the other images in this team.

- `init_routine` and `term_routine`. These are pointers to the functions that are used to intialize and terminate the image (more specifically, the image's main thread). The `B_PFV` type is a cover for a pointer to a `(void`*) function.

- `device`. The device that the image file lives on.

- `node`. The node number of the image file.

- `name`. The full pathname of the file whence sprang the image.

- `text` and `text_size`. The address and the size (in bytes) of the image's text segment.

- `data` and `data_size`. The address and size of the image's data segment.

The self-explanatory `image_type` constants are:

Constant
`B_APP_IMAGE`
`B_LIBRARY_IMAGE`
`B_ADD_ON_IMAGE`

RETURN CODES

- `B_NO_ERROR`. The image was found; info contains valid information.
- `B_BAD_VALUE`. image doesn't identify an existing image, team doesn't identify an existing team, or there are no more images to visit.

### get_image_symbol(), get_nth_image_symbol()

          status_t get_image_symbol(image_id image,
             char *symbol_name,
             int32 symbol_type,
             void **location)

          status_t get_nth_image_symbol(image_id image,
             int32 n,
             char *name,
             int32 *name_length,
             int32 *symbol_type,
             void **location)

`get_image_symbol()` returns, in location, a pointer to the address of the symbol that's identified by the image, symbol_name, and symbol_type arguments. An example demonstrating the use of this function is given in "Symbols."

`get_nth_image_symbol()` returns information about the n'th symbol in the given image. The information is returned in the arguments:

- name is the name of the symbol. You have to allocate the name buffer before you pass it in--the function copies the name into the buffer.

- You point name_length to an integer that gives the length of the name buffer that you're passing in. The function uses this value to truncate the string that it copies into name. The function then resets name_length to the full (untruncated) length of the symbol's name (plus one byte to accommodate a terminating `NULL`). To ensure that you've gotten the symbol's full name, you should compare the in-going value of name_length with the value that the function sets it to. If the in-going value is less than the full length, you can then re-invoke `get_nth_image_symbol()` with an adequately lengthened name buffer, and an increased name_length value.

  Keep in mind that name_length is reset each time you call `get_nth_image_symbol()`. If you're calling the function iteratively (to retrieve all the symbols in an image), you need to reset the name_length value between calls.

- The function sets symbol_type to `B_SYMBOL_TYPE_DATA` if the symbol is a variable, `B_SYMBOL_TYPE_TEXT` if the symbol is a function, or `B_SYMBOL_TYPE_ANY` if the executable format doesn't distinguish between the two. The argument's value going into the function is of no consequence.

- The function sets location to point to the symbol's address.

To retrieve `image_id` numbers on which these functions can act, use the `get_next_image_info()` function. Such numbers are also returned directly when you load an add-on image through the `load_add_on()` function.

RETURN CODES

- `B_NO_ERROR`. The symbol was found.
- `B_BAD_IMAGE_ID`. image doesn't identify an existing image.
- `B_BAD_INDEX`. n is out-of-bounds.

### load_add_on(), unload_add_on()

          image_id load_add_on(const char *pathname)
          status_t unload_add_on(image_id image)

`load_add_on()` loads an add-on image, identified by pathname, into your application's address space.

- pathname can be absolute or relative; if it's relative, it's reckoned off of the current working directory.

- The function returns an `image_id` (a positive integer) that represents the loaded image. Image ID numbers are unique across the system.

An example that demonstrates the use of`load_add_on()` is given in "Loading an Add-on Image."

You can load the same add-on image twice; each time you load the add-on a new, unique `image_id` is created and returned.

`unload_add_on()` removes the add-on image identified by the argument. The image's symbols are removed, and the memory that they represent is freed. If the argument doesn't identify a valid image, the function returns `B_ERROR`. Otherwise, it returns `B_NO_ERROR`.

RETURN CODES

- Positive `image_id` value (load) or `B_NO_ERROR` (unload). Success.
- `B_ERROR`. The image couldn't be loaded (for whatever reason), or image isn't identify a valid imag ID.

### load_image()

          thread_id load_image(int argc,
             const char **argv,
             const char **env)

Loads an app image into the system (it doesn't load the image into the caller's address space), creates a separate team for the new application, and spawns and returns the ID of the team's main thread. The image is identified by the pathname given in argv[0].

The arguments are passed to the image's `main()` function (they show up there as the function's similarly named arguments):

- argc gives the number of entries that are in the argv array.

- The first string in the argv array must be the name of the image file. You then install any other arguments you want in the array, and terminate the array with a `NULL` entry. Note that the value of argc shouldn't count argv's terminating `NULL`.

- envp is an array of environment variables that are also passed to `main()`. Typically, you use the global `environ` pointer:

          extern char **environ;

          load_image(..., environ);

The argv and envp arrays are copied into the new thread's address space. If you allocated either of these arrays, it's safe to free them immediately after `load_image()` returns.

The thread that's returned by`load_image()` is in a suspended state. To start the thread running, you pass the `thread_id` to `resume_thread()` or `wait_for_thread()`.

An example that demonstrates the use of `load_image()` is given in "Loading an App Image."

RETURN CODES

- Positive integers. Success.
- `B_ERROR`. Failure, for whatever reason.

The Be Book, in lovely HTML, for BeOS Release 3.

Copyright © 1998 Be, Inc. All rights reserved.

Last modified March 26, 1998.