Displaying Newsletter
Issue 38, 19 Mar 2003


  In This Issue:
 
template < madness > : Unit Test Policy by Erik Jaesler 

Almost all of my recent coding efforts have gone into work on BMessage. Early on, I decided that wanted to try a more "modern" back-end to BMessage, something that would leverage the power of the STL. The end result is out there enough that I'm not sure I want to use it. At any rate, I'm not going to talk about that right now; maybe in another article, if you're all good. ;)

The OpenBeOS project has a pretty serious policy concerning unit testing, and BMessage is no different. BMessage has a great many convenience functions for directly adding/finding/replacing data for common types. While it could be argued that all of these functions are similar enough that writing tests for one set would be good enough, real programmers won't settle for anything less than complete coverage. =) For those of you who aren't actually writing code for OpenBeOS and haven't ever written unit tests, let me assure you that they are often mind-numbingly tedious to code. Now imagine the fun that lay in store for me as I write the same group of tests for each and every set of convenience functions: there are, so far, 8 tests for the various convenience functions, each of which needs to be implemented for bool, int8, int16, int32, int64, float, double, char*, BString, BPoint, BRect, entry_ref, BMessage, BMessenger, raw pointer and BFlattenable. Do the math and I've got 128 functions to implement. "What about copy-'n'-paste and search-'n'-replace?" you ask. For 120 functions?? I'd rather be gummed to death by a mob of toothless grandmothers!

