BArchivable

Derived from: none

Declared in: be/support/Archivable.h

[method summary]

BArchivable is a protocol for archiving and unarchiving objects. When you archive an object, you record its state into a BMessage that can be sent to another application, flattened and saved as a file, cached in memory, and so on. Unarchiving does the opposite: It takes a BMessage archive and turns it into a functioning object.


Archiving

To archive an object, you create a BMessage and pass it to the object's Archive() function:

   BMessage message;
   theObject->Archive(&message);

It's the job of the Archive() implementation to write a description of the object into the message. Each class is responsible for archiving the parts 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() that it inherits from its base class.

The chain of calls to inherited functions ends at the BArchivable root class. Its version of Archive() puts the object's class name into the archive under the field named "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, 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.)

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.

Deep and Shallow Archives

Archive()'s second argument, a bool flag, indicates whether the archive should be deep or shallow. By default the flag is true (deep).

For a deep archive, a class should include in its archive any other objects that it "owns." For a shallow archive, it should exclude these objects. For example a BView object archives its children for a deep archive, but not for a shallow one.

To perform a deep archive, an object invokes Archive() on the objects it owns, and adds the resulting archives to its own archive. For example:

   status_t TheClass::Archive(BMessage *archive, bool deep)
   {
       baseClass::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.

Names

Name collisions in an archive are not automatically detected and corrected. For example, if both class A and subclass B add fields named "dog", the unarchiving mechanism will get confused.

To try to avoid collisions, all the Archive() functions implemented in the Be kits use names beginning with an underbar ("_name") or the "be:" prefix ("be:name"). Use a different convention for naming archived data in the classes you define.


Instantiability

To be unarchivable, a class must implement a constructor that takes a BMessage archive as an argument. The constructor is a counterpart to the Archive() function: it begins by calling the constructor for its immediate base class, and then looks in the BMessage for the fields that were added by its Archive() function.

The class must also implement the static Instantiate() function (declared in BArchivable), which needn't do much more than call the archive-accepting constructor, and return a BArchivable pointer. For example:

   BArchivable *TheClass::Instantiate(BMessage *archive)
   {
       if ( validate_instantiation(archive, "TheClass"))
           return new TheClass(archive);
       return NULL;
   }

The validate_instantiation() function, provided by the Support Kit, is a safety check that makes sure the BMessage object is, in fact, an archive for the named class.


Unarchiving

To unarchive a BMessage, you call the instantiate_object() function. When passed a BMessage archive, instantiate_object() 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 a BArchivable instance. You then use cast_as() to cast the object to a more interesting class. A typical unarchiving session looks something like this:

   /* archive is the BMessage that we want to turn into an object. 
    * In this case, we want to turn it into a BView.
    */
   BArchivable *unarchived = instantiate_object(archive);
   if ( unarchived ) {
       BView *view = cast_as(unarchived, BView);
       if ( view ) {
           . . .
       }
   }

The BArchivable-derived class must be exported for unarchiving to work:

   class _EXPORT MyArchivableView : public BView {
      ...
      static BArchivable *Instantiate(BMessage *data);
      ...
   };

Dynamic Loading

As described so far, an application can only unarchive objects that it knows about--it can't unarchive an object that it doesn't have the code to run.

An additional convention gets around this restriction: A BMessage archive can include a B_STRING_TYPE field named "add-on" that contains the signature of an add-on that defines the archived object. If instantiate_object() fails to unarchive an object on its first try, it will look for the add-on image, load it, and try again.

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.


Constructor and Destructor


BArchivable()

      BArchivable(void) 
      BArchivable(BMessage *archive) 

Does nothing.


~BArchivable()

      virtual ~BArchivable()

Does nothing.


Static Functions


Instantiate()

      static BArchivable *Instantiate(BMessage *archive) 

Derived classes should implement Instantiate() to return a new BArchivable object that was constructed from the BMessage archive. For example:

   BArchivable *TheClass::Instantiate(BMessage *archive)
   {
       if ( !validate_instantiation(archive, "TheClass") )
           return NULL;
       return new TheClass(archive);
   }

Instantiate() must return a BArchivable *, regardless of the actual class in which it's implemented.

This function depends on a constructor that can initialize the new object from the archive BMessage. See Instantiability, above, for more information.

The default implementation returns NULL.


Member Functions


Archive()

      virtual status_t Archive(BMessage *archive, bool deep = true) const

The default implementation adds the name of the object's class to archive's "class" field. Derived classes must override Archive() to augment this implementation by adding, to the BMessage, data that describes the current state of the object. Each implementation of this function should begin by incorporating the inherited version:

   /* We'll assume that MyView inherits from BView. */
   status_t MyView::Archive(BMessage *archive, bool deep)
   {
       BView::Archive(archive, deep);
       . . .
   }

If the class can be instantiated directly from a derived class, it should also add its name to the "class" array:

   archive->AddString("class", "MyView");

The deep flag declares whether Archive() should include objects that "belong" to the archiving object. For example, a deep BView archive would include archived forms of the view's children. An example is given under Deep and Shallow Archives, above.

Archive() should return B_OK if it's successful; otherwise, it should return B_ERROR or a more descriptive error code.






The Be Book, in lovely HTML, for BeOS Release 4.

Copyright © 1998 Be, Inc. All rights reserved.

Last modified December 21, 1998.