Table of Contents

Overriding Base Methods

Overriding Constructors

Objects Without Child Objects

An object constructor (OM_NEW() method), takes the same message structure opSet as the OM_SET() method. The message contains the ops_AttrList field, being a pointer to a taglist containing the initial object's attributes. Implementation of a constructor for an object without child objects is simple. The superclass constructor is called first, then, if it succeeds, the constructor initializes object instance data, allocates resources needed and sets initial values of attributes from tags passed via ops_AttrList.

A rule of thumb when overriding constructors is to never leave a half-constructed object. The constructor should either return a fully constructed object, or fail completely, freeing all successfully obtained resources. This is important if the object obtains more than one resource and any of the resource allocation has failed (for example allocating a big chunk of memory or opening a file). An example implementation below obtains three resources: A, B and C:

IPTR MyClassNew(Class *cl, Object *obj, struct opSet *msg)
{  
  if (obj = DoSuperMethodA(cl, obj, (Msg)msg))
  {
    struct MyClassData *d = (struct MyClassData*)INST_DATA(cl, obj);

    if ((d->ResourceA = ObtainResourceA()
     && (d->ResourceB = ObtainResourceB()
     && (d->ResourceC = ObtainResourceC())
    {
      return (IPTR)obj;    /* success */
    }
    else CoerceMethod(cl, obj, OM_DISPOSE);
  }
  return NULL;
}

If the object destructor frees resources A, B and C (which would be logical considering the constructor allocates them), the cleanup job may be delegated to the destructor. It requires however, that the destructor must be prepared for destruction of a not fully constructed object. It can't assume all three resources have been allocated, so it should check every resource pointer against NULL before calling a freeing function. The destructor also takes care of calling a superclass destructor when resources are freed. See Overriding Destructors for some destructor example code and explanation.

The only question remaining is what CoerceMethod() does and why it is used instead of a plain DoMethod()? The CoerceMethod() call works exactly the same as DoMethod(), but performs method coercion by a forced call to the dispatcher of the class specified as the first argument instead of the dispatcher of the object's true class. It makes a difference, when the class in question is later subclassed. The flowchart below explains the problem:


Explanation of CoerceMethod().

The class B on the diagram is a subclass of the class A and similarly, the class C is a subclass of B. Let's assume an object of the class C is being constructed. As every constructor calls the superclass first, the call goes up to rootclass (the root of all BOOPSI classes) first. Then going down the class tree, every class constructor allocates its resources. Unfortunately the constructor of class A has been unable to allocate one of its resources and decided to fail. If it had just called DoMethod(obj, OM_DISPOSE) it will unnecessarily execute destructors in classes B and C, while constructors in these classes have been not yet fully executed. Even if these destructors can cope with this, calling them is superfluous. With the CoerceMethod() the destructor in the class A is called directly. Then class A constructor returns NULL, which causes constructors in classes B and C to fail immediately without resource allocation attempts.

Objects With Child Objects

While retaining the same principles, the constructor of an object with subobjects is designed a bit differently. The most commonly subclassed classes able to have child objects are Application, and Group. The Window class is also often subclassed in a similar way. While a Window object can have only one child, specified by MUIA_Window_RootObject, this child often has multiple subobjects. The constructor should create its child objects first, then insert them into the ops_AttrList taglist and call the superclass constructor. If it succeeds, then resources may be allocated if needed. As any of the three constructor stages may fail, proper handling of errors becomes complicated. Also inserting objects created into the taglist as values of child tags (like MUIA_Group_Child) is cumbersome. Fortunately one can use the DoSuperNew() function, which merges the creation of subobjects and the calling of the superclass into one operation. It also provides automatic handling of failed child object construction. An example below is a constructor for a Group subclass putting two Text objects in the group.

IPTR MyClassNew(Class *cl, Object *obj, struct opSet *msg)
{  
  if (obj = DoSuperNew(cl, obj,
    MUIA_Group_Child, MUI_NewObject(MUIC_Text,
      /* attributes for the first subobject */
    TAG_END),
    MUIA_Group_Child, MUI_NewObject(MUIC_Text,
      /* attributes for the second subobject */
    TAG_END),
  TAG_MORE, msg->ops_AttrList)) 
  {
    struct MyClassData *d = (struct MyClassData*)INST_DATA(cl, obj);

    if ((d->ResourceA = ObtainResourceA()
     && (d->ResourceB = ObtainResourceB()
     && (d->ResourceC = ObtainResourceC())
    {
      return (IPTR)obj;    /* success */
    }
    else CoerceMethod(cl, obj, OM_DISPOSE);
  }
  return NULL;
}
An important thing to observe is the fact, that DoSuperNew() merges the taglist passed to the constructor via the message ops_AttrList field and the one specified in the function arguments list. It is done with a special TAG_MORE tag, which directs a taglist iterator (like NextTagItem() function) to jump to another taglist pointed by the value of this tag. Taglist merging allows for modifying the object being constructed with tags passed to NewObject(), for example adding a frame or background to the group in the above example. The automatic handling of failed child objects works in the following way: when a subobject fails, its constructor returns NULL. This NULL value is then inserted as the value of a "child" tag (MUIA_Group_Child) in the example. All MUI classes able to have child objects are designed in a way that: Finally DoSuperNew() returns NULL as well. This design ensures that in case of any fail while building the application, all objects created are disposed and there are no orphaned ones.

Overriding Destructors

The only task of a destructor is freeing resources allocated by the constructor and other methods (some resources may be allocated on-demand only). In any case the destructor must leave the object in the same state as right after DoSuperMethod()/DoSuperNew() in the constructor. After that the destructor calls a super class destructor. The destructor receives an empty message.
IPTR MyClassDispose(Class *cl, Object *obj, Msg msg)
{
  struct MyClassData *d = (struct MyClassData*)INST_DATA(cl, obj);

  if (d->ResourceA) FreeResourceA();
  if (d->ResourceB) FreeResourceB();
  if (d->ResourceC) FreeResourceC();
  return DoSuperMethodA(cl, obj, msg);
}
The example destructor follows the example of the constructor in the Overriding Constructors article. Three resources obtained in the constructor are freed here. The destructor is also prepared for a partially constructed object, every resource is checked against NULL before freeing. If for some type of resource NULL is a valid handle, an additional flag may be added to the object instance data area.

Overriding OM_SET()

The OM_SET() method receives an opSet structure as its message. The structure is defined in the <intuition/classusr.h> header.
struct opSet
{
  ULONG              MethodID;            /* always OM_SET (0x103) */
  struct TagItem    *ops_AttrList;
  struct GadgetInfo *ops_GInfo;
};
The most important field is ops_AttrList. It is a pointer to a taglist containing attributes and values to be set. The ops_GInfo field is an obsolete legacy thing and is not used by modern components like MUI or Reggae. The method implementation should iterate the taglist and set all attributes recognized. The operation of setting an attribute may be just setting some field in an object instance data, it may also trigger some actions (like for example object redrawing). It is recommended however that complex actions are implemented as methods rather than attribute changes. A reference implementation of OM_SET() may look like this:
IPTR MyClassSet(Class *cl, Object *obj, struct opSet *msg)
{
  struct TagItem *tag, *tagptr;
  IPTR tagcount = 0;

  tagptr = msg->ops_AttrList;

  while ((tag = NextTagItem(&tagptr)) != NULL)
  {
    switch (tag->ti_Tag)
    {
      case SOME_TAG:
        /* attribute setting actions for SOME_TAG */
        tagcount++;
      break;

      /* more tags here */
    }
  }

  tagcount += DoSuperMethodA(cl, obj, (Msg)msg);
  return tagcount;
}
The taglist iteration is done with the NextTagItem() function from the utility.library. The function returns a pointer to the next tag each time it is called and keeps the current position in tagptr. The advantage of this function is automatic handling of special tag values (TAG_MORE, TAG_IGNORE, TAG_SKIP), they are not returned, but their actions are performed instead. The OM_SET() function returns the total number of recognized tags. It is implemented with tagcounter. It gets incremented on every tag recognized and finally the number of tags recognized by superclass(es) is added. Common bugs in OM_SET() implementation are:
In some rare cases a subclass may want to override an attribute completely, so it is not passed to superclasses. This can be done by replacing the tag (not value!) by TAG_IGNORE. There is one caveat however. In most cases in C and C++, the taglist is built dynamically on the stack from variable arguments of a function like SetAttrs(). It is possible however, that a taglist is a static object (for example a global one, or created in an allocated chunk of free memory). In this case changing a tag is a permanent operation, which may have unexpected results. This remark also applies for changing a value of a tag before passing it to a superclass. A safe solution is to clone the taglist with the CloneTagItems() function from the utility.library. Then changes are made in the copy and this copy is passed to the superclass. The copy is then freed with FreeTagItems(). The disadvantage of this solution is that cloning a taglist may fail due to lack of free memory and this possibility must be handled somehow.

Overriding OM_GET()

The OM_GET() method, used for getting an attribute from an object, receives an opGet structure as its message. The structure is defined in the <intuition/classusr.h> header file:
struct opGet
{
  ULONG  MethodID;           /* always OM_GET (0x104) */
  ULONG  opg_AttrID;
  ULONG *opg_Storage;
};
Unlike OM_SET(), this method handles only one attribute at a time. The attribute is placed in the opg_AttrID field. The field opg_Storage is a pointer to a place where the attribute value should be stored. It is defined as a pointer to ULONG, but it may point to anything (for example to some larger structure). It allows for passing attributes not fitting in a 32-bit variable. Because OM_GET() does not have a taglist iteration loop, its implementation is simple:
IPTR MyClassGet(Class *cl, Object *obj, struct opGet *msg)
{
  switch (msg->opg_AttrID)
  {
    case Some_Integer_Tag:
      *msg->opg_Storage = /* value of the tag */;
    return TRUE;

    case Some_String_Tag:
      *(char**)msg->opg_Storage = "a fixed string value";
    return TRUE;
  }

  return DoSuperMethodA(cl, obj, (Msg)msg);
}
The implementation consists of a switch statement with cases for all recognized attributes. If an attribute is recognized, the method should return TRUE. It is very important, as MUI notifications rely on OM_GET() and will not work on the attribute if TRUE is not returned. Unknown attributes are passed to the superclass. The DoSuperMethodA() call may be alternatively placed as the default clause of the switch statement. It is important that msg->opg_Storage is dereferenced when storing the attribute value. If the value type is not integer, a typecast is needed. For a value type T, the dereferencing combined with typecast is denoted as *(T*).