My first idea was to procrastinate (during which time Nathan Whitehorn's Resourcer app got a lot of attention from me). My second idea was more productive: I thought I would write a light-weight scripting engine to generate the source for me. That actually made OK progress up to a point, but it felt clunky and it became clear that the engine would be difficult to extend. As it turns out, the third time was a charm, but before I get into that, a little background. On and off through 2002, I read a book called "Modern C++ Design: Generic Programming and Design Patterns Applied" by Andrei Alexandrescu. If, like me, you happen to think that C++ templates are the greatest thing since iced water or just plain want to melt large portions of your cortex, this book is the bomb. The things that Mr. Alexandrescu does with templates is absolutely sick -- and eminently useful, if you are writing libraries. As it turns out, one of the techniques he discusses actually managed to lodge itself in my brain: policy-based class design. And now the fun begins.

The basic concept of policy-based class design is that certain bits of functionality in a class are grouped together into their own interface--the policy--which then becomes a template parameter for the class itself. Here's an example: let's suppose that you're writing a library of classes and you want to make the method of memory allocation for the library customizable. You could specify some base class which the user would then derive from to create his custom allocator. The problem with this approach becomes apparent in that you want your allocator class to handle different types (after all, this library contains classes in the plural); something which is difficult to express through inheritance alone. The alternate approach is to make your allocator class templatized on the type it's going to allocate. Lifted straight from "Modern C++ Design" are two examples:

template  struct OpNewCreator { 
        static T* Create() { 
                return new T; 
        } 
};
template  struct MallocCreator { 
        static T* Create() { 
                void* buf = std::malloc(sizeof(T)); 
                if (!buf) return 0; 
                return new(buf) T; 
        } 
};

The second allocator, MallocCreator, ensures that T can be a type with a constructor by using placement new on the malloc'd buffer. Now the issue becomes one of getting your library classes to use these allocators -- after all, they're not inherited from some base class, so you can't just go passing base pointers around. Again, templates ride to the rescue (also from "Modern C++ Design"):

template  
class WidgetManager : public CreationPolicy { 
        ... 
};

Now if you want to use OpNewCreator for your allocation you simply do this:

typedef WidgetManager< OpNewCreator > MyWidgetMgr;

If you want to use malloc, just do the same, substituting MallocCreator instead. This concept works because the host class (WidgetManager in this case) relies not on the policy class's type, but on its interface--static T* Create().

As I was getting my scripting engine to the point where it was almost generating tests for BMessage's BRect convenience functions correctly ("Just a few more hours of ruthless hacking!"), it occurred to me that I was looking at a series of policies:

  • the set of convenience functions getting tested (add/find/replace, etc.)
  • the initialization of variables (and arrays of variables)
  • the assertion of results in the tests

Thrown on top of those policies were two other vital pieces of information: the type we were testing for (e.g., int32) and it's type_code (e.g., B_INT32_TYPE). Taken all together, we end up with a class declaration like this:

template 
< 
        class Type, 
        type_code TypeCode, 
        class FuncPolicy, 
        class InitPolicy, 
        class AssertPolicy 
> 
class TMessageItemTest { 
  public: 
        void MessageItemTest1(); 
        ... 
        void MessageItemTest8(); 
};

The tests are a bit big to list one here in its entirety, but they look something like the following:

void MessageItemTest1() { 
        BMessage msg; 
        Type out = InitPolicy::Zero(); 
        assert(FuncPolicy::Find(msg, "item", 0, &out) == B_NAME_NOT_FOUND); 
        assert(out == AssertPolicy::Invalid()); 
}

As I said, this isn't a full test, but it should give you an idea of how the policies are getting used. Just for clarity's sake, here's the same code, but written for a specific type ( int32):

void MessageItemTest1() { 
        BMessage msg; 
        int32 out = 0; 
        assert(msg.Find("item", 0, &out) == B_NAME_NOT_FOUND); 
        assert(out == 0); 
}

An interesting thing to note here is that for int32, InitPolicy::Zero() and AssertPolicy::Zero() boil down to the same value: zero. Why not use one or the other in both places? As it turns out, for other types, "zero" and "invalid" are not the same thing. For instance, a "zero" BRect has left, top, right and bottom all set to 0. However, an invalid BRect has left and top set to 0 and right and bottom set to -1. You might begin to see why scripting could have a hard time cutting the mustard.

As it turns out, for most types FuncPolicy is conceptually identical: call the type's convenience function, passing in a name and value or index. In fact, they're so identical that we can create a policy-based class for that as well! Here's an abbreviated version:

template 
< 
        typename Type, 
        status_t (BMessage::*AddFunc)(const char*, Type), 
        ... 
> 
struct TMessageItemFuncPolicy { 
        static status_t Add(BMessage& msg, const char* name, Type& val) { 
                return (msg.*AddFunc)(name, val); 
        } 
        ... 
}

It you've been considering what the fully fleshed out code for all this looks like, you're probably thinking "This is getting pretty out of hand" by now, and if I had to do this more than once, I would absolutely agree. The beautiful thing, though, is that I only have to write the test code once. After that, I can specify a full set of tests for a given type with just this much code:

typedef TMessageItemFuncPolicy 
< 
        int32, 
        &BMessage::AddInt32, 
        &BMessage::FindInt32, 
        &BMessage::FindInt32,   // The version that returns int32 directly 
        &BMessage::HasInt32, 
        &BMessage::ReplaceInt32 
> 
TInt32FuncPolicy; 

struct TInt32InitPolicy { 
        inline static int32 Zero()      { return 0; } 
        inline static int32 Test1()     { return 1234; } 
        inline static int32 Test2()     { return 5678; } 
}; 

struct TInt32AssertPolicy { 
        inline static int32 Zero()              { return 0; } 
        inline static int32 Invalid()   { return 0; } 
}; 

typedef TMessageItemTest 
< 
        int32, 
        B_INT32_TYPE, 
        TInt32FuncPolicy, 
        TInt32InitPolicy, 
        TInt32AssertPolicy 
> 
TMessageInt32ItemTest;

There! In a little over 30 lines of code, I've just created nearly 400 lines of tests! Once that was done, it took less than half an hour to create the tests for about half of the types with BMessage convenience functions. The key to all this is that you're letting the compiler generate all the test code for you.

As I said before, I left a bit out, so if you have a burning curiosity (or twisted perversion, depending on your perspective) to know more, I encourage you to check out the various MessageXXXItemTest.h files in current/src/tests/kits/app/bmessage. If that doesn't cook your noodle, you just might be the very kind of sick monkey that would love "Modern C++ Design". If so, you definitely owe it to yourself to read it.

You know, I just noticed I haven't created the tests for floats or doubles yet--I think I'll spend the next 10 minutes banging out 800 lines worth of code!

 
Can I Take a Message? by DarkWyrm  

One of the most interesting (and crucial) parts of BeOS aside from how blazingly fast it is would be the way an application communicate with its internals and with other applications. BMessages are flexible - usable for both a dessert topping and a floor wax. I'm still working on figuring out how to use them to make french fries, but I digress. Let us take a look at the big picture in how to communicate in BeOS.

Being Deported

BeOS messaging builds on a foundation laid by the kernel. From the BeBook:

A port is a system-wide message repository into which any thread can copy a buffer of data, and from which any thread can then retrieve the buffer. This repository is implemented as a first-in/first-out message queue: A port stores its messages in the order in which they're received, and it relinquishes them in the order in which they're stored. Each port has its own message queue.

They can be used in C or C++ code, so it is theoretically possible to design and implement a completely different messaging system for BeOS - the kernel provides most of the heavy lifting and actual delivery. In fact, BeOS and OpenBeOS have done this to send low-overhead messages to and from the app_server, although in slightly different ways.

At the most basic level, all that is needed to send a message is knowing the target port and calling the function write_port in some way like this:

write_port( int32_message_code, pointer_to_associated_data );

Voila - it has been placed in a message queue for that port, awaiting the recipient to read the port, pick up the message, and have its way with what we sent.

"You Can't Handle the Truth!"

Well, maybe not, but BeOS messaging has a base class which can: BHandler. BHandler's main task is to receive a message and decide what course of action to take next based on the message - calling a function, sending a reply, or whatever. It does this via the virtual function MessageReceived. By itself, a BHandler can't do much, which leads to another important foundational class: BLooper.

BLoopers are a special kind of BHandler which monitors a port associated with it in a big loop - they pretty much wait for a message at the port, pick it up, and place it in a queue. Each message in the queue is taken in the order received. The Looper checks to see if a target was specified, and if not, passes it around to a list of message handlers. If not handled by any in the list, the Looper checks with its preferred message handler, and then finally handles the message itself.

Neither of these sound particularly exciting on their own. However, when one considers that BViews are subclasses of BHandler and BWindows and BApplications are BLooper subclasses, suddenly the whole picture of BeOS Messaging becomes a little clearer. Windows and Applications talk to each other via a convenient, elegant abstraction over what the kernel provides.

BMessages are converted to a flat stream of bytes in order to be sent to a BLooper's port via write_port(). The Looper unflattens the BMessage before placing it in a BMessageQueue object. This is done because a port has a limited capacity and a BMessageQueue does not. BMessages also contain useful identifying information, such as the ID of the BHandler who is the intended recipient if the sender specifies one and some useful data to allow the recipient to send a reply if it wishes to.

Other Cool Stuff

As the announcer says in TV commercials, "But wait, there's more!" Most of the time, one wishes to send a message to a window or the global application object from within an application. This merely requires having a pointer to the recipient object. What about a recipient in another application? That is why there are BMessengers. They can send a BMessage anywhere, if given an application signature, a looper, or a handler. Quite useful for interapplication communications. BInvokers are designed to be, well, invoked. They send a copy of a message to a specified target and are commonly used as a mix-in class for controls, such as buttons and such. BMessageRunners act as an interface to the system roster to send a message to a specific target via a BMessenger after a certain interval of time, possibly for a specified number of times. BMessageFilters can effectively allow a BLooper to say "I'm not listening! I can't hear you! LA LA LA LA LA!", ignoring certain messages.

Certain classes probably won't be used except in certain specialized cases. Others are used literally all the time. Now that you understand the big BeOS messaging picture, go write the next killer app and communicate better at the same time.

 
The Palm Paradigm by Michael Phipps 

I recently bought a Palm III XE on EBay (where yesterday's technology goes to be recycled). I didn't buy it to use as a PDA; my life is not so complicated that I need a calendar, to do list, or address book. I do, however, read a lot, on the order of 1000 pages a week. My paper habit was getting to be very expensive. I have long been aware of Project Gutenburg, but sitting in front of my monitor to read is not what I would hold up as a great example. Running this huge, fast machine to be a text reader is pretty silly. And the 19" monitor is overkill, when I would be perfectly happy with a 4X6 sized book. Not to mention the lack of portability. So I bought the Palm to read e-texts on.

In the process of getting acquainted with it, though, I have had a slow realization of why people love these things. It reminds me of the Walkman - it doesn't do much, but what it does, it executes perfectly. Let's start with the size - very comfortable and fits nicely in your hand. The square shape is a bit of a put-off, but the new Palms are nicely rounded. The reset button is easy to find but is impossible to press accidentally - nice thinking. It is a sturdy device - important for something designed to go into your pocket. The hardware isn't the real jewel here, though. The software is extremely well executed.

The introduction of Charles on the TV show M*A*S*H had a very memorable line - "I do one thing at a time, I do it very well, and I move on." In a way, the same thing could be said about Palm OS. I haven't looked at the API, but from the end user's point of view, it seems very obvious to me how to use it. As soon as I turned it on, there were icons on the desktop. Pretty familiar. But the desktop is not the "catch all" desktop that we use on our PCs. It is a display of applications. The applications are named by what they do, not cool in-jokes, programmers' initials or funny acronyms. "Mail" is better than "Outlook", in my book. Every application runs in "full screen" mode. This seems to go back 20 years to the days of DOS but it works really well on the small screen. Furthermore, when you do switch applications, the old one continues to exist in just the state that you left it. It is similar to a persistent object paradigm. Applications are small, simple, and to the point. There aren't so many features that you have to "learn" the program, but there are enough that I haven't yet wondered if the application could be made tremendously more useful with just one more bell or whistle.

Graffiti is a pretty decent handwriting recognition system. While it does take a little training, it isn't an onerous task in exchange for the ability to quickly and easily enter text. It does not make for a great Rogue-playing experience, though.

How does all of this relate to OBOS, aside from the irony of me buying from the company that bought Be? Just some thoughts about possible future directions. We aren't making a PDA, nor should we try. But I do wonder if a micro-Tracker that works a lot like PalmOS would be good for newbies. All of the apps on the desktop would be clearly laid out and would start on single click. There would be no windows, but whole screen apps. Apps would never really close down, but would hide instead. File requestors would display any and all files that they can load, but nothing more or less. Syncing with another machine (file server? laptop? backup?) would be a trivial exercise. The applications would be so easy to use that a new person could learn the whole system in an hour or less. Installation of an app would be as easy as downloading a file. No need to select where the app goes or what disk it goes on - it would just work.

Maybe even moving some of this point of view into "normal" OBOS would be a good idea. Do you really care how your hard drive is laid out? Do you really care that /boot/apps is where applications go? Or would an interface that takes care of all of that for you be "OK"? Would an interface that allows you to query for files more naturally than by directory name be good enough? Dealing with an interface like this would, to some degree, be about taking away power from the user. But if you don't want or need that power, does it matter?