Developer Area Developer Area
www.be.com

Becoming A Be Developer

Becoming A Be Developer

Join Registered Developer Program

Registered Developer Area

Developer Library

Developer Library

Developer Events

Search Contact


Approaching Be

An introduction to programming for the BeOS



As the Be Operating System (BeOS ) has emerged onto the computing landscape, thousands of software developers have been attracted to its feature set, simplicity and performance. This interest has been amplified by the introduction of versions of the BeOS that can run on mainstream PowerPC systems, such as PowerMacintosh and its clones. As interest has risen, one of the questions we get most often from programmers is, "Where do I start?"

Programming for the BeOS is similar in many ways to programming for other operating systems, at least those that can be considered "modern." But there are fundamental differences in the design and goals which can affect how you might structure your application. Programmers want to understand those fundamental design goals and the philosophy of the OS design before they start to write code.

This white paper is designed to answer this set of basic questions. It is written with the programmer in mind, though some non-programmers may find it useful as well. It is also written to be useful from the perspective of the programmer coming to the BeOS from a MacOS or Windows environment. Many of the topics will be discussed in a way that compares the BeOS approach with those used by these other systems.

The content is broken into three sections. The first will discuss the underlying fundamental design and philosophy of the BeOS and how they might impact the design of your software. The second section will discuss the general architecture of the BeOS and identify the key components a programmer deals with. In the third section, we'll walk through the creation of a basic, simple application.

There are a number of other white papers and primers available that build on this document. These can be found in the Developer section of the Be web site at http://www.be.com.

