posted by Andrew Hudson on Thu 14th Jul 2011 17:47 UTC
IconThis article provides a brief overview of the Haiku operating system from a programmer's point of view, with sample code for an inter-application communication application. The Haiku API is simple and powerful and by the end of this article you will be able to use a variety of objects to write your own Haiku applications. Some C or C++ coding experience is useful.

Haiku Overview

Haiku is the open source reincarnation of the advanced Be operating system, BeOS. The Haiku project is now 10 years old and is readying its third Alpha release. This article will provide a brief overview of the Haiku architecture from a developer's perspective and provide an application as an example for how a number of the interesting Haiku objects can work together to provide some sophisticated results.

For anyone who might be new to Haiku, here are some features:

  • Pervasive multithreading
  • A hybrid microkernel
  • A single, clean, orthogonal API
  • A native, mature windowing system
  • An advanced, journaling, database file system
  • Decent and growing hardware support

The original BeOS architecture was designed and written by a small development team over 10 or so years. It was designed to be a highly responsive, developer-friendly, personal operating system. It was written in C++ as a series of programmer-accessible objects. The API is written in C++ and is fairly easy to learn and work with . Much but not all of Haiku is documented online in the BeBook. A great set of introductory Haiku programming lessons can be found here, Learning to Program With Haiku. And additional source code examples can be downloaded here, BeOS sample code.

What does it mean when you have a great API with lots of multithreading built in? It means Haiku lets you build up some pretty advanced functionality fairly quickly, using only standard parts and a little bit of know-how. This article will take you through the design of a small program that does exactly that. Using the BLooper, BApplication, BRoster, BMessenger, BResources, and BGameAudio objects, we will create an application that spawns multiple instances of itself, where each instance can communicate with any other instance. But first, let's take a quick overview of how Haiku works.


Haiku Architecture Overview

Haiku is based around client/server architectures, where applications are clients to functional servers, and the functional servers are in turn clients to the kernel, at the lowest level.

You as the developer write Haiku applications using the C++ class libraries. Applications can be simple command line programs, or sophisticated multi-windowed, multi-threaded affairs.

The functional servers that run on top of the kernel include:

  1. the application server
  2. the roster
  3. the network server
  4. the media server
  5. the media addon server
  6. the input server
  7. the print server

Each server runs in its own protected memory space, and together with the other servers implement much of the functionality available through the C++ API . If the concept of a server embedded in an operating system seems confusing, just think of it as a 'subsystem'. You don't actually have to know how it works to make it work. The Haiku API as a whole is divided into 'kits'. These help organize the various object hierarchies into a classification that makes sense. The most important kits include:

  1. Application kit
  2. Interface kit
  3. Device kit
  4. Game kit
  5. Media kit
  6. Network kit
  7. Storage kit
  8. OpenGL kit
  9. Kernel kit

The Haiku kernel provides the basic operating system functions that the servers depend on. These functions include: multi-threading, multiple processors/multiple cores, preemptive multitasking, memory protection, virtual memory, semaphores, loadable drivers, shared libraries, and process scheduling. The scheduler was designed from inception to support multithreading as well as multiple processors. These features are intrinsic to Haiku and are the heart and soul of the Haiku API. You get these features for free when you use the API. For instance, our sample application will spawn 4 instances of itself, and each instance can run on its own CPU, with no explicit coding needed.

The application server is the first server that applications interface with. It is an instance of the BApplication C++ class that gets things started. The application server handles window management, graphics rendering, user interface interaction, event handling and more. An exceptional feature of Haiku that sets it apart from other operating systems is the fact that every window has, by default, two separate threads. One thread for graphic updates, and a second thread for user interaction. One thread runs inside the application server, and the other inside the application. This allows visual updates to occur completely separately from IO. This is a major contributing factor to the overall responsiveness of Haiku. It also means that every Haiku application with a window is both multithreaded, and supports multiprocessing by default. More threads can be spawned as needed, and Haiku natively supports the POSIX threads interface. This solves the common problem of applications becoming unresponsive during disk, CD, or network activities.

The Storage Kit provides access to the BFS file system, which has basic database functionality built in. The storage server gives you direct access to directories, files, indexes, and add/update/delete notifications that you can design. This saves you the effort of either writing your own database functions, or having to use a 3rd party library. The Haiku mail system uses the these functions to store emails. The result is that your email storage is limited only by the size of your hard disk. Another great feature is the 'live' search mechanism built into the file system. And once registered, you can receive continuous updates for searches matching your query criteria. Since common file attributes are automatically indexed, file system searches are very quick.

The network server provides inetd and configuration, such as DHCP. The network server was recently extended to support WEP wifi with encryption. Haiku includes applications for ftp, ftpd, telnet, as well as the poorman web server. The diner and Cherokee web servers are also available with many more Haiku-specific features.

The media server is the backbone for processing multimedia streams. It was designed to provide low latency access to audio, video, and image data types. The server manages the multimedia streams through a pipeline of buffers that are dispatched to data handlers. Each handler can hook into a media stream. Once it has done so, a handler can read or change the media stream in the pipeline. Buffers are implemented as shared memory, and are accessible by multiple applications without the need to copy the buffers. The media server can also synchronize different media streams by means of a global scheduling object. This is vital for processing video and audio together.


