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:
classBTheRegistry
{ public:BTheRegistry()
; status_tRegSetValue
(const char *appMimeSig
, const char *valueName
, uint32valueType
, const void *value
, size_tsize
); virtual voidPickARandomEntryAndCorruptIt()
; ... };
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:
classCompatibilityGlue
{ public: static voidSetup()
; // Setup is used instead of the constructor // to initialize the class from the application static voidTeardown()
; static status_tRegSetValue
(const char *appMimeSig
, const char *valueName
, uint32valueType
, const void *value
, size_tsize
); // more calls here private: // constructor and destructor called by Setup/Teardown, // shouldn't be called directlyCompatibilityGlue()
;~CompatibilityGlue()
; // stub function pointers status_t (*glueRegSetValue
)(const char *, const char *, uint32, const void *, size_t); // ... more here staticCompatibilityGlue
*compatibilityGlue
; image_idimageID
; };CompatibilityGlue
*CompatibilityGlue
::compatibilityGlue
=NULL
; voidCompatibilityGlue
::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
= newCompatibilityGlue
; } voidCompatibilityGlue
::Teardown()
{ deletecompatibilityGlue
; }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_idimageID
= 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_tCompatibilityGlue
::RegSetValue
(const char *appMimeSig
, const char *valueName
, uint32valueType
, const void *value
, size_tsize
) { 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_tresult
=someSettingsNode
->WriteAttr
(valueName
,valueType
, 0,value
,size
); if (result
< 0) returnresult
; } returnB_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_tInit()
; EXPORT extern "C" voidDeInit()
; EXPORT extern "C" status_tStubRegSetValue
(const char *appMimeSig
, const char *valueName
, uint32valueType
, const void *value
, size_tsize
); status_tInit
() { // if needed, objects needed by the glue stub library // can be allocated here returnB_OK
; } voidDeInit
() { // if needed, objects needed by the glue stub library // can be deallocated here } status_tStubRegSetValue
(const char *appMimeSig
, const char *valueName
, uint32valueType
, const void *value
, size_tsize
) { returnBTheRegistry()
.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.
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
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_terr
; int64numFrames
;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
= newBMediaFile
(&ref
);err
=mFile
->InitCheck()
; if (err
==B_OK
) { int32i
= 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 ismFormat
.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 int32mode
, const bigtime_tgrabInterval
)
lets you explicitly set the mode and frame interval.
FilmStrip
also has a static member function
static status_tSniffRef
(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.
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.