Issue 2-48, December 3, 1997

Be Engineering Insights: Writing Add-ons in C++

By Cyril Meurillon

When the compiler sees a function, it creates a symbol that encapsulates the function's prototype. This is called "name mangling"; the mangled symbol name is how your application's compiled code identifies the functions that it calls.

It used to be that few application developers needed to worry about how a function's name was mangled. But as more and more programmers use "add-ons," the issue of understanding and decoding mangled names becomes more important. This article explains name mangling basics, how mangling affects add-ons, and what you can do to make your life (and the lives of the developers that will load your add-ons) easier.

Here's a simple name-mangling example. Compiling for the PPC, the Metrowerks compiler turns this function declaration:

int fred(unsigned long, int *);

into this:

fred__FUlPi

The amusingly euphonious "FUlPi" actually means something:

Let's look at an example of a C++ member function:

void foo::fred(const char *, short);

This becomes:

fred__3fooFPCcs

Now assume you're writing an add-on in C++. Let's consider first the simple case of exporting a function. (We'll look at exporting classes later.)

Here's our add-on source:

// declaration, possibly in a separate header file
#pragma export on
int fred(unsigned long, int *);
#pragma export reset

// definition
int fred(unsigned long i, int *p)
{
    return p[i];
}

Here's the code that loads the add-on, does the symbol lookup, and then invokes the function:

image_id imid;
int (*f)(unsigned long, int*);

static int p[] = { 9, 2, 7, 1 };

// Load the add-on
imid = load_add_on("my_add_on");
if (imid < 0) {
  ... // add-on not found
}

// Find the symbol for the fred() function
if (get_image_symbol(imid, "fred__FUlPi",
    B_SYMBOL_TYPE_TEXT, &f) !=  B_NO_ERROR) {
  ... // symbol not found
}

// Invoke the function
printf("f(%d, %.8x) = %d\n", 1, &p[0], (*f)(1, p));
...

Tres bien, you think. But you have sinned in your ignorance. The mangling scheme that I have just described applies to the PowerPC compiler. It does not apply to the Metrowerks compiler for Intel. That means you have just written code that is not portable. Our function fred() would actually be mangled this way for Intel:

?fred@@YAHKPAH@Z

Just as cryptic as the PPC—and even more fun to say out loud.

The best solution to this problem is to avoid it altogether by "externing" your add-on functions. The fred() function declaration, for example, would look like this:

#pragma export on
extern "C" int fred(unsigned long, int *);
#pragma export reset

The definition remains unchanged.

The lookup code also remains unchanged, with the exception of the symbol name that is now unmangled:

// Lookup the extern'ed fred() function.
if (get_image_symbol(imid, "fred",
    B_SYMBOL_TYPE_TEXT, &f) !=  B_NO_ERROR)

This code (when compiled for the target platform) will run on both the PPC and Intel versions of the BeOS.

Let's now talk about how to export a class from an add-on. Because the class constructors and destructor are mangled, we cannot invoke them directly in a portable manner. The solution is to write and export a function that creates an instance of our class. Of course, this function needs to be externed to avoid name mangling.

Let's say we want to export a class Pet.

Here's Pet.h:

class Pet {
  public:
    Pet(const char *name);
    virtual ~Pet();
    virtual const char *name();

  private:
    char buf[32];
};

And Pet.cpp:

#include <string.h>
#include "Pet.h"

#pragma export on
extern "C" Pet * instantiate_pet(const char *name);
#pragma export reset

Pet *instantiate_pet(const char *name)
{
  return new Pet(name);
}

Pet::Pet(const char *name)
{
  strncpy(buf, name, 32);
  buf[31] = '\0';
}

Pet::~Pet()
{
}

const char *Pet::name()
{
  return &buf[0];
}

And here's the application code that loads the add-on and instantiates the object:

#include "Pet.h"

...
image_id  imid;
Pet *myPet;
Pet *(*f)(const char *);

imid = load_add_on("Pet");
if (imid < 0) {
   ... // add-on not found
}

if (get_image_symbol(imid, "instantiate_pet",
    B_SYMBOL_TYPE_TEXT, &f) != B_NO_ERROR)  {
      ... // symbol not found
}
myPet= (*f)("fido");
printf("pet name is %s\n", myPet->name());
delete myPet;
...

Note that only instantiate_pet() is exported from the add-on. Because all the functions called from the application are virtual (Pet::~Pet() and Pet::name()), their invocations go through the virtual table, which is pointed to by the instantiated object. Therefore no symbol of class Pet is explicitly referenced by the application.

In a future article, we'll look at a more difficult—and more practical —example, such as subclassing BView in an add-on.


A Remembrance of Things Past: Processor-Independent Device Drivers

By Arve Hjønnevåg

If you have previously written or are now writing a device driver for BeOS you may be interested in learning how to make it work on the upcoming BeOS for x86 hardware. In order for a driver to work on different hardware platforms, it must be independent of any special processor feature. To help you avoid creating dependencies, I've formulated four general rules for writing device drivers:

  1. Do not use assembly language without providing a fallback for other processors.

  2. Use endian-independent code to access external devices.

  3. Use system functions for all timing and delays.

  4. Use macros to access ISA I/O.

If you follow these rules, a simple recompile will make the driver work on a different processor, in most cases. For further explanation, read on.

First, if you use assembly language, always provide a C version of the same code and use the preprocessor to include the right code at compile time. A simple example might look like this:

#if __POWERPC__
__asm long long
longlongzero()
{
  li  r4, 0
  li  r3, 0
  blr
}
#elif __INTEL__
__declspec(naked)
longlongzero()
{
  asm {
    mov  eax, 0
    mov edx, 0
    ret
  }
}
#else
long long
longlongzero()
{
  return 0;
}
#endif

Second, the device that the driver controls may have commands and arguments in a specific byte order. Let's say that our device has a 16-bit command register mapped in memory at address MYCMDADDR. The device expects a command in little endian byte order and it defines two commands: LAUNCH=0x0100 and DISARM=0x0001.

To make the driver source code processor-independent, use the macro B_HOST_TO_LENDIAN_INT16(arg), defined in support/byteorder.h, whenever you write to the command register:

void
guard_thread()
{
  while(1) {
    *MYCMDADDR = B_HOST_TO_LENDIAN_INT16( DISARM );
    snooze(1000000);
  }
}

The third item on the checklist is timing. Take a device that has a command register but provides no acknowledgment that the command has been received. If a command is written to the command register before the device reads the first command, the first command will be overwritten. Since we know, however, that the device processes a command in 5 microseconds, if the driver needs to write two commands, we just put a 5 microsecond delay, spin (5), between the two commands. In cases when the register is used by different functions, the driver may work fine without adding any delay—though this is bound to break on a future system.

One way to ensure that no command is missed is by always adding a delay after sending a command. But although this method always works, it is only efficient when commands are sent in large chunks. A better way to solve the problem is by always accessing the command register through one function. This will add a delay only when it is needed. The following function shows you how to do this:

void
SendCommand(command_t command)
{
  static bigtime_t lasttime = 0;
  while((lasttime + 5) > system_time()) ;
  *COMMANDADDR = B_HOST_TO_xENDIAN_COMMAND_T(command);
  lasttime = system_time();
}

Fourth and finally, we need to macro-ize access to ISA I/O. On PowerPC hardware the ISA I/O space is mapped in memory and can be accessed by normal memory operations, while the x86 hardware uses special I/O instructions to do this. The next release of BeOS will have predefined macros that allow the same code to do ISA I/O on both platforms. These macros are not defined in PR2, but using your own macros to read and write to I/O addresses will make the switch easier.

Using macros to access I/O changes the previous example to look like this:

void
SendCommand(command_t command)
{
  static bigtime_t lasttime = 0;
  while((lasttime + 5) > system_time()) ;
  ISA_WRITE_COMMAND_T(COMMANDPORT,
                      B_HOST_TO_xENDIAN_COMMAND_T(command));
  lasttime = system_time();
}

Now, when you receive the next BeOS release, you can define ISA_WRITE_COMMAND_T to expand to the corresponding system-supplied macro.


Developers' Workshop: Axe

By Doug Fulton

"Developers' Workshop" is a new weekly feature that provides answers to our developers' questions. Each week, a Be technical support or documentation professional will choose a question (or two) sent in by an actual developer and provide an answer.

We've created a new section on our website. Please send us your Newsletter topic suggestions by visiting the website at: http://www.be.com/developers/suggestion_box.html.

This week's request was sent in by Mrs. Rose Hovick of Seattle, Washington:

"Can I have an example of a working app that uses the General MIDI synthesizer?"

Sure. If you make a donation at the $0 dollar level, you can download a program called Axe from the MIDI section of Be's source code site:

ftp://ftp.be.com/pub/samples/preview/midi_kit/Axe.zip

Axe is a mindless sound-generating program with a simple, no-help-needed interface. It displays a round playing field that looks something like a radar screen; it also displays a two-tiered palette of instruments, divided into orchestral groups.

Choose an instrument (from the lower palette) and then click in the radar screen; you've just composed the first note of the rest of your life. Choose another instrument, click another note. Lather, rinse, repeat. Don't force the music, it will come to you. It will breathe, expand, and, frankly, it will sound like garbage. Axe isn't Art.

When you click in the playing field, a little circle appears under the mouse. When the little red bead that's circling the radar screen passes by your note circle, a noise is made. The pitch of the noise depends on how close you are to the center of the radar: Pitch heightens as you approach the perimeter. To erase a note, click on the little note circle. To erase everything, hit the space bar.

This all may sound like a joke—and it is, sort of—but it's the kind of joke that you can waste many hours laughing at. And it comes with source code so you can make it even funnier. The code isn't heavily commented, so I'll point out some features and caveats:

Here's one of my favorite Axe tricks: Lay down a series of low-pitched timpani notes that circumnavigate the radar. Look for the instruments called "Halo" and "Manhole" in the "Music FX" section; put in a few of lines of these in the sweet section—combined, they sound like an angel with a hangover. Now stare at the center of the radar for awhile (the "burn in" circles) and pretend you're floating five feet above your body. Instant near-death experience.


UI and the Integrated Browser

By Jean-Louis Gassée

I've been toying with idea of a tongue-in-cheek column in which I would claim that the Department of Justice was suing us for bundling a browser with the BeOS. Clearly, in our case (the column would continue), Janet's hounds weren't sniffing the foul odor of monopolistic abuse of power. Instead, their warrant was misguided egalitarianism, a posture of fairness in going after the smallest as well as the mightiest of offenders.

But with the Bill and Joel (Klein) show entering a dull third act of highly technical lawyering, my proposed column lost its edge. We'll know more by Friday, but the smart money is betting on a protracted, confusing fight that will ultimately be rendered moot by Windows 98 and its integrated browser.

Regardless of the legal outcome, and even though the integration of Explorer isn't really "done" yet, we can already recognize the influence of Web browser UI on general UI ideals. Like it or not, Web browsers are changing the way desktops look and feel. It may be half-baked, but with Windows and Explorer we have a large scale experiment in UI integration. So far, the results are mixed:

So, aside from a few nice tweaks, the merger of the browser UI with the more conventional desktop feels forced. After a while, most users turn off the Web-like desktop for a simpler, more intuitive icons-and-folders mode, saving Web mode for Web interaction.

Modality—having different ways to approach different data—has long been bemoaned as wrong-headed and overly restrictive. An illustrious computer scientist acquiantance still sports a "NO MODES" license plate. This was a meaningful protest in the days when the UI controlled (or "guided," in softspeak) the user. But what we're starting to find, in this experiment, is that if the context is truly different enough, we like modes. You can't hammer a nail with a loaf of bread.

Creative Commons License
Legal Notice
This work is licensed under a Creative Commons Attribution-Non commercial-No Derivative Works 3.0 License.