The SHWorker inheritance API (derives from SHComponent -> SHDistributableObject -> BArchivable)

If you want the nodes in your tree to do any useful work, you are going to need to get your executable code out to them, and that means writing an SHWorker subclass.  SHWorkers aren't very complicated; they are like BLoopers, but even easier to use!  Note that SHWorkers inherit from the SHComponent class; this gives them some handy methods they can call, as well as some pure virtual methods that they must implement.

SHWorkers, like BLoopers, have a MessageReceived() method that is called whenever a BMessage arrives for them.  Unlike BLoopers, however, SHWorkers follow normal C++ object semantics--it is safe to put an SHWorker on the stack if you want to, and you can delete an SHWorker using the delete operator instead of calling Quit().  Instead of a Run() method, SHWorkers have Start() and Stop() methods.  And you can't call PostMessage() on an SHWorker to send it a BMessage--instead, you call GetMessenger() on it to get a BMessenger that targets the SHWorker, and then call SendMessage() on the BMessenger.

The methods you are required to implement in your SHWorker subclass are few.  They are:



SHWorker(BMessage * archive)

You must create a constructor for your class that can create an object from the data contained in the passed-in BMessage.  This constructor essentially does the opposite of your Archive() method;  instead of converting an object to a BMessage, it converts a BMessage back to an object.  Be sure to call SHWorker(archive) in the initialization list!



static BArchivable * Instantiate(BMessage * archive)

This static method (part of the BArchivable API) is also used to turn BMessages back into real objects, and should be implemented like this:

BArchivable *
MySHWorkerSubclass::
Instantiate(BMessage * archive)
{
   // replace the second argument to validate_instantiation with your class's name!
   if (!validate_instantiation(archive, "MySHWorkerSubclass")) return NULL;
   return new MySHWorkerSubclass(archive);
}



virtual status_t GetAddOnSpec(SHFileSpec & addTo) const

This method (part of the SHDistributableObject API) should be overridden to return an SHFileSpec that indicates where the add-on executable file(s) for this class can be found.  Like BArchivable::Archive(), each subclass should call the GetAddOnSpec() method of its parent class;  in this way the SHFileSpec gets built up into a list of all the files needed to instantiate the object.

status_t
MySHWorkerSubclass::GetAddOnSpec(SHFileSpec & spec) const
{
   status_t ret;

   // Allow our parent class to say what files he needs
   ret = SHWorker::GetAddOnSpec(spec);
   if (ret != B_NO_ERROR) return ret;

   // Specify location of Intel version of the add-on file
   SHFlavor x86("add-ons/x86/SHMyWorkerSubclassAddOn", SH_ARCH_BEOS_X86);
   ret = spec.AddFlavor(x86);
   if (ret != B_NO_ERROR) return ret;

   // Specify location of the PowerPC version of the add-on file
   SHFlavor ppc("add-ons/ppc/SHMyWorkerSubclassAddOn", SH_ARCH_BEOS_PPC);
   ret = spec.AddFlavor(ppc);
   if (ret != B_NO_ERROR) return ret;

   return B_NO_ERROR;
}



virtual const char * GetName()

This method (part of the SHComponent API) should be overridden to supply an identifying text string for your SHWorker object.  There are no conditions imposed on what this name is, in particular it is not required (by SockHop) to be unique in any way.  This name can be used to identify the SHWorker in certain situations, however, so it's not a bad idea to make each SHWorker's name unique.



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

This method must be overridden to store all relevant state for your SHWorker into (archive).  This method helps fulfill the SHWorker's BArchivable interface, and is how your SHWorker object gets transported over the TCP streams.  At a bare minimum, your Archive() method should look like this:

status_t
SHTestWorker::
Archive(BMessage * archive, bool deep) const
{
   // Let our base class do its archiving first
   status_t ret = SHWorker::Archive(archive, deep);
   if (ret != B_NO_ERROR) return ret;

   // store any additional data describing the current state
   // of this SHTestWorker object into the BMessage here
   // ...

   return B_NO_ERROR;
}



Class Destructor

One more requirement is placed on SHWorkers--they must call the Stop() method at the beginning of their destructor method.  This is necessary to avoid race conditions during the destruction of the SHWorker object.  If you forget the Stop() call, you will get warning messages printed to stdout, and the SockHop node may crash.



And there are some methods which you may override if you wish, but don't have to:



virtual void MessageReceived(BMessage * msg)

This, of course, is equivalent to the standard BLooper method to handle incoming BMessages.  You don't have to implement it, but if you don't your SHWorker won't do very much that is interesting!



virtual bool IsInterestedIn(BMessage * msg)

This method can be used as an optimization, to cut down on the number of BMessages sent to your SHWorker.  Before sending any BMessage to your SHWorker, the SockHop node thread calls this method to see if your SHWorker wants it.  The default implementation of this method just returns true; which is to say, by default an SHWorker is "interested in" every kind of BMessage.  If you wish, you can override this method to return false for BMessages that your SHWorker can't use.  That will save the overhead of sending a BMessage that will be ignored anyway.  It is important to remember, however, that IsInterestedIn() will be called from a thread other than the SHWorker's thread, and that by default no locking occurs to synchronize access to the data in your SHWorker object.  Thus, you may need to serialize access yourself (by calling Lock() and Unlock()) in this method.