Application Fundamentals

At the core of any Haiku GUI application are the following objects: BApplication, BWindow, BView, BMessage, and BLooper. Each BLooper has a thread that acts as an event loop, where messages are handled. BApplication and BWindow are both derived from BLooper and so they each have a thread that handles messages. This is how window updates and IO are handled separately.

When an application is run in Haiku, it's BApplication object is created. This starts the application's main thread and event loop. As you will see in the MessageDemo code, it is at this point that an application can send and receive messages. Once the application thread is started, the application can create windows, which also create threads with event loops. Once a window is created and displayed, it can send and receive messages, which are generally related to user interactions, such as mouse or keyboard events.

BMessage is used to encapsulate Haiku messages. At a minimum, a BMessage must have a unique identifier, also known as the 'what' of a message:

  enum
  {
          M_ABOUT_REQUESTED = 'abrq'
  };
  
  BMessage *msg = new BMessage(M_ABOUT_REQUESTED);
  

BMessage also has the capability to pass an arbitrary amount of additional data in a message. BMessage provides a number of member functions for packing or 'flattening' various data into a message, and for finding, or 'unflattening' the data after it is received.

Messages are used for communication between applications, windows, and threads, for drag-and-drop, and for accessing the clipboard.

BMessenger is used to send a message to a target, which is a BLooper. Within a simple application or window, you can simply call SendMessage with a message you want, for example:

  SendMessage(B_QUIT_REQUESTED);
  

This puts the B_QUIT_REQUESTED message in the message queue of the application or the window, to be handled in MessageReceived(). In order to send messages between applications, you have to uniquely identify the receiving application. To do this you need to know the application's signature, as defined in the BApplication initialization:

  BApplication("application/x-vnd.dw-MessDemo1");
  

If you are sending a message to one of several instances of an application, as we are, you must differentiate between application instances by using its team_id:

  

BMessenger(const char* signature, team_id team, status_t* error);


Let's talk about MessageDemo

Message Demo demonstrates inter application messaging, and uses a number of interesting Haiku objects.

When launched, MessageDemo spawns 5 instances of itself. The 5th instance sends the A_GET_CONNECTED message to the first four and then terminates. The other four display themselves and take user input. Upon receipt of the A_GET_CONNECTED message each instance does the following:

  • Retrieves the identifying team_id for each of the instances
  • Moves itself to a quadrant of the screen
  • Sets its window title

When the applications are displayed, each one displays 4 buttons, each button corresponds to one of the four applications. When you click on button 1, the M_PLAY_SOUND message is sent to Application 1. When Application 1 receives that message it plays its corresponding sound.

That's what it does, let's look at the code.

The first thing we do is subclass BApplication in App.h so we can add our own App and MessageReceived methods:

 
  class App : public BApplication
  {
  public:
          App(void);
          void MessageReceived(BMessage *msg);;
  };

In our App method we use the global be_roster to fill list with apps that match the application signature specified by mySig. Each application instance will spawn another, and rerun this same code. CountItems will increase until there are 5 app instances. When there are 5, the 5th instance will use Broadcast() to send the A_GET_CONNECTED message to every application running. It will be ignored by every app except MessDemo. Messaging in Haiku is efficient so there's no performance hit.

 
be_roster->GetAppList((const char *)mySig, list);
 if (list->CountItems() < 5){
       // spawn new app instance
       fprintf(stderr, "Launching another appn");
       be_roster->Launch(mySig);
 } else {
       // broadcast to all apps
       BMessage msg(A_GET_CONNECTED);
       status = be_roster->Broadcast(&msg);        
       fprintf(stderr, "Got 5 apps, quittingn");
        PostMessage(B_QUIT_REQUESTED);
 }     

MessageDemo needs its own version of App::MessageReceived. This is because in Haiku the Application object has one event loop thread, and each Window has another. In MessDemo, the application receives the user generated messages and must pass them on to the Window to be processed. All other messages are sent to the application object for standard processing.

be_app is another 'built-in' Haiku global. Be_app always points to the current application object. In this case we use it to get the first window. In MessDemo the first window is also the only window, so things are simple. We use PostMessage() to send the message to the window.

 
switch (msg->what){
   case A_GET_CONNECTED:
       fprintf(stderr,"MD: Get Connectedn");
   case M_PLAY_SOUND:{
                  
       fprintf(stderr,"passing msg to  the window's handlern");
       be_app->WindowAt(0)->PostMessage(msg);
       break;
   }
   // Quit all instances of App
   case B_QUIT_REQUESTED:
   // pass messages on to App's handler
   default:
        BApplication::MessageReceived(msg);
   break;
}
 

OK, let's say we click on button 2. When we go into the MessageReceived method for MainWindow, we see that the event handler code for that button is this:

 
// send remote message to App 2
if ((err = bmsg2->SendMessage(M_PLAY_SOUND)) != B_OK){
   fprintf(stderr,"App2: failure in SendMessage - %sn", strerror(err));
   PostMessage(B_QUIT_REQUESTED);
} else {
   fprintf(stderr, "Sent msg to App 2n");
}
  

