15 Global Counter Library

Oz can be very simply extended with new functionality and datatypes implemented in C or C++. This capability is often used to interface Oz to existing libraries: for example, the regex and gdbm modules are implemented in this fashion.

Every extension is presented to the system in the form of a native functor, i. e. a functor which happens to be implemented in C or C++ rather than in Oz.

In this chapter, we define a native functor that exports a  next function which returns the next value of a global counter each time it is called.

15.1 Implementation

#include "mozart.h" 
 
static long n;
 
OZ_BI_define(counter_next,0,1)
{
  OZ_RETURN_INT(n++);
}
OZ_BI_end
 
OZ_C_proc_interface * oz_init_module(void)
{
  static OZ_C_proc_interface table[] = {
    {"next",0,1,counter_next},
    {0,0,0,0}
  };
  n = 1;
  return table;
}

OZ_BI_define(counter_next,0,1) indicates that we are defining a procedure  counter_next that implements a new builtin which takes 0 input arguments and returns 1 output value. OZ_BI_end is required to finish this definition.

 OZ_RETURN_INT(d) is a macro that causes the builtin to return integer d as an Oz integer. This should only be used when the builtin has one unique output value; and it is essentially equivalent to the code sequence:

OZ_out(0)=OZ_int(d);
return PROCEED;

Finally procedure  oz_init_module implements the native functor: it performs arbitrary initializations and then returns a table of builtins. Each entry in this table consists of (1) the name of the export, (2) the input arity, (3) the output arity, (4) the procedure implementing the builtin. The table must be terminated by an entry whose fields are all zero.

Note that global variable n is explicitly initialized by  oz_init_module rather than with a static initializer. Here, it probably makes no difference, but you cannot in general rely on the fact that constructors for global objects will be properly invoked when the native functor is loaded. What actually happens varies from one system to another. The only reliable technique is to perform all initializations in  oz_init_module.

You may also define the variable  oz_module_name to give your native module a name that can be used when printing the builtins which it exports. This is particularly useful for debugging and for interactively looking at values. For example, you could give it the name "GlobalCounter":

char oz_module_name[] = "GlobalCounter";

15.2 Compilation

We must now make this native module available as a shared object library. First we must compile it and create counter.o:

oztool c++ -c counter.cc

Then we must produce a platform specific shared object library:

oztool ld counter.o -o counter.so-`oztool platform`

You may find it useful to create a Makefile of the form:

PLATFORM = $(shell oztool platform)
NATIVES  = counter
TARGETS  = $(addsuffix .so-$(PLATFORM),$(NATIVES))
all: $(TARGETS)
%.so-$(PLATFORM): %.o
        oztool ld $< -o $@ 
%.o: %.cc
        oztool c++ -c $< -o $@ 

oztool is a program that invokes the facility named as its first argument with appropriate options. For example, it is essential to invoke the same C++ compiler and with the same e. g. code generation options as were used for building the Oz emulator; otherwise it will not be possible to dynamically link your library into a running Oz process. Normally, the Oz emulator is compiled without run time information (option -fno-rtti for g++) and without support for C++ exceptions (option -fno-exceptions for g++). oztool c++ automatically invokes the right compiler with the right options. oztool is documented in Chapter 6 of ``Oz Shell Utilities''.

Even more complicated is how to create a DLL from a compiled object file: it varies depending on the system, compiler and linker used. Under Windows, the sequence of necessary incantations is so arcane and highly magical, it could well endanger your sanity. Fortunately oztool ld automatically takes care of the details.

15.3 Deployment

Normally, you will then place the resulting shared object file, e.g. counter.so-linux-i486 on a Linux system, in an installation directory; let's call it install. If your site has several platforms sharing one file system, then you can place all platform specific shared object libraries that you create from counter.cc in the same install directory. They all have distinct names since the platform name is appended.

In an Oz functor, you then write an import of the form:

Cnt at 'install/counter.so{native}'

The '{native}' suffix indicates to the system that this denotes a native functor whose platform independent basename is install/counter.so. The module manager dynamically links the appropriate platform specific shared object library (by appending the platform specific extension to the basename) and makes available the module it defines as the value of Cnt. The body of your functor can invoke {Cnt.next} to get the next value of the global counter.

In the emacs OPI, you can try this out immediately:

declare [M] = {Module.link ['install/counter.so{native}']}


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