Issue 4-48, December 1, 1999

Be Engineering Insights: Supporting Different Versions of BeOS in Your Application

By Pavel Cisler

Sorry. This time (to George's dismay) I will not subject you to any esoteric C++ features. Rather, I'd like to share a sneaky little trick I've been doing with my app Eddie that lets it use new system features while staying binary compatible with old versions of BeOS.

Every new BeOS release comes with a new API that applications can use to support new functionality, integrate better with the system, etc. Using these new APIs requires that your application be built against the latest headers and libraries.

Supporting an older version of BeOS lets users who haven't upgraded to the latest version run your application. It will become more important as the BeOS installed base expands and people upgrade to new versions less often or later. To support BeOS R4 and later, for example, you need to set up your build environment to use the R4 headers, libraries, and tools from an R4 BeOS CD (be careful about the lib folder; a lot of the files in it are symbolic links to the /system/lib and you actually have to replace the links with the real binaries from the R4 /system/lib).

Clearly, app developer has to decide what the right compromise is between supporting new features and being backwards compatible.

You can, however, have your cake and eat it too—you don't even need any proprietary BeOS secrets. Here's how you do it:

Let's pretend that BeOS R6.8 is about to come out. As a third party developer, you're seeded with a beta version that includes new headers. Pretend that R6.8 adds a new API:

class BTheRegistry {
public:
    BTheRegistry();


    status_t RegSetValue(const char *appMimeSig, const char *valueName,
        uint32 valueType, const void *value, size_t size);


    virtual void PickARandomEntryAndCorruptIt();
    ...
};

In the application that you're about to release, you'd like to take advantage of the new API by calling:

BTheRegistry().RegSetValue(
    "application/x-vnd.CoolLabs.CoolApp",
    "studioAssistant", "PaperClip", strlen("PaperClip") + 1);

When you build your new app with the R6.8 headers, it won't run under BeOS R6.7 because the loader will (among other things) not be able to find the symbols for the BTheRegistry class.

Ideally, we'd like to set up our class to still run under R6.7 and either not offer the functionality supported by the new API or have a reasonable fallback alternative. When running under R6.8 we'd like it to take full advantage of the new API, in our case the hypothetical BTheRegistry::RegSetValue call. To do this, we build the entire application with the old headers/libraries and place the calls to the new APIs into a special glue compatibility library. We single out the calls to the new APIs in the application and direct them through a CompatibilityGlue class. Your app will replace the call to:

BTheRegistry().RegSetValue(....

with:

CompatibilityGlue::RegSetValue("application/x-vnd.CoolLabs.CoolApp",
    "studioAssistant", "PaperClip", strlen("PaperClip") + 1);

The new CompatibilityGlue class in your app tries to load a compatibility glue stub library. If it succeeds, it redirects the call to RegSetValue to the glue stub, which ends up calling the new call in the new system binaries. If it fails to load the glue stub library, it's most likely because the loader refused to load it, which implies that you're running under an older version, R6.7. The CompatibilityGlue will fall back onto alternative code in that case, and the next closest thing using only R6.7 APIs.

Here's the CompatibilityGlue class that you add to your app:

class CompatibilityGlue {
public:
    static void Setup();
        // Setup is used instead of the constructor
        // to initialize the class from the application
    static void Teardown();

    static status_t RegSetValue(const char *appMimeSig,
        const char *valueName, uint32 valueType,
        const void *value, size_t size);

    // more calls here

private:
    // constructor and destructor called by Setup/Teardown,
    // shouldn't be called directly
    CompatibilityGlue();
    ~CompatibilityGlue();

    // stub function pointers
    status_t (*glueRegSetValue)(const char *, const char *,
        uint32, const void *, size_t);
        // ... more here

    static CompatibilityGlue *compatibilityGlue;
    image_id imageID;
};

CompatibilityGlue *CompatibilityGlue::compatibilityGlue = NULL;

void
CompatibilityGlue::Setup()
{
    // This trivial version of setup is not thread safe, OK if
    // we only call it once from the application initialization
    // code
    if (!compatibilityGlue)
        compatibilityGlue = new CompatibilityGlue;
}

void
CompatibilityGlue::Teardown()
{
    delete compatibilityGlue;
}

CompatibilityGlue::CompatibilityGlue()
    :glueRegSetValue(NULL),
     imageID(-1)
{
    BPath path;

    // find the path to the applications addon folder
    // (call provided by the app)
    if (MyCoolApp::GetAppPluginPath(&path) != B_OK)
        return;

    path.Append("CompatibilityGlue.so");

    // try loading the compatibility addon
    image_id imageID = load_add_on(path.Path());
    if (imageID < 0)
        // failed to load the compatibility glue stub,
        // running under an older system
        return;

    // initialize the compatibility addon
    // (optional)
    status_t (*init)();
    if (get_image_symbol(identifier, "Init",
        B_SYMBOL_TYPE_TEXT, (void **)&init) != B_OK) {
        // failed to initialize the compatibility glue stub
        return;
    }

    if ((init)() != B_OK)
        return;

    // compatibility addon initialized, find the calls we
    // are interested in and hook them up

    if (get_image_symbol(identifier, "StubRegSetValue",
        B_SYMBOL_TYPE_TEXT, &glueRegSetValue) != B_OK)
        // couldn't find the symbol for the RegSetValue stub call
        return;

    // ... setup more calls here
}

CompatibilityGlue::~CompatibilityGlue()
{
    if (imageID >= 0) {
        status_t (*deinit)();
        if (get_image_symbol(identifier, "DeInit",
            B_SYMBOL_TYPE_TEXT, (void **)&deinit) == B_OK)
            (deinit)();

        unload_add_on(imageID);
    }
}

status_t
CompatibilityGlue::RegSetValue(const char *appMimeSig,
    const char *valueName, uint32 valueType, const void *value,
    size_t size)
{
    if (compatibilityGlue->glueRegSetValue)
        return (compatibilityGlue->glueRegSetValue)(appMimeSig,
            valueName, valueType, value, size);
    else {
        // running under older system, use a fallback mechanism
        // (or do nothing if applicable)
        ssize_t result = someSettingsNode->WriteAttr(valueName,
            valueType, 0, value, size);
        if (result < 0)
            return result;
    }
    return B_OK;
}

As you can see, the CompatibilityGlue class tries to load the CompatibilityGlue.so library, calls its Init function, does a symbol lookup for the desired stubbed calls, and, if successful, points the stub function pointers to them.

The actual compatibility glue stub library CompatibilityGlue.so is built against R6.8 headers/libraries/tools the same way we would build a shared library. For our example it will be tiny:

#include <TheRegistry.h>

EXPORT extern "C" status_t Init();
EXPORT extern "C" void DeInit();
EXPORT extern "C" status_t StubRegSetValue(const char *appMimeSig,
    const char *valueName, uint32 valueType, const void *value,
    size_t size);

status_t
Init()
{
    // if needed, objects needed by the glue stub library
    // can be allocated here
    return B_OK;
}

void
DeInit()
{
    // if needed, objects needed by the glue stub library
    // can be deallocated here
}

status_t
StubRegSetValue(const char *appMimeSig, const char *valueName,
    uint32 valueType, const void *value, size_t size)
{
    return BTheRegistry().RegSetValue(appMimeSig, valueName,
        valueType, value, size);
}

The resulting stub library CompatibilityGlue.so is placed in the application plugin directory -- /boot/home/config/add-ons/MyCoolApp/ would be a good candidate (or in any other place where the MyCoolApp::GetAppPluginPath will find it).

When your application starts up, it calls the CompatibilityGlue::Setup() call that tries to load the stub library. If it succeeds, you're running R6.8 and the spanking new APIs are called. If it fails, the fallback code is used instead.

Theoretically, you could actually skip the CompatibilityGlue.so—you could actually look up the symbols of the new calls directly and fix up the stub function pointers to call directly into the corresponding new shared library. To do that, you'd have to use the mangled C++ name for the function lookup, know where to look for the symbols, etc. The technique using CompatibilityGlue.so is easier.

Obviously, this technique works best with simple, single routine calls that add cool functionality to your application. It would be foolish to stub out an entire class with all its methods, etc., this way. I've used it in the past to build a version of Eddie (found at http://www.el34.com) compatible all the way back to BeOS Preview Release 2 and still have it use calls like BNode::Sync() and I'll very likely use it to support new BeOS versions while staying backwards compatible.

Disclaimer:

Some of the APIs discussed in this article are purely hypothetical, used primarily to illustrate the techniques I describe. No promises are being made about the availability of such APIs in any future versions of BeOS.


Be Engineering Insights: Transforming Media With BMediaFile

By Nathan Schrenk

In BeOS R4.5, the extremely useful BMediaFile API was added to the Media Kit, but a quick review of newsletter articles shows that BMediaFile hasn't been discussed much in this forum. BMediaFile provides a (relatively) easy way to access audio and video data in a number of common file formats, such as QuickTime, AVI, and WAV, among others. By using BMediaFile and its trusty sidekick BMediaTrack, you can read and write video and audio data without worrying about how to decode compressed Cinepak video or how to encode IMA-4 audio. Using these APIs should allow you to spend more time writing the real code for your application, or will enable your application to support more data input and output formats. We plan to continue to add to the file formats and encoders and decoders we support, so if your application uses the BMediaFile/BMediaTrack API, its capabilities will grow along with the BeOS.

You might think that this general idea of being able to support many different audio and video file formats by utilizing BMediaFile in your application is similar to another part of the BeOS API, the Translation Kit. If you were thinking this, you would be correct. The Translation Kit documentation and header files hint at being able to translate data of various formats, such as bitmaps, sound, text, MIDI, and media, but I believe that the Translation Kit is only being used for bitmaps in practice. The Translation Kit handles reading and writing bitmaps very well, but it's not rich enough to support the complexities of the audio and video formats that are handled by BMediaFile, which is why a new API was introduced. One great advantage of the Translation Kit is that quite a few third party developers have released additional translators for bitmap formats that are not supported by BeOS out of the box. Unfortunately, it's not currently possible for third party developers to add to the file formats, decoders, or encoders supported by BMediaFile. We're looking into making this possible. Also, there are functions in BMediaFile and BMediaTrack for accessing a BParameterWeb of settings values. These functions are not currently implemented, although we are planning to add this support in the future.

The nitty-gritty details of how to read and write audio and video data using BMediaFile are spelled out in the R4.5 Be Book, so I won't go into them here. You can find BMediaFile API documentation at BMediaFile.html.

An in-depth example on how to use BMediaFile to convert from one file and encoding format to another is located at TheMediaKit_Overview_ReadingWriting.html.

Near the bottom of the in-depth example referred to above, is a section titled "Integrating Into a Real Application." I thought it might be useful to write a "Real Application" that uses the concepts presented in the example. The result is MediaConverter, <ftp://ftp.be.com/pub/samples/media_kit/MediaConverter.zip


Developers' Workshop: How to Drop Frames

By Todd Thomas

Here's something you weren't expecting: sample code for a BeOS digital video application that plays a maximum of 4 frames per second and can be set to drop as many frames as you like. But you can also think of it as an exploration of BMediaTrack's ReadFrames() function.

The app and its central class are named FilmStrip, after the medium that falls somewhere between overhead transparencies and 16mm film in the great hierarchy of audio-visual presentation formats. A film strip is, well, a strip of exposed positive photographic film that is run one frame at a time through a projector, usually to the accompaniment of a narrative on audio cassette. It's a lot like a movie, but without all those gratuitous extra frames used to make stuff move.

So now you're asking yourself, "How can I use BeOS to simulate the immersive multimedia experience of the film strip, when all I have are these movie files full of gratuitous extra frames?" That's what the FilmStrip class and app are all about.

You can find the source code for FilmStrip at:

<ftp://ftp.be.com/pub/samples/media_kit/FilmStrip.zip>

The FilmStrip class extracts individual frames as bitmaps out of any digital video file the BeOS has a codec for, using the magic of the BMediaTrack class. The heart of the FilmStrip class is its function

BBitmap* NextFrame(void)

which returns a pointer to an internally allocated BBitmap containing the "next" frame. The meaning of "next" depends on which of two modes the FilmStrip object is in: in Frame mode, "next" is taken literally to mean the frame immediately following the current frame; in Time mode, "next" means the frame occurring some number of microseconds after the current frame.

BMediaTrack makes it easy to get frame data from a media file. It's as simple as

status_t err;
int64 numFrames;

err = track->ReadFrames((char*)someBuffer,
    &numFrames);

for some BMediaTrack* track.

Of course there are a few preparatory steps you need to take first. You can only instantiate a BMediaTrack from a BMediaFile. And in the case of FilmStrip, we want that track to be the video track of a movie, as opposed to the audio track or the laugh track or whatever. Next, you need to negotiate with the codec which format you want the decoded data to be in. FilmStrip's needs are simple—it just wants the video data to be decoded if necessary. Here's a fragment of code from FilmStrip's private SetTo() function that accomplishes all that:

mFile = new BMediaFile(&ref);
err = mFile->InitCheck();
if (err == B_OK) {
    int32 i = 0;
    do {
        mTrack = mFile->TrackAt(i);
        err = mTrack->InitCheck();
        if (err == B_OK) {
            // sniff out whether it's the video track
            // and break out if it is
            mFormat.u.raw video =
                media_raw_video_format::wildcard;
            mFormat.type = B_MEDIA_RAW_VIDEO;
            err = mTrack->DecodedFormat(&mFormat);
            if (err == B_OK) {
                if (mFormat.IsVideo()) {
                    break;
                }
                else {
                    // when mFile is deleted it
                    // will delete all open tracks
                    // as well, but why waste
                    // the memory on tracks
                    // we're not using?
                    mFile->ReleaseTrack(mTrack);
                }
            }
        }
        i++;
    } while (i < mFile->CountTracks());

If your needs are more complex, you can change other fields of the media format object (mFormat in the code above) as necessary. Note that ReadFrames() always reads one and only one frame from a video track, but reads as many frames as negotiated from an audio track. After you've successfully negotiated an output format, you can safely use ReadFrames().

Which frame (or frames) does ReadFrames() read? Whichever you tell it to using the SeekToFrame() or SeekToTime() functions. Be aware that not all media codecs are capable of seeking to arbitrary frames in a track -- some can only seek to key frames. Note that your nearest keyframe may be behind you. You can pass either function the flag B_MEDIA_SEEK_CLOSEST_BACKWARD or B_MEDIA_SEEK_CLOSEST_FORWARD to indicate which keyframe you want relative to the frame you asked for. FilmStrip gives examples of using both SeekToFrame() and SeekToTime() at the end of NextFrame().

Note: the R4.5.2 Indeo-5 codec doesn't behave properly when you pass B_MEDIA_SEEK_CLOSEST_FORWARD to SeekToFrame()—it actually does the opposite. If you play an Indeo-5 encoded movie with FilmStrip in Frame mode, you'll notice it only shows frame 0. This bug is fixed in the next release of BeOS.

There are two FilmStrip constructors.

FilmStrip(const entry_ref& ref)

takes the entry_ref of the file you wish to grab frames from. It defaults to Frame mode, and tries to be clever about setting the interval between consecutive frames in Time mode.

FilmStrip(const entry_ref& ref, const int32 mode,
const bigtime_t grabInterval)

lets you explicitly set the mode and frame interval.

FilmStrip also has a static member function

static status_t SniffRef(const entry_ref& ref)

that does a quick 'n dirty test to see if ref's MIME type is of supertype "video". You can use this function to quickly reject a ref that's not some kind of movie, but it doesn't guarantee that there's a codec for the specific type of movie if it is.

There are a few other things of interest in the FilmStrip application. It uses two BWindow descendants to accomplish the tasks of showing frames from and letting you control a FilmStrip object. The FilmStripWindow class defines the app's main window. It contains a single BitmapView which displays the frame obtained from its member FilmStrip object. FilmStripWindow has a member BMessageRunner which sends a message to the window at a user specified interval indicating it's time to show the FilmStrip's next frame. FilmStripWindow also has a member BMessenger which targets a floating control panel window.

The ControlWindow class defines that floating control panel window. It contains a BMessenger targeted at the main window, so it can send messages when the user tweaks its controls that will cause the main window to update its FilmStrip object.


Understanding Stock Prices

By Jean-Louis Gassée

Perhaps I should use another title—something like "Je ne Parle pas NASDAQ" or "Yo no Hablo Wall Street." Understandably, the movements in our stock price have attracted attention and prompted questions to jlg@be.com or info@be.com. I have two positions on this topic, one institutional and the other personal.

The company position is a classical one: we don't comment on our stock price. The law and SEC regulations dictate why, when, and how we inform our shareholders. When you step back from technical and legal details, it's pretty much common sense. Shareholders need good, timely, and equally distributed information. Once they have it, they're in a good position to make decisions to hold, buy, or sell shares, among other things. That's why we have regular filings every quarter and ad hoc filings or press releases to disclose meaningful events such as a relationship with another party or a significant hire.

Apart from these obligations, it's better for us to stick to our knitting -- developing and promoting the product and supporting customers and partners. We don't object to comments on our stock price by third parties, shareholders, analysts, the press, or observers of any kind, but we feel strongly that we shouldn't join in the conversation, nor do we track, review or endorse them.

On the personal side, I know I'm not meant to understand the stock market. I know this partly because of my love of mathematics. I won't abuse near clichés such as "chaos" or "non-linear," but I just think that there are no a priori—sorry, make that "before the fact" -- explanations of market behavior. There is no testable theory. There are just evening news pontifications: profit-taking; resistance; interest rate increase already discounted; earnings surprises; whisper numbers; beating expectations; visibility; EBITDA (Earnings Before Interest, Tax and Debt Amortization); and even, for the choicest e-businesses, EBE, Earnings Before Expenses (no kidding). My father, an accountant, passed away too early to see this explosion of vaporous financial analysis.

Instead, just for fun, I'll contradict myself and do a twist on a Greenspan theory. Our beloved Chairman of the Fed once groused about the "irrational exuberance" he saw in the stock market. More recently, he relented a bit. Yes, the Chairman said—and I paraphrase—there are going to be a few General Motors, General Electrics, and IBMs in this broth of e-companies, but we don't know which ones. As a result, buying these stocks is like buying lottery tickets. Big winners in small numbers.

Now comes my twist: Imagine a Lotto with a big pot. And, instead of printing as many tickets as the buying public desires, imagine selling a fixed number of tickets through an auction. Or selling a fixed number of tickets six months before the drawing and letting buyers resell tickets among themselves or at auctions in the interval before the liquidating event. Or imagine what would happen to the on-going auction process if the amount of the prize wasn't known with certainty, if it varied, if various opinions and rumors regarding the amount circulated before the drawing...

Metaphors are not to be confused with a testable theory. As a great crook, Werner Erhardt, inventor of the very Seventies EST, used to say, everything works by agreement. And, when it comes to a market agreement between buyer and seller, with or without an underlying auction, what is the real difference between a DM banknote, a painting, a share of T (NYSE), or a book?

Speaking of books and group behavior, two titles you might enjoy, if only as nonaddictive alternatives to sleeping pills: "Logic of Collective Action: Public Goods and the Theory of Groups," by Mancur Olson, and "Principles of Group Solidarity," by Michael Hechter. The first one, after many years out of print (I had to buy the French translation) is now available in paperback, with a terrific introduction. The second one is an easier read, but is currently out of print. With luck, your library or Alibris will find it for you.

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