16 Native Counter Objects

In this chapter, we are going to generalize the counter idea: instead of having just one global counter, we are going to have counter objects implemented as extensions. Of course, this is intended purely as a didactic exercise: such counters could much more easily be defined as Oz objects directly.

16.1 Counter Class

We derive a new class Counter class from the base class class OZ_Extension.

#include "mozart.h" 
 
class Counter : public OZ_Extension {
public:
  long * n;
  Counter();
  Counter(long*);
  static int id;
  virtual int getIdV();
  virtual OZ_Term typeV();
  virtual OZ_ExtensiongCollectV(void);
  virtual OZ_ExtensionsCloneV(void);
  virtual void gCollectRecurseV(void) {}
  virtual void sCloneRecurseV(void) {}
  virtual OZ_Term printV(int depth = 10);
};

A class Counter object contains a pointer to a malloced long. Why not simply have a member of type long: simply because we want to illustrate an application of finalization; malloced memory is a resource that needs to be freed when no longer needed.

The class Counter class provides implementations for a number of virtual member functions introduced by class class Oz_Extension. We are now going to explain each of them and provide the necessary code.

16.2 Counter Creation

For this we need the Counter() constructor and the new builtin counter_new. The constructor allocates a new long, sets n to its address and initializes it with 1. The builtin creates a new instance of the class Counter class, boxes it as an Oz value by invoking OZ_extension and returns the result.

Counter::Counter() { n = new long[1]; n[0]=1; }
 
OZ_BI_define(counter_new,0,1)
{
  OZ_RETURN(OZ_extension(new Counter));
}
OZ_BI_end

16.3 Type Testing

Every extension class should be uniquely identified. This is the purpose of virtual function getIdV. Here we illustrate the usual way of doing so: the class is equipped with a static id member and getIdV() returns it. This static member is initialized by oz_init_module() (see Section 16.9).

int Counter::id;
int Counter::getIdV() { return id; }

Your code will also need to test whether some OZ_Term is in fact a boxed class Counter.

inline OZ_Boolean OZ_isCounter(OZ_Term t)
{
  t = OZ_deref(t);
  return OZ_isExtension(t) &&
    OZ_getExtension(t)->getIdV()==Counter::id;
}

Additionally, you should probably provide a builtin to perform this test in Oz code:

OZ_BI_define(counter_is,1,1)
{
  OZ_declareDetTerm(0,t);
  OZ_RETURN_BOOL(OZ_isCounter(t));
}
OZ_BI_end

Finally, it would be nice if {Value.type C} would return the atom counter when C is a counter object.

OZ_Term Counter::typeV() { return OZ_atom("counter"); } 

16.4 Expecting Counter Arguments in Builtins

Obviously we need a way to unbox counter objects.

inline CounterOZ_CounterToC(OZ_Term t)
{
  return (Counter*) OZ_getExtension(OZ_deref(t));
}

Now we can define a convenient macro that we can use in the implementation of a builtin to wait until argument ARG is determined, check that it is a boxed class Counter, and declare a variable VAR to hold a pointer to its unboxed value.

#define OZ_declareCounter(ARG,VAR) \
OZ_declareType(ARG,VAR,Counter*,"counter",OZ_isCounter,OZ_CounterToC)

Next, we illustrate how to use this macro.

16.5 Operations on Counters

The first operation obtains the current value of the counter object, but does not change it. We use our new macro to state that the first argument (i. e. argument number 0) should be a determined boxed counter and that c should be set to point to its unboxed value.

OZ_BI_define(counter_get,1,1)
{
  OZ_declareCounter(0,c);
  OZ_RETURN_INT(*c->n);
}
OZ_BI_end

Thanks to our macro, if the argument is not determined, the builtin will automatically suspend, and if it is determined but is not a counter object, it will raise an error exception.

We can similarly define a builtin for setting the value of the counter. It takes 2 arguments: a counter object and an integer.

OZ_BI_define(counter_set,2,0)
{
  OZ_declareCounter(0,c);
  OZ_declareInt(1,i);
  *c->n=i;
  return PROCEED;
}
OZ_BI_end

Finally, we can define a builtin to obtain the current value of a counter object and post increment the counter by 1.

OZ_BI_define(counter_next,1,1)
{
  OZ_declareCounter(0,c);
  long i = *c->n;
  *c->n = i+1;
  OZ_RETURN_INT(i);
}
OZ_BI_end

16.6 Printing Support

Of course, it would be nice if {Show C}, when C is a counter object, would display <counter n> where n is the current value of the counter. This is easily achieved by defining virtual function printV to return an appropriate virtual string.

OZ_Term Counter::printV(int depth = 10)
{
  return OZ_mkTupleC("#",3,
                     OZ_atom("<counter "),
                     OZ_int(*n),
                     OZ_atom(">"));
}

16.7 Garbage Collection

An instance of an class OZ_Extension class lives on the heap and must be properly copied at each garbage collection. This is realized simply by creating a new instance (automatically allocated on the to heap) and initializing it with the appropriate info. In the case of a counter object, we must copy the n pointer. For this purpose we define a one argument constructor.

Counter::Counter(long*p):n(p){}
OZ_ExtensionCounter::gCollectV() { return new Counter(n); }

Cloning is a kind of copying used during search rather than garbage collection. Every variable and every data-structure that has token equality (rather than structural equality), e. g. OZ_Extension, is situated in a space: its home space, i. e. the computation space in which it was created. When its home space H is cloned, the data-structure D must also be cloned: the clone of D must be situated in the clone of H. In the present case, for simplicity we only intend to support counters at top level; thus, the sClone method should never be used:

OZ_ExtensionCounter::sCloneV() { Assert(0); return 0; }

16.8 Finalization

When all references to a counter object disappear, we would like the malloced long to be freed. We cannot easily register a counter object for finalization from the C++ code (this will have to be delegated to Oz code), but we can provide the implementation of the finalization handler.

OZ_BI_define(counter_free,1,0)
{
  OZ_declareCounter(0,c);
  free(c->n);
  return PROCEED;
}
OZ_BI_end

16.9 Native Functor

We must now package this library as a native functor. This is done by providing the function oz_init_module() which returns a table of builtins. Here, it must also initialize the static member Counter::id.

OZ_C_proc_interface * oz_init_module(void)
{
  static OZ_C_proc_interface table[] = {
    {"new",0,1,counter_new},
    {"is",1,1,counter_is},
    {"get",1,1,counter_get},
    {"set",2,0,counter_set},
    {"next",1,1,counter_next},
    {"free",1,0,counter_free},
    {0,0,0,0}
  };
  Counter::id = OZ_getUniqueId();
  return table;
}

Assuming the code above is put in file counter-obj.cc, we first compile and then create a DLL as follows

oztool c++ -c counter-obj.cc
oztool ld counter-obj.o -o counter-obj.so-`oztool platform`

16.10 Oz Wrapper Module

The counter object native library will now be wrapped in an Oz module that registers every new counter object for finalization.

functor 
import 
   CNT(new:NEW is:Is get:Get set:Set next:Next free:Free)
   at 'counter-obj.so{native}' 
   Finalize(guardian)
export 
   New Is Get Set Next
define 
   Register = {Finalize.guardian Free}
   proc {New C}
      {NEW C}
      {Register C}
   end 
end


Denys Duchier, Leif Kornstaedt and Christian Schulte
Version 1.4.0 (20080702)