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:
"F" indicates that this is a function.
"Ul" represents the type of the first argument (unsigned long).
"Pi" (pointer to int) is the type of the second argument.
Let's look at an example of a C++ member function:
voidfoo
::fred
(const char *, short);
This becomes:
fred__3fooFPCcs
"3" is the string length of the class name ("foo").
"foo" is the class name.
"F" is the function indicator.
"PCc" stands for "pointer to const char" (the first argument).
"s" stands for short (the second argument).
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 intfred
(unsigned long, int *); #pragma export reset // definition intfred
(unsigned longi
, int *p
) { returnp
[i
]; }
Here's the code that loads the add-on, does the symbol lookup, and then invokes the function:
image_idimid
; int (*f
)(unsigned long, int*); static intp
[] = { 9, 2, 7, 1 }; // Load the add-onimid
=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 functionprintf
("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
:
classPet
{ public:Pet
(const char *name
); virtual~Pet
(); virtual const char *name
(); private: charbuf
[32]; };
And Pet.cpp
:
#include <string.h> #include "Pet.h" #pragma export on extern "C"Pet
*instantiate_pet
(const char *name
); #pragma export resetPet
*instantiate_pet
(const char *name
) { return newPet
(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_idimid
;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 }myPe
t= (*f
)("fido");printf
("pet name is %s\n",myPet
->name
()); deletemyPet
; ...
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.
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:
Do not use assembly language without providing a fallback for other processors.
Use endian-independent code to access external devices.
Use system functions for all timing and delays.
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 longlonglongzero
() { li r4, 0 li r3, 0 blr } #elif __INTEL__ __declspec(naked)longlongzero
() { asm { mov eax, 0 mov edx, 0 ret } } #else long longlonglongzero
() { 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:
voidguard_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:
voidSendCommand
(command_tcommand
) { static bigtime_tlasttime
= 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:
voidSendCommand
(command_tcommand
) { static bigtime_tlasttime
= 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" 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:
Axe uses the big General MIDI synthesizer (/boot/beos/etc/synth/big_synth.sy). If you don't already have a copy of the synthesizer, you can download it by going to:
http://www.be.com/products/beos_download/packages.html
The entire synthesizer file is loaded when the app is launched, so start-up time takes awhile—ten seconds or so, depending on the speed of your machine. This is an eternity compared to most apps.
The two circles within the radar screen designate the C below middle C (the inner circle) and the C above middle C (the outer circle). Musically, this is the "sweet zone" for many instruments, although for our purposes, it's just as interesting to avoid this section—try dropping a clump of baritone sax notes an octave or two below this zone for a satisfying growl.
Don't be timid or discrete. You can't write music with this thing, all you can do is make noise, so the more notes the better.
You can't change the speed of the radar's sweep. And, if you're a code-fiddler, I implore you not to add such a feature. Dogged periodicity is Axe's most important characteristic. If the frequency of the sweep were alterable, the illusion would fall apart.
The menu items aren't connected to anything. I was going to put in some wire so you could save a "performance," but I forgot to list it as a goal.
The cpu-wasting graphics look best in 32-bit.
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.
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:
The Big Idea was to "seamlessly" integrate all sources of data. My hard disk and my favorite Web site were to look and feel the same. A nice idea—but, currently, not a practical one: Remote data could easily be made to look like the contents of a hard disk, but until bandwidth limitations are overcome, the "feel" won't be the same.
Of course, we'll have DSL lines or cable modems in a not-too-distant future but, as a somewhat privileged T-1 user, I can attest to the fact line speed isn't everything. The entire Web fabric needs an overhaul before we can fuse the UI paradigms (that's the one and only time I'll use the "p" word today).
Conversely, making the contents of a folder look like a Web page doesn't improve my ability to navigate large amounts of data and, in some cases, increases the feel of clutter.
The "Channels" experiment is even less convincing but, as it is even younger than the browser, we can leave it for another day.
"Push" has been criticized for being nothing more than a classical client-server interaction in disguise—but isn't disguise the basis of a UI? E-mail is a very successful push application when you're on a LAN, and reverts to the original form with a dial-up connection. Hopefully, with time, we'll become better at sorting out the types of data we want pushed to our desk and the ones we'd prefer browsing for.
Some other modest "improvements" have fallen out of browser integration, such as auto-highlighting when the cursor hovers over an object, and one-click action (as opposed to selection). Nothing to criticize here—but these aren't really new. If memory serves, we saw these with HyperCard ca. 1987.
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.