[Note: Source code for this tutorial can be found at ftp://ftp.be.com/pub/samples/preview/intro/HelloWorld.zip]


Fundamental Concepts

In creating the BeOS, the Be engineering team has used a set of fundamental assumptions and concepts as guides. These concepts are so ingrained in the OS that they can have important impacts on how you might architect your own applications. If you are approaching the BeOS from the MacOS space, or even the Windows space, it's also important to understand where the BeOS differs; many of the differences lie in these fundamental concepts.

One common joke around the Be offices is that "BeOS" actually stands for "buzzword enabled operating system." Without a doubt, the BeOS meets and exceeds the market expectations of what a "modern" operating system should look like. Five of these buzzword concepts are important to understand because they impact the design of software written for the BeOS; object-orientation, preemptive multitasking, protected memory, symmetric multiprocessing and extensive multithreading.

Object-Orientation

Probably the most significant difference between the BeOS and current mainstream operating systems is that the BeOS is an object-oriented operating system. This means that its application programming interface (API) is composed of objects, rather than the procedure calls you might find in the MacOS or Windows. When you create an application for the BeOS, you will create a derivative of the basic BApplication object, and you will use either the standard objects built into the BeOS such as BWindow and BButton, or derivatives of these objects.

Object-oriented programming has been around for awhile, but it is only recently that software development has utilized its basic concepts. The BeOS is one of the few operating systems to base itself entirely upon the object-oriented concept. When looking at the BeBook, the reference book that describes the entire BeOS API, you won't find a set of procedures. What you will find is an entire application framework, with objects that can be linked together to create an application, create its windows and interface, manage network connections, and manipulate most of the data objects you'll create within your application. These objects contain a large amount of functionality built-in; you won't need to start from scratch nor connect separate procedures into complex chains. A lot of the basic work is already done for you. And further, you'll be able to use objects created by other BeOS programmers from within your application as well.

But what this means is that within in the BeOS, you'll need to program in an object-oriented language. The BeOS API is based on C++. Java, a close cousin of C++, will also be supported in the future. Be chose these languages because they are based on industry standards and have the support of a wide range of vendors.

If you understand object-oriented concepts, and have used C++, you'll have no problems jumping into the BeOS. If you have programmed on the MacOS, and have used an application framework such as Metrowerk's PowerPlant or Apple's MacApp, you are even in a better position because the BeOS API will look very familiar to you. Likewise, if you have used Microsoft Foundation Classes for Windows, you will recognize many of the concepts found in the BeOS.

If you don't know C++ or Java, but would like to learn about object-oriented programming, the BeOS is a great place to start because it is a new, relatively uncluttered operating system that is built on object-oriented concepts. So you won't have to deal with a lot of exceptions and work-arounds in the API that you might find in other systems.

Preemptive Multitasking

Most programmers are familiar with multitasking - the ability to run more than one application at a time and have the operating system worry about splitting CPU time between them. Preemptive multitasking goes a step further, and it's important to understand the difference, especially if you are coming from the MacOS. Several operating systems, including the MacOS, use a system known as cooperative multitasking, which means that each application must voluntarily give up time so that other applications can continue running. In the MacOS, this system is represented by the WaitNextEvent() procedure call, which turns control back to the OS until another event is detected. Of course, if an application does not call WaitNextEvent(), or delays calling it, other applications may have trouble executing in a smooth fashion.

Preemptive multitasking eliminates the need for something like WaitNextEvent(). In a preemptive system like the BeOS, the operating system itself allows each application to execute for a specific period of time, depending upon its priority and the number of tasks currently executing in the system. When that time is up, the BeOS will stop the application, no matter what it is doing, and pass control onto the next process. This results is a much smoother and more interactive system for the user.

Of course, what this also means is that you, as a programmer, must always remember that your application can be interrupted at any time. Usually, you don't need to do anything differently under this system. But if you are accessing a shared resource, it's possible that other processes sharing that resource will jump in (or out) during your "suspended" state, and that may affect you. Remember, unlike cooperative multitasking, you won't necessarily be guaranteed to finish a sequence of instructions before the OS switches your code out. There are ways of telling the OS how important certain tasks are (such as real-time audio or video, etc.) and there are ways of locking resources if needed. We'll talk more about that in a bit.

Protected Memory

One of the key complaints users have about personal computer systems is how often applications crash, and in doing so lock up their entire machine. The reason for this is that the failed application has usually attempted to alter physical memory owned by another application, or by the operating system itself. These changes quickly ripple through the system and cause both the application and entire machine to crash.

To prevent this, the BeOS uses a scheme known as protected memory. What this means is that the memory owned by one process cannot be accessed by any other process. When an application starts up under the BeOS, it is told that it has the entire logical address space (currently 32-bits or 4 gb of memory) all to itself. Applications under the BeOS use pointers or other direct references to memory. If you are coming from Windows, this is pretty much old hat, but the MacOS often uses indirect memory "handles" to allow for memory management.

Of course, most real computers don't have 4 gb of memory installed. The BeOS therefore keeps a close watch on each application's real memory use. It breaks down the entire 4 gb memory space into "pages" and then keeps track of how many pages are used, and how often each page is accessed. If more physical memory is needed by another process, the BeOS will temporarily unload several least-used pages onto disk to make room, and will reload those pages when needed again. This is the real meaning behind "virtual memory." Within the BeOS (as well as within Unix, Windows NT and other modern operating systems) virtual memory is always on.

Protected memory goes a long way towards keeping a system stable. If you are writing applications, servers or many other pieces of code, you'll have difficulty bringing the system down. Another one of the reasons for this is that the BeOS is built upon a "client-server" architecture where system resources such as the screen or networking hardware are controlled by a "server" which multiple "clients" talk to in order to gain access. All applications lie on the client side of this architecture. This provides a further level of protection by minimizing resource collisions. If you are one of the developers creating code that lies on the server side of the architecture, such as hardware drivers, you'll need to make sure your code is simple, fast and bullet-proof. The BeBook provides further advice for building this type of software.

Symmetric Multiprocessing

Another key limitation of most mainstream operating systems today is an assumption that there is only one microprocessor per system. Although some systems, like the MacOS, can make limited use of a second processor, most OSes were not designed with multiprocessing in mind.

The BeOS was designed as a multiprocessor OS from the beginning. The initial systems it was created on all used two microprocessors, and the BeOS now runs on systems containing one to four processors. The BeOS itself is not limited -- it can support "n" processors in theory -- although realistically there are significant limitations today in mainstream hardware implementations once you pass about eight processors.

Further, the implementation of multiprocessing in the BeOS is "symmetric." As a programmer, you do not have to worry about which processor your application uses at any particular point in time. In designing a BeOS application, you do nothing differently in order to run on single processor systems and eight processor systems (although you may want to take the number of processors into account in a few calculation-intensive cases for optimization purposes) Your application may start on one processor, move to another processor, and move back many times during execution. All of this is transparent to your code.

But one thing that symmetric multiprocessing implies is that you can create applications that do many (many, many) things in parallel. And in order to do this, there is one last BeOS concept that you need to keep in mind...

Extensive Multithreading

In traditional operating systems, each application can be thought of as a single "thread" of execution, from the top of the code to the eventual end. If you have multiple applications open, there are multiple threads of execution happening at once, and they share the processor(s) time.

In the BeOS, threading goes much further. In order to take maximum advantage of multiple processors, each application should be broken down into multiple threads of execution. If an application has a single thread, then it must execute on only one processor at a time (although that processor may change during execution.) But if an application has multiple threads, then that application can make use of multiple processors in parallel. This can vastly improve the execution time of code.

In testing various execution models, we've found out that breaking an application into multiple threads can make overall execution and task switching faster and smoother, even on single processor systems. It turns out that extensive threading aids and amplifies the affect of preemptive multitasking.

Knowing this, the BeOS itself is heavily threaded. Even at a complete standstill, the BeOS makes use of dozens of threads. Every server in the system spawns threads constantly. Further, it is virtually impossible to write an application that is not threaded. In creating a BApplication object, two threads are automatically created for you, and are managed by the BApplication object (you'll need to do nothing other than use the application object.) Every window in the BeOS creates two threads thus every window can theoretically run on its own processor. Multithreading, symmetric multiprocessing, and preemptive multitasking all build on each other, and it's this synergy which allows the BeOS to deliver much of its power.

Other than the object-oriented nature of the BeOS, multithreading has perhaps the most significant impact on software design. Luckily, object-orientation and threading go hand-in-hand. If you are coming to the BeOS from either the MacOS or Windows, there are two things involving threading to keep in mind;

  • Think of each thread as an "event-loop"
    Both the MacOS and Windows use the concept of an application event-loop through which all events from the real-world (mouse-clicks, key presses, etc.) pass. You can think of each thread in the BeOS as a separate event-loop. This means that each application has multiple event-loops, not just one. In the BeOS, a mouse-click in a window will not pass through an application-wide event loop, it will be delivered directly to the window thread. This also means that an application may be processing multiple events at once. Sounds a bit complex, but it's actually quite simple because in an object-oriented system, each object worries only about its own events. Thus code is usually much simpler and cleaner than in single event-loop systems.

    Now, take the above paragraph, and replace the word "events" with the word "messages." In the BeOS, the "event-loop" is referred to as the "message loop." This is because each thread can deal with a much wider range of messages than simply a set of real-world mouse clicks and key presses. You can find more information about threads by reading about the BLooper and BHandler objects in the BeBook (and looking at which objects inherit from these), and more about messages by looking at the BMessage and BMessenger objects.

  • You may need to "lock" objects
    Multiple threads mean multiple things can happen at the same time. What happens if an object attempts to change a value that's part of a BWindow object at the same time that BWindow is drawing itself? The results can be interesting. You can prevent these conflicts by locking an object that you are using, if necessary, and unlocking it afterwards. During the lock period, one process has exclusive access to an object. For those of you familiar with threading, this might sound a lot like the concept of a "semaphore." You're right, they are exactly the same. You can find out more about locks by looking at the BLocker object in the BeBook.



System Architecture

Now that we've described the basic concepts underlying the BeOS, we can outline the architecture of the system in a more concrete fashion. The block diagram below provides an overview of the components of the BeOS;

The BeOS can be divided into three main groups of components; the Kernel, the BeOS Servers, and the BeOS Kits.

The Kernel
The BeOS employs a microkernel to abstract the hardware and to provide basic OS services. As you might expect, the BeOS kernel is optimized to take advantage of multiple processors, and for handling large numbers of simultaneously executing threads. Kernel capabilities can be extended through software drivers, which are dynamically loaded and unloaded as needed by the system.

BeOS Servers
An array of software servers encapsulate key areas of functionality used by applications and by the OS itself, and represent the heart of the BeOS. Examples of these servers are the App Server , which provides graphics services, the Storage Server , which provides file system and database functionality, the Network Server , which provides TCP/IP capabilities, and the Audio Server , which handles real-time audio streams from a variety of sources. These servers are automatically started at system start-up, are designed around a client-server model, and are heavily threaded themselves.

BeOS Kits
Applications never access the BeOS servers directly. Rather, applications make use of the BeOS Kits, which in turn know how to interface with each server. Each Kit is made up of a set of C++ objects, which can be used and subclassed. These objects in turn call the servers and each other to accomplish more complex tasks. Examples of the BeOS Kits are the Interface Kit, handling most window, view, and drawing functions, the Application Kit , handling application-level capabilities and communications, and the Media Kit, handling audio and video capabilities. The array of BeOS software kits provide a wide range of classes and functions that applications use to build complex capabilities. These Kits are constantly being extended and improved by Be through each release of the BeOS.

A sampling of the core BeOS software kits found in the BeBook include;

Application Kit
Contains core application objects, such as BApplication, BMessage (for communications), and BLooper (for creating new threads).

Storage Kit
One of the key advantages of the BeOS is that it provides both file system capabilities and integrated database capabilities. The Storage Kit works with the Storage Server to provide objects such as BFile, BDirectory, and BDatabase.

Interface Kit
The largest of the kits, the Interface Kit works with the Application Server to provide objects such as BWindow and BView, which provide the basis for other interface objects such as BListView, BCheckBox, BTextView, and many others. The Interface Kit (through BView) also encapsulates all drawing capabilities.

Media Kit
The Media Kit handles real-time streams of data through the system which means it's the kit that deals with audio and video streams. The kit employs a "subscriber" model, through objects such as BAudioSubscriber, which allows multiple processes to access and manipulate a single stream of data.

Midi Kit
This kit provides a set of classes that know how to decipher and construct MIDI messages and that can listen to and broadcast to hardware MIDI ports, or communicate with the built-in software MIDI synthesizer

Device Kit
This kit provides a generalized interface for building drivers that extend the capabilities of the kernel such as hardware drivers, graphics drivers and others. It also includes such objects as BSerialPort.

Network Kit
The Network Kit provides TCP/IP services based on the Berkeley Sockets (BSD) model. It also provides classes that know how to send and receive e-mail.

3D Kit
The 3D Kit offers a 3-D rendering engine and classes with which a developer can easily construct and manipulate interactive 3-D graphics. This kit will be expanded in the future to incorporate the OpenGL® standard.

The servers and kits work closely together, and in most cases the kits are simply the interface through which applications make requests of the servers. For example, if during a draw method you draw a line into a window;

In this case, you've created a BView object in your code (BViews are responsible for any drawing on the screen, or off it for that matter.) Each view has a Draw() method which you write to give the view a distinctive appearance (if it wasn't unique, you'd simply use one of the standard view objects in the Interface Kit and forget about writing a Draw() method.) The Draw() method would call the built-in StrokeLine() method to draw the line. StrokeLine() actually sends a message to the App_Server, telling it to draw the line, and then it returns immediately. While the next line of your view Draw() code is executing, the App_Server is busy drawing the line (there's that multithreading concept again) by calling the appropriate drivers (there may be acceleration code in the hardware, for example) to change the screen buffer.

Thus the objects in the BeOS kits correspond to a BeOS server. Here are some of the kits and the corresponding servers they communicate with;

Application KitApp_Server and Kernel
Storage KitStorage_Server (File System)
Interface KitApp_Server
Media KitAudio_Server and Video_Server
MIDI KitAudio_Server
3D KitApp_Server
Kernel KitKernel
Device KitKernel
Game KitApp_Server
Network KitNet_Server and Mail_Daemon

Now that we've built a foundation of BeOS concepts, the best way to understand how they fit together is to write an application. To do this, we'll look at a BeOS implementation of a simple application, HelloWorld. The discussion in the next session assumes that you understand the basics of C++.



HelloWorld Application

HelloWorld will do a few simple things. At startup, it will create a window. Within that window it will write the words "Hello, World!". Closing the window will cause the application to quit.

What we'll find in creating this application is that most of the work is already done for us in the basic BeOS objects. We're going to define each object with a header file and an implementation file. With such a simple application, we could take shortcuts and place the code in fewer files, even one, but the purpose here is to write HelloWorld in a way similar to how you'd write a full-blown application.

First off, we need to create the application object. The interface to this object is simple and straightforward;

// -----------------------------------------------------------------------
// HelloWorldApp.h
// -----------------------------------------------------------------------
     #pragma once

     #include <Application.h>

     class HelloWorldApp          : public BApplication
     {
          public:
               HelloWorldApp();
     };

The #pragma once line should be placed at the front of any header file you create. CodeWarrior uses this to ensure that no header is accidentally included twice. This is simpler than #define, #if and #endif statements which can be used to do the same, though #pragma once is not as portable across development systems.

The next line includes the BeOS BApplication definitions. Since HelloWorldApp will be a subclass of the general BApplication object, we need to include these definitions.

The HelloWorldApp class is defined next, and is very simple. HelloWorldApp is a subclass of BApplication. It inherits all of the normal behavior of a BeOS application. It also inherits a main thread for the application itself.

There is only one new method defined in this simple example, a constructor for the class, HelloWorldApp(). We'll use the constructor to create the application's window. The implementation of the HelloWorldApp is equally simple;

// -----------------------------------------------------------------------
// HelloWorldApp.cpp
// -----------------------------------------------------------------------
     
     #include "HelloWorldApp.h"
     // other includes go here

     main()
     {
          HelloWorldApp     myApplication;
          myApplication.Run();
          return (0);    
     }


     HelloWorldApp::HelloWorldApp()     
     				: BApplication(application/x-vnd.Be-HelloWorldSample")
     {
          // Set up windows and stuff
     }

For clarity, we've left out the window creation code. We'll get to that in a minute. As with any C and C++ application, a main() routine is required. In this case, main() is simple and looks pretty much the same no matter how complex your application might be. main() creates an application object of type "HelloWorldApp". Then the application is started by calling Run(). Run() embodies the main thread of the application, and so does not return until the application has been instructed to quit. When that happens, main() returns 0 as an overall result (this last line is not strictly necessary.)

The only other routine in the implementation is the HelloWorldApp() constructor. Since HelloWorldApp is derived from BApplication, we allow the BApplication constructor to execute and send it the applications' creator signature "application/x-vnd.Be-HelloWorldSample". We'll return to this constructor after we define the HelloWindow object.

The window we need to create is actually made up of two objects. There is the window itself, and within the window there will be a view that draws the "Hello, World!" text. We'll start at the bottom of this ladder, and define the view first.

// -----------------------------------------------------------------------
// HelloView.h
// -----------------------------------------------------------------------
     #pragma once

     #include <StringView.h>

     class HelloView          : public BStringView
     {
          public:
          		HelloView(BRect frame, const char* name, const char* text);
     };

// -----------------------------------------------------------------------
// HelloView.cpp
// -----------------------------------------------------------------------
     #include "HelloView.h"

     HelloView::HelloView(BRect frame, const char* name, const char *text)
                    : BView(frame, name, text, B_FOLLOW_ALL, B_WILL_DRAW)
     {
          SetFont(be_bold_font);
          SetFontSize(24);
     }

In the header, HelloView is defined as a subclass of the BStringView object. BStringView is a subclass of the more general BView. There are a myriad of objects in the BeOS which derive from BView -- almost anything that draws on the screen is a BView object, because that's what BView is designed to do. BViews also have the ability to contain other views. A BStringView simply displays a string. The class does all the work of allocating and freeing memory for you.

In the implementation, we provide an empty constructor whose purpose is to pass along arguments to the BStringView constructor. We pass the frame of the view, provided as a BRect rectangle object, the name of the view (which can be used by more complex applications to find and manipulate the view,) and the text we wish to display. We also pass two other arguments. The first, B_FOLLOW_ALL instructs the HelloView to resize itself to fit the window it's in whenever the window's size changes. By doing this, we don't have to worry about window changes, the underlying BView object has that functionality built in. The second parameter, B_WILL_DRAW, will tell the window the view is in that HelloView draws on the screen (rather than being merely a container for other views) and should be told whenever an screen update is needed.

Now that we have the view defined, we can define the window.

// -----------------------------------------------------------------------
// HelloWindow.h
// -----------------------------------------------------------------------
     #pragma once

     #include <Window.h>

     class HelloWindow          : public BWindow
     {
          public:
                                    HelloWindow(BRect frame);
               virtual     bool     QuitRequested();             //Override
     };

// -----------------------------------------------------------------------
// HelloWindow.cpp
// -----------------------------------------------------------------------
     #include "HelloWindow.h"
     #include "HelloView.h"

     HelloWindow::HelloWindow(BRect frame)     
                 :BWindow(frame, "Hello", B_TITLED_WINDOW, B_NOT_RESIZABLE | B_NOT_ZOOMABLE)
     {
          BRect       viewRect = Bounds();
          HelloView*  theView = new HelloView(viewRect, "HelloView", "HelloWorld!");
          AddChild(theView);
          Show();
     }


     HelloWindow::QuitRequested()
     {
          be_app->PostMessage(B_QUIT_REQUESTED);
          return true;
     }

The HelloWindow class only has two features unique to it over the BWindow class it derives from; a constructor and an override of the QuitRequested() hook method.

In the constructor, we pass the arguments to the inherited BWindow constructor. frame determines where the window is placed on the screen. "Hello" is the title of the window, used in the window's title bar (if it has one, which in this case it does.) B_TITLED_WINDOW sets the type of the window to a normal movable window with a titlebar at top, and B_NOT_RESIZABLE | NOT_ZOOMABLE fixes the window size as unchanging and eliminates the zoom-box.

Within the constructor itself, we create a HelloView object. To get the correct size for the view, we can get the HelloWindow bounds rectangle by calling the built-in Bounds() method. We then call the window's AddChild() method to install the view inside the window. Lastly, we cause the HelloWindow to be visible by calling Show().

Within the BeOS, each BWindow object runs in its own thread. What this means is that each time you create a BWindow object, behind the scenes a message loop is created (BWindow derives from BLooper) and is automatically set to start running (thus we don't have to call Run() explicitly.) When the user clicks in the window "go-away" box, essentially we are quitting the window thread. So whenever the user closes the window, QuitRequested() is called. In the case of BWindow, QuitRequested() will return true as a default and will delete the window.

In our case, we want to do one more thing. If the user closes the window, we also want the HelloWorld application to quit. So, we override QuitRequested(). There are a couple of ways to tell the application to quit. In this case, we post the B_QUIT_REQUESTED message to the application. be_app is a global variable available within any application that simply points to the the application object, so we can use be_app to PostMessage(B_QUIT_REQUESTED). What happens is that a new quit message is sent from the window thread to the application thread. We need to return true so that the inherited BWindow features of HelloWindow will finish closing the window properly, and quitting the windows thread. At the same time, the application is picking up the quit message we sent and beginning to shut down the entire application. Though trivial in this case, this parallelism is an important part of the BeOS, and will be used often in more complex applications.

Now that we have defined the window and view, we can complete the application implementation;

// -----------------------------------------------------------------------
// HelloWorldApp.cpp
// -----------------------------------------------------------------------
     
     #include "HelloWorldApp.h"
     #include "HelloWindow.h"

     main()
     {
          HelloWorldApp     myApplication;
          myApplication.Run();
          return (0);    
     }


     HelloWorldApp::HelloWorldApp()     
     				: BApplication("application/x-vnd.Be-HelloWorldSample")
     {
          BRect     frameRect;
          frameRect.Set(100,100,280,140);
          HelloWindow*  theWindow = new HelloWindow(frameRect);
     }

We've added an #include to bring in the HelloWindow header, and we've added a body to the constructor. When the application is created, a window is automatically created as well, placed at (100,100,280,140) on the screen. The application simply needs to create the window -- the HelloWindow does the rest of the work as we've defined above.

Once complete, our CodeWarrior project should look something like the window below.


Completed CodeWarrior project for HelloWorld

We can then make the project (Project menu/make.) Make sure that within CodeWarrior you've set the project preferences to name the application "HelloWorld", otherwise the name of the application will be "Application". Another exercise that might be useful is to take HelloWorld and modify it so that a "Quit" button appears in the window as well, which would close the window and quit the application. This would simply take the BView and messaging approaches we've already used here and apply it to a different type of interface object.

This paper has provided an introductory overview of programming in the BeOS. For further information and sample code, take a look at the tutorials section of the Developer area on the Be web site (http://www.be.com) You may also want to take a look at the BeBook, the API of the BeOS, to get a more thorough overview of the BeOS Kits and other objects.

[Webmaster's Note: This tutorial was written by Mark Gonzales, our former VP of Marketing. No kidding.]

 

The BeOS News and Events Developers User Groups Support BeStore BeWare