Derived from: none
Declared in: <support/Archivable.h>
It can be useful at times to take a living, breathing object and archive it--copy it to a form that can be saved on disk, stored, or handed to another application. For example, before it quits, your application might want to save an object in its current state so that it can be resurrected without losing any information when the user next launches the application. Or you might want to bundle up an object--possibly along with a reference to the code needed to run it--so that it can be delivered in a message to someone else. Your application might permit users to drag objects to other applications or accept objects dragged from elsewhere.
The BArchivable class defines a uniform strategy for archiving objects and instantiating them again from their archives. It contributes a bit of the implementation itself, but mostly defines a protocol that other classes can follow. For an object to be archivable, its class must derive, directly or indirectly, from the BArchivable class and implement the protocol. Since this is a mix-in class suitable for multiple inheritance, a class can derive from BArchivable in one line of inheritance and from more substantial classes in another line.
Archiving an object entails capturing its current state in a package of some kind. The package that the BeOS uses is a BMessage object. The BMessage can be delivered somewhere, just like any other message, or it can be flattened to a file or other data repository. An object is unarchived by reconstructing it from the values stored in the BMessage archive.
The archiving mechanism rests on a small set of global functions and on three functions that are defined for each kind of object that can be archived:
These functions and their global companions are discussed in more detail below. The sections that follow first look at the mechanism for archiving an object and then at the process for instantiating an object from the archive.
To archive an object, you create a BMessage object and pass it to the object's Archive() function:
BMessage message; theObject->Archive(&message);
The message will be the archive--it will contain all the information necessary to re-create the object.
It's the job of the Archive() function to write a description of the object into the message. Each class is responsible for archiving only those properties of the object that it defines. To incorporate properties archived by its base classes, it should begin its implementation of the function by calling the version of Archive() it inherits.
For example, suppose a Slider class is derived from BControl in the Interface Kit. In addition to the values it inherits from the BControl, BView, and BHandler classes, it defines data members for the slider's minimum and maximum values. Its Archive() function might look something like this (minus any checking for errors):
status_t Slider::Archive(BMessage *archive, bool deep) { inherited::Archive(archive, deep); archive->AddString("class", "Slider"); archive->AddInt32("max", MaxValue()); archive->AddInt32("min", MinValue()); return B_OK; }
By incorporating the inherited version of Archive(), this implementation ensures that all important properties that the Slider inherits from its base classes are also archived.
The chain of calls to inherited functions ends with the BArchivable root class. Its version of Archive() puts the object's class name into the BMessage under the name "class". This information is used later when instantiating objects from the archive. It's good practice for derived classes to put their own class names in the "class" array as well, as the Slider class does in the example above, provided that instances of the class can be initialized from the archive. An abstract class should not put its name in the array. (See validate_instantiation() for more on this issue.)
It's possible that an object may not need all the information archived by its base classes. If not, it can forgo calling the inherited function and add only selected information to the archive. In this case, it must duplicate the BArchivable version of Archive() and begin by putting the name of the object's class in the "class" array:
archive->AddString("class", class_name(this));
For example, the Slider class might choose to archive only its current values and frame rectangle (although the result in this case wouldn't be all that useful):
status_t Slider::Archive(BMessage *archive, bool deep) { archive->AddString("class", class_name(this)); archive->AddString("class", "Slider"); archive->AddInt32("cur", Value()); archive->AddInt32("max", MaxValue()); archive->AddInt32("min", MinValue()); archive->AddRect("frame", Frame()); return B_OK; }
For complex objects, the archive BMessage can get quite large. Classes should therefore be careful about what information they archive. In general, only information that cannot otherwise be reconstructed when the object is unarchived should be added to the BMessage. For example, transitory states that won't initialize the unarchived object--such as whether the object is currently hidden or not--should not be recorded in the archive.
Similarly, default settings don't need to be archived. If an object has, say, a choice of three values for a particular parameter, it's most efficient to treat one of them as a default; the function that reconstructs the object will set that value if it doesn't find either of the other two possibilities in the archive.
If a class doesn't have any data to add to the BMessage archive, it doesn't need to implement an Archive() function; it can rely on the version it inherits. However, it must implement the constructor and static Instantiate() function for its objects to be successfully unarchived.
In addition to the BMessage container, Archive() is passed a second argument, a bool flag that indicates whether the archive should be deep or shallow. By default the flag is true, meaning that the archive should be deep.
For a deep archive, a class should include in its archive any other objects that it owns by virtue of their being assigned to it. For a shallow archive, it should generally exclude the assigned objects it owns. For example, in the Interface Kit, a BView archives its children for a deep archive, but not for a shallow one. A BPictureButton archives the BPictures that it was assigned when the archive is deep, but not when it's shallow. Similarly, the flag controls whether a BMenuItem archives the submenu it controls and whether a BListView archives its BListItems.
An object includes another object's archive in its own by first calling that object's version of the Archive() function and then calling AddMessage() to add the resulting BMessage archive to its own archive. For example:
status_t TheClass::Archive(BMessage *archive, bool deep) { inherited::Archive(archive, deep); archive->AddString("class", "TheClass"); . . . if ( deep ) { BMessage cronyArchive; if ( crony->Archive(&cronyArchive, deep) == B_OK ) archive->AddMessage("crony", &cronyArchive); } }
Neither a deep nor a shallow archive should include objects that the target object is associated with but doesn't own. For example, a BView doesn't archive its parent or the BWindow to which it's attached.
The archiving mechanism runs into difficulty if a derived class adds information to the archive under a name without knowing that a base class has also added data under the same name. When unarchiving the object, the derived class will expect its data to be at index 0 for that entry. However, since the base class version of Archive() was called before the derived class wrote to the BMessage archive, its data won't be at the expected location. Errors will result.
To prevent such clashes, all the Archive() functions implemented in the BeOS software kits use private names--that is, names beginning with an underbar, such as "_min" and "_max". Use a different convention for naming archived data in the classes you define.
Every class that defines a kind of archivable object must implement a constructor that can initialize the new object from a BMessage archive. The constructor is a counterpart to the Archive() function: it begins by calling its immediate base class version of the constructor, typically through a member initialization list, and it unarchives only those values that the corresponding version of Archive() put in the BMessage. Continuing the Slider example above, a simple constructor might look something like this (again without error checking):
Slider::Slider(BMessage *archive) : BControl(archive) { int32 max, min; archive->FindInt32("max", &max); SetMax(max); archive->FindInt32("min", &min); SetMin(min); . . . }
The constructor may, of course, also need to initialize values that were not archived.
An Archive() function and a constructor are sufficient to archive and unarchive an object. However, because a constructor bears the name of its class, it forces you to know at compile time the class of the object being unarchived. This inhibits any dynamic use of the archiving mechanism.
The Support Kit solves this problem in two steps:
The task of an Instantiate() function is to create a new instance of its class (using the new operator) and call upon the constructor to initialize it from the BMessage archive. For example:
Slider *Slider::Instantiate(BMessage *archive) { if ( validate_instantiation(archive, "Slider") ) return new Slider(archive); return NULL; }
This function first calls validate_instantiation() to make sure the BMessage object is in fact an archive for a Slider object. The test rests mainly on the class names that are stored in the archive's "class" array; validate_instantiation() looks for the target name anywhere in the array. If the archive can be matched to the target class ("Slider," in the example), Instantiate() produces a new object and returns it. Otherwise, it returns NULL.
The Instantiate() function is static; to call it, you need an instance of its class or, once again, you need to know the class name at compile time. The second function, instantiate_object(), gets around this difficulty. When passed a BMessage archive, it looks for the first name in the "class" array, finds the Instantiate() function for that class, and calls it. Failing that, it picks another name from the "class" array (working up the inheritance hierarchy) and tries again.
instantiate_object() returns the object that Instantiate() produces as a BArchivable instance. You can use the cast_as() macro to cast the object to a more interesting class. For example:
BArchivable *unarchived = instantiate_object(archive); if ( unarchived ) { Slider *slider = cast_as(unarchived, Slider); if ( slider ) { . . . } }
As described so far, the archiving mechanism permits objects to be archived and reconstructed dynamically--provided that the application that does the unarchiving knows about the class of the target object. That is, the target's class must be included in the application's executable image; an application can't unarchive an object that it doesn't have the code to run.
To overcome this restriction, an additional convention allows a host application to unarchive instances of previously unknown classes: If the BMessage archive includes an entry referring to an add-on image, instantiate_object() will load the image if its first attempt to unarchive the object fails. If the add-on image contains the class definition for the archived object, instantiate_object() will then be able to find the Instantiate() function and produce the object. The entry's name should be "add_on", its type should be B_STRING_TYPE, and it should contain the signature of the add-on executable.
It's not defined how a host will interact with an unarchived instance of a previously unknown class. It's up to the parties to define entry points and protocols, just as it is for any other add-on module.
However, the framework of the Interface Kit already defines a protocol for how views (BView objects) interact. Views are added to other views in a window-based hierarchy; they respond to update messages and messages notifying them of user actions. The BShelf class takes advantage of this architecture to accept archived BView objects, of any derived class, and install them in a view hierarchy where they can draw and respond to user actions. BDraggers give users the ability to drag archived BViews--so called "replicants"--to a shelf. Both BShelf and BDragger are defined in the Interface Kit.
BArchivable(void) BArchivable(BMessage *archive)
Does nothing. Because the BArchivable class has no data members to initialize, both of its constructors are empty.
virtual ~BArchivable(void)
Does nothing. Because the BArchivable class doesn't declare any data members, its destructor has nothing to free.
static BArchivable *Instantiate(BMessage *archive)
Returns NULL. This is an abstract class; you should not attempt to create BArchivable instances.
However, in derived classes, Instantiate() should be implemented to return a new instance of the class allocated by new and constructed from the BMessage archive. The return type of the function should be specific to the class. For example:
TheClass *TheClass::Instantiate(BMessage *archive) { if ( !validate_instantiation(archive, "TheClass") ) return NULL; return new TheClass(archive); }
This function depends on a constructor that can initialize the new object from the archive BMessage.
See also: instantiate_object(), Archive(), Instantiation in the overview
virtual status_t Archive(BMessage *archive, bool deep = true) const
Places the name of the object's class in the archive, in an entry named "class". Because the BArchivable class doesn't declare any data members, this function has nothing else to do.
Derived classes must override Archive() to put data describing the current state of the object in the BMessage. In general, each implementation of this function should begin by incorporating the inherited version:
status_t TheClass::Archive(BMessage *archive, bool deep) { inherited::Archive(archive, deep); . . . }
If the class can be instantiated, it should also add its name to the "class" array:
archive->AddString("class", "TheClass");
The deep flag hints whether Archive() should include other objects--objects that belong to the archiving object because they were assigned to it--in the archive. It hints, for example, whether a BView (in the Interface Kit) should also archive its children or whether a BMenu should archive its BMenuItems. When the deep flag is true, these other objects should be included in the archive and they should also archive themselves deeply. When the flag is false, assigned objects should generally be excluded from the archive. An example is given under Deep and Shallow Archives in the "Overview" section.
Archive() should return any errors encountered when writing to the archive. If all goes well, it should return B_OK. If not, it should return B_ERROR or a more descriptive error code.
See also: instantiate_object(), Instantiate() static function, Archiving in the overview
virtual status_t Perform(uint32 op, void *data)
< Reserved by the BeOS for future use. For the time being, don't implement or call this function. >
The Be Book, in lovely HTML, for the BeOS Preview Release.
Copyright © 1997 Be, Inc. All rights reserved.
Be is a registered trademark; BeOS, BeBox, BeWare, GeekPort, the Be logo, and the BeOS logo are trademarks of Be, Inc.
Last modified June 30, 1997.