The Kernel Kit Table of Contents     The Kernel Kit Index

Image Concepts

An image is compiled code. There are three types of image:

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 Tracker, 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.

To load an app image, you call the load_image() function:

   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 returns the thread_id of the thread to you. The thread isn't running: To make it run you pass the thread_id to resume_thread() or wait_for_thread() (as explained in "Threads and Teams").

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

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);
   
   while (--arg_c >= 0)
      free(arg_v[arg_c]);
   
   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:

      $ 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:

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

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:

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)();

The Kernel Kit Table of Contents     The Kernel Kit Index


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

Copyright © 2000 Be, Inc. All rights reserved..