bmsg2 is a BMessenger object that was already initialized to application instance 2 using the application signature and the team_id:

 
bmsg2 = new BMessenger(mySig, team_id2, &err);

BMessenger object uses SendMessage() to send the M_PLAY_SOUND message to the BLooper specified by the mySig application signature and the team_id for application 2. That message is sent to the BLooper for application instance 2, which then passes it on to the window, where the event handler plays the second audio resource.

MessageDemo uses the BSimpleGameSound object to play the wav or mp3 audio files.

The array of audio files is defined as:

 BSimpleGameSound *Sounds[4];
 gs_audio_format format;

Format is initialized here:

 format.frame_rate = 22050; // CD quality
 format.channel_count = 1;  // mono
 format.format = 0x11;      // unsigned 8-bit
 format.byte_order = 1;
 format.buffer_size = 0;
  

The audio files are then read in from the resource file:

 //load the sound data from the BResource and create array of 
//BSimpleGameSound objects for (i=0; i < 4; i++){ err = res->GetResourceInfo(B_RAW_TYPE, i, &Name, &Size); if (!err){ fprintf(stderr, "Can't read audio resource from resource file - %sn",strerror(err)); PostMessage(B_QUIT_REQUESTED); break; } const void *Data = res->LoadResource(B_RAW_TYPE, i, &Size); Sounds[i] = new BSimpleGameSound(Data, Size, &format); fprintf(stderr,"nNumSounds = %dn",i); } //check to see if the last sound object was initialized int result = Sounds[3]->InitCheck(); if (result != B_OK){ fprintf(stderr,"Problem starting sound device - %sn",strerror(result)); PostMessage(B_QUIT_REQUESTED); }

When the application gets the M_PLAY_SOUND message all it has to do is run InitCheck() to validate, and then play the file:

 if ((err=Sounds[whoami-1]->InitCheck()) != B_OK){
     fprintf(stderr,"bad audio InitCheck - %s",strerror(err));
  } else
        Sounds[whoami-1]->StartPlaying();
  

MessDemo also has a File/Quit menu item and implements some spiffy multi-application quit code. In this case, when you select quit for one application, they all quit:

  be_roster->GetAppList(mySig,&appTeams);
   
  for (i=0; i< appTeams.CountItems(); i++){
     myTeamID = (team_id)appTeams.ItemAt(i);
     BMessenger *bmsg = new BMessenger(mySig, myTeamID, &err);
   
  // send quit msg to all apps
  if ((err = bmsg->SendMessage(B_QUIT_REQUESTED)) != B_OK){
     fprintf(stderr,"Failed to send quit request, SendMessage - %sn", strerror(err));
     PostMessage(B_QUIT_REQUESTED);
  }        
  delete bmsg;
  }


Coding Tips

If and when you decide to do some application coding of your own on Haiku, here are some tips.

When you use resources, QuickRes is a nice tool to add resources into your resource file. And remember that the application signature in the resource file must match the signature in App.h.

I like to use fprintf for debugging. It's a little untraditional because it mixes console printing with a GUI. How does that work? It works pretty well if you use Paladin as your IDE, and if you start Paladin from a Haiku Terminal instance: bring up a Terminal from Tracker. After installing Paladin you can start it from Terminal by typing: /Haiku/apps/Paladin/Paladin. When you run your application, any standard IO output from fprintf will print to Terminal.


Using Application Instances

All very clever you say, but why would you write an application with multiple instances? There are several ways this can be useful. One reason is redundancy. When a process is critical and yet prone to failure it is useful to have 2 or more copies that monitor each other. If one instance dies it can either send a message it is quitting, or if it fails to respond, the other instances will know it has died and can take appropriate action. This is how monitors are implemented.

Applications can start with the same code but run completely different code by loading different library modules and invoking different functions. The Haiku kernel kit allows this with the load_add_on() method. In this way, the 4 instances of MessageDemo could start as the same program but perform completely different functions.

As Intel and AMD develop CPUs with 2, 4, 6, and 8 cores, developers have to come up with new ways to efficiently spread calculations over those cores. If calculations can be spread across multiple applications, the operating system scheduler will run the applications, and even the threads, across multiple system cores. Game coders are implementing IO, physics, AI, and graphics across separate modules for better game play. Haiku is friendly to coders who need to work with data and threads on multiple cores.


Summary

MessageDemo is a simple example to show the power and elegance of the Haiku API. With just a half dozen basic objects it lays out a foundation for applications that know about each other and can communicate effectively. Programming with Haiku is rich, rewarding, and easy. The objects used in MessDemo are just a few of the many powerful objects available to you in Haiku. Give Haiku a try and you will be amazed at how quickly you can write serious programs that use cool features. And when you have a cool application to share, upload it to Haikuware.com.


Haiku References

The Haiku Project

MessDemo Sample Code

A Programmer's Introduction to the Be Operating System.

The Be Book

Haiku Overview

Hello, World in Haiku

The Paladin IDE

Haikuware Application Respository

Haiku Coding Projects

e p (15)    32 Comment(s)

Technology White Papers

See More