Plasma GitLab Archive
Projects Blog Knowledge


Jump to:  OMake Home • Guide Home • Guide (single-page) • Contents (short) • Contents (long)
Index:  All • Variables • Functions • Objects • Targets • Options

Chapter 18  The GTK+ 2 binding (and a tutorial)

In this section we'll describe the process of creating a binding for GTK+ 2, and we use the identifier gtk in filenames to indicate that the file is part of the GTK binding. If you are building a different binding, the process will be nearly the same, but of course you will use a name other than gtk.

A binding is specified with the following input files.

  • gtk_types.h a header file that declares all the values and types in the binding.
  • gtk_lib.c a source file that #includes gtk_types.h and implements any functions.
  • gtk_post.ml, gtk-post.om any extra code that you wish to add to the binding. These files are optional.
  • values.export, structs.export, unions.export, and enums.export: these files list the names of the values and types that are part of the binding. The files themselves are optional—if you like, you can specify the values directly in the OMakefile.
  • OMakefile the description of the how to build the binding.

The output of the binding is the following files.

  • gtk.ml the OCaml interface to the binding,
  • gtk.om the OMake interface to the binding,
  • gtk_bindings.c the C implementation of the binding,
  • libgtk_bindings.so (Unix), or libgtk_bindings.dylib (MacOS X), or libgtk_bindings.dll (Win32).

18.1  The binding OMakefile

The OMakefile follows fairly standard boilerplate. The GTK OMakefile proceeds as follows. The entire file is as follows.

#
# Optionally build the GTK library
#
open build/Dll
open parse/C/Dll
open configure/gtk

########################################################################
# Generic configuration.
#
# The C files should be compiled with the -fPIC (position independent
# code) flag for efficiency.
#
static. =
    CAMLLIB = $(shell ocamlc -where)

CPP = gcc -E
CFLAGS += -g -O3 -fPIC $(GTK_CFLAGS)
INCLUDES += $(CAMLLIB) $(ROOT)/src/clib $(GTK_INCLUDES)
LDFLAGS += $(GTK_LDFLAGS) $(GTK_LIBS)
OCAMLINCLUDES[] +=
    $(ROOT)/src/libmojave

if $(equal $(SYSNAME), Darwin)
    CFLAGS += -DMACOSX
    export

VERSION = 0.5.0

########################################################################
# Dll config
#

#
# Define all the things in the C library that you want to
# be visible to ML.  Only those things mentioned here are visible.
#
# The array values are the names of the types (for structs, unions, enums)
# or the value names (for values).
#
structs[] = $(cat structs.export)
unions[] = $(cat unions.export)
enums[] = $(cat enums.export)
values[] = $(cat values.export)

gtk_types.i: gtk_types.c gtk_types.h
gtk_lib$(EXT_OBJ): gtk_bindings.c

private.libs = $(CBindings gtk, gtk_types, gtk_lib, version = $(VERSION), structs = $(structs), unions = $(unions), enums = $(enums), values = $(values), ml-post = gtk_post.ml, omake-post = gtk_post.om)

.DEFAULT: $(libs)

The module build/Dll defines the function CBindings that describes how to build the binding (called near the bottom of the file). The module configure/gtk is the configuration script that defines such variables as the include path GTK_INCLUDES, the linker/loader flags GTK_LDFLAGS, etc. You don't have to define a configuration module—if you don't have one, then you should define variables XXX_INCLUDES, XXX_LD_FLAGS, etc. directly.

18.1.1  values.export

The files *.export describe what is to be included in the binding. Here are some fragments.

sh>head -5 values.export
atk_action_do_action
atk_action_get_description
atk_action_get_keybinding
atk_action_get_localized_name
atk_action_get_n_actions

sh>head -5 structs.export
GTokenValue
GdkRectangle
GdkEventAny
GdkEventExpose
GdkEventNoExpose

sh>head -5 enums.export
    AtkCoordType
    AtkHyperlinkStateFlags
    AtkKeyEventType
    AtkLayer
    AtkRelationType

sh>head -5 unions.export
_GdkEvent

Each file is a list of names for the thing that is to be part of the binding. The items in values.export can be functions or other values. If it is a function, OMake will generate a wrapper for the function to process the arguments and result (and interface peacefully with the OCaml garbage collector).

18.1.2  structs.export

The struct names in struct.export are the values to be “marshaled,” meaning that a value of this type is represented as: a record in OCaml, and an object in OMake. For example, the C definition of GdkRectangle is as follows.

struct _GdkRectangle
{
  gint x; 
  gint y; 
  gint width;
  gint height; 
};

Since GdkRectangle is listed in structs.export, any function that returns a value of type GdkRectangle * (a pointer to a GdkRectangle) is automatically converted to a record/object. The OCaml definition is wrapped in a module with the prefix Struct_, defined as follows.

module Struct__GdkRectangle : sig
    type id
    val id : id
    type t =
        { id : id; 
          l_x : t_int; 
          l_y : t_int; 
          l_width : t_int; 
          l_height : t_int; 
        };;
end

The identifier id is an abstract value that provides a tag that identifies the type of value uniquely.

All conversions are performed automatically by the binding. If the C function expects an argument of type GdkRectangle *, it should be passed a value of type Struct__GdkRectangle.t; if the C function returns a value of type GdkRectangle *, the OCaml function returns a value of type Struct__GdkRectangle.t.

The OMake definitions are similar, but the values are represented as objects, not records. Again, the __id is a unique identifier that serves to tag the value. The fields of the struct become fields of the object.

public._GdkRectangle. =
    class _GdkRectangle
    public.const.__id = $(Dll_object_1__GdkRectangle)
    public. = 
        declare x # gint x; 
        declare y # gint y; 
        declare width # gint width; 
        declare height # gint height; 

18.1.3  CBindings

The final part of the OMakefile is the call the generate the binding itself. It has a lot of arguments; they correspond to all the parts of the binding.

private.libs = $(CBindings gtk, gtk_types, gtk_lib, version = $(VERSION), structs = $(structs), unions = $(unions), enums = $(enums), values = $(values), ml-post = gtk_post.ml, omake-post = gtk_post.om)

The first three arguments are as follows:

gtk
the name of the binding (corresponding to gtk.ml and gtk.om),
gtk_types
the name of the gtk_types.h file,
gtk_lib
the name of the gtk_lib.c file.

The version is the version number of the file. Normally, it should be specified with three integers separated by decimal points, like 0.5.0, etc. The next four arguments specify the *.export values. The final arguments specify the gtk_post files. All keyword arguments are optional.

18.2  gtk_types.h

The file gtk_types.h specifies the header files the contain the declarations for the binding. Normally, this file can be short, it simply #includes the files in the normal way. For GTK, the first part is normal enough.

#include <gtk/gtk.h>
#include <glib.h>

18.2.1  Explicit struct coercions

Sometimes, rather than having a struct be marshaled automatically, it is more useful to have the programmer call the marshaling functions explicitly. this is expecially true for recursive struct values. In GTK, for example, a GSList represents a list of items, using a standard cons-cell representation. The NULL value is the empty list.

struct _GSList
{
  gpointer data;
  GSList *next;
};

Since the data structure is recursive, we don't want to unmarshal it automatically.

Explicit marshaling should be defined in the file gtk_types.h using the __dll_typedef directive. In the following two cases, the types OpenGSList and OpenGtkItemFactoryEntry should be listed in structs.export.

__dll_typedef GSList OpenGSList;
__dll_typedef GtkItemFactoryEntry OpenGtkItemFactoryEntry;

These definitions effectively provide a typedef where the first value is opaque, and the second value is the be marshaled. Conversion between the two elements is performed with three functions (generated as part of the binding).

  • coerce_GSList : (struct__GSList) dll_pointer -> Struct__OpenGSList.t -> unit copy the second argument (OpenGSList) to the first argument (GSList) destructively.
  • make_GSList : Struct__OpenGSList.t -> (struct__GSList) dll_pointer create a GSList from the OpenGSList, nondestrictively.
  • make_OpenGSList : (struct__GSList) dll_pointer -> (t_OpenGSList) dll_pointer create a OpenGSList from the GSList.

18.2.2  Explicit values

Sometimes it is desireable to export #defined constants as part of the binding. Since the binding is created after runnning the C preprocessor, the binding generator has no knowledge of #defined constants, and they must be specified explicity. For example, gtk_types.h contains the following values.

static int sizeof_GValue = sizeof(GValue);
static int sizeof_GtkTextIter = sizeof(GtkTextIter);

static int g_TYPE_NONE = G_TYPE_NONE;
static int g_TYPE_INTERFACE = G_TYPE_INTERFACE;

The sizeof values are also not automatically computed by the binding generator, so they are listed explicitly as well.

18.2.3  Tagged unions

General union types can only be handled as opaque values because the case of the union is unknown. However, it is also accepted practice for C programmers to use tagged unions, where one of the fields of the union (usually the first one) is an integer that tags the kind of value.

In GTK, this is the case with the type GdkEvent, which represents some kind of window event like a mouse block, a keypress, or some other event. The C definition of GdkEvent is defined as follows.

union _GdkEvent
{
  GdkEventType type;
  GdkEventAny any;
  GdkEventExpose expose;
  ...
}

The GdkEventType is just an enumeration of the possible kinds of values. Each kind of event begins with the type.

typedef enum
{
  GDK_NOTHING = -1,
  GDK_DELETE = 0,
  GDK_DESTROY = 1,
  ...
} GdkEventType;

struct _GdkEventButton
{
  GdkEventType type;
  GdkWindow *window;
  ...
};

struct _GdkEventKey
{
  GdkEventType type;
  GdkWindow *window;
  ...
};

Tagged unions are a special case of union that can be marshaled automatically. The OCaml type becomes a disjoint union.

type t__GdkEvent = 
    | L__GdkEvent_default of enum_enum91 
    | L__GdkEvent_GDK_NOTHING of Struct__GdkEventAny.t 
    | L__GdkEvent_GDK_DELETE of Struct__GdkEventAny.t 
    | L__GdkEvent_GDK_DESTROY of Struct__GdkEventAny.t 
    ...

The OMake type is simply an object, where the __id field or the instanceof method can be used to determine the kind of event.

Tagged unions must be specified explicitly in the gtk_types.h, using a kind of switch syntax and the __tagged_union directive. Since the code is not legal C, it must be surrounded by the appropriate #ifdef.

#ifdef __DLL__
__tagged_union _GdkEvent {
default:
    GdkEventType type;
case GDK_NOTHING:
case GDK_DELETE:
case GDK_DESTROY:
    GdkEventAny any;
case GDK_EXPOSE:
    GdkEventExpose expose;
...
};
#endif /* __DLL__ */

18.2.4  Reference cells

In C, it is common practice to return some of a function's results through the arguments. For example, the function gtk_init(int *argcp, char ***argvp) initializes the library. Effectively, it takes a standard argument list, represented as an array of string pointers, where argc is the length, and argv is the array of pointers. That is, the function takes as input a pair int argc, char **argv, then it processes the arguments and removes any that are specific to GTK, then it returns the new argument list by assigning to *argcp and argvp.

In OCaml, this kind of value/result argument is specified most naturally as a reference cell. For those functions that have value/result arguments, it is often useful (but not necessary) to declare them in gtk_types.h to use reference cells.

void gtk_init(int DLL_REF(argc), char** DLL_REF(argv));

The OCaml binding has the following type. The Dll module provides functions to convert between types like string array and char dll_pointer dll_pointer.

gtk_init : char dll_pointer ref -> char dll_pointer dll_pointer ref -> unit

18.3  Callbacks

A callback is an upcall from C code to OCaml/OMake code. Because C is not a language with closures, there can only be a fixed number of callback functions. That is, you can't simply pass an OCaml function in a place where a callback is expected, because an OCaml function is representable as a C function pointer.

Fortunately, most well-designed libraries that use callbacks have a form of manual closure, where a function is passed as two parts: a function pointer, and opaque data that is to be passed to the function when it is called. Together, the function pointer and the opaque data (environment) can be used to form a closure.

Like many widget toolkits, GTK+ 2 defines an event-driven API. Specifically, the program installs callbacks on the components (widgets) that can handle or produce events, and then enters a main loop (in this case gtk_main_loop()). When an event occurs, like a button press or mouse motion, the appropriate callback is issued to process the event. The GTK function that we will use to register a callback is g_signal_connect_data, where instance is the widget, detailed_signal is the name of the “signal” (the callback), c_handler is the function pointer, and data is the opaque data.

gulong g_signal_connect_data (gpointer instance,
            const gchar *detailed_signal,
            GCallback c_handler,
            gpointer data,
            GClosureNotify destroy_data,
            GConnectFlags connect_flags);

18.3.1  OCaml callbacks

Let's look at OCaml first. In OCaml it is most natural to pass the callback as an OCaml function, so we actually want to define a function with the following type.

    val g_signal_connect :
        struct__GtkWidget dll_pointer ->
        string ->
        (Struct_callback_simple.t -> nativeint) ->
        unit

The difficult part in implementing this is splitting the callback function (of type (Struct_callback_simple.t -> nativeint)) into two parts, a C function pointer and a pointer to an environment. For simplicity, we'll take a direct approach, where we'll store the actual callback in a table of functions and pass an integer index as the data value to g_signal_connect_data.

The first step is to define a callback handler in C to handle all callbacks. We do this by adding the following definitions to gtk_types.h, and adding callback_simple_fun to values.export.

__dll_callback int callback_simple(GtkWidget *widget, gpointer data);

static GCallback callback_simple_fun = (GCallback) callback_simple;

The __dll_callback declaration constructs a new callback function in the binding, which we then export.

Next, we need to add some glue code to gtk_post.ml to manage the callbacks.

First, we need to define a table of functions.

open Lm_int_set

let simple_callback_table = ref IntTable.empty

let register_simple_callback f =
   let table = !simple_callback_table in
   let i = IntTable.cardinal table in
      simple_callback_table := IntTable.add table i f;
      i

We use the IntTable as a map from integers to functions. The function register_simple_callback stores a function in the table, and returns an integer index.

Next, to register the callback with GTK, we perform all of the steps, coercing the window to a void *, coercing the integer index to a void *, and then calling the function f_g_signal_connect_data.

external void_p : 'a dll_pointer -> t_void dll_pointer = "%identity"

let g_signal_connect (window : struct__GtkWidget dll_pointer) name callback_fun id =
   let window = void_p window in
   let id : t_void dll_pointer = Obj.magic (Dll.pointer_of_int id) in
      f_g_signal_connect_data window name callback_fun id Dll.p_NULL 0n

let g_signal_connect_simple window name (callback : Struct_callback_simple.t -> nativeint) =
   let id = register_simple_callback callback in
      g_signal_connect window name callback_simple_fun id

For the final step, we need to define a OCaml version of the actual callback function. This function uses the integer index to find the actual callback function, then calls it with the callback argument.

let callback_simple arg =
   let i = Dll.int_of_pointer arg.Struct_callback_simple.l_id in
      try
         let f = IntTable.find !simple_callback_table i in
            f arg
      with
         Not_found ->
            eprintf "callback_simple: callback %d is not defined@." i;
            0n

The callbacks must be registered, as follows. This registration need only be performed once, at program startup.

let callbacks =
   { l_callback_simple = callback_simple;
     ...
   }

let () = dll_set_callback_handlers callbacks

18.3.2  OMake callbacks

The OMake callbacks are similar to the OCaml callbacks. Again, we register functions as integers, using the builtin function dll-register-value and dll-get-registered-value.

For the first step, we wrap the g_signal_connect_data function.

protected.g_signal_connect(window, name, callback-fun, callback) =
    window = $(dll-cast void *, $(window))
    id = $(dll-register-value $(callback))
    g_signal_connect_data($(window), $(name), $(callback-fun), $(id), $(NULL), 0)

public.const.g_signal_connect_simple(callback, window, name) =
    g_signal_connect($(window), $(name), $(callback_simple_fun), $(callback))

Next, we implement the function that handles the callbacks.

dll-register-callback(x => ..., $(callback_simple))
    private.f = $(dll-get-registered-value $(x.id))
    f($x)

That's it!

18.4  A object-oriented GTK API

GTK+ 2 is implemented with an object-oriented style but, since it is written in C, it doesn't use objects directly. Both OCaml and OMake have an object system, why not use objects directly? Actually, we haven't implemented an object-oriented GTK API for OCaml, but we have for OMake.

The translation is simple. Each GTK function with a name of the form gtk_xyz_a_b_c(widget, args) is implemented as a method GtkXyz::a-b-c(args). For example, the following function

void gtk_widget_set_parent(GtkWidget *widget, GtkWidget *parent)

is implemented as the following method, where raw-widget is the widget itself.

GtkWidget. =
    declare raw-widget
    ...
    public.set-parent(parent) =
        # parent : GtkWidget *
        # result : void
        private.result = $(gtk_widget_set_parent $(raw-widget), $(parent.raw-widget))
        value $(result)

Constructors are similar, but they return a new object.

GtkWindow. =
    ...
    public.new(type) =
        raw-window = $(gtk_window_new $(type))
        value $(this)

To illustrate, let's take a look at the Browser.om file in test/dll/gtk/examples/TargetBrowser. This is a simple application to display the OMake dependency graph.

We define a class called Browser that displays the dependency graph in a window. The main function uses the usual GTK style, but in object-oriented form. The first part sets up the main application window, calling the method $(GtkWindow.new ...) to create a new window. When a delete_event occurs, the gtk_main_quit() function is called to terminate the application.

open gtk/GtkConst
open gtk/Gtk

public.Browser. =
    class _Browser
    ...
    public.make() =
        # Create a new window
        window = $(GtkWindow.new $(GTK_WINDOW_TOPLEVEL))
        window.set-default-size(450, 450)

        # Quit when a delete event happens
        window.signal-connect-event(event => ..., delete_event)
            gtk_main_quit()
            return 0

        window.set-title(OMake Target Navigator)
        window.set-border-width(4)

The rest of the function is fairly standard boilerplate: create widgets and add them to the window hierarchy. Here is the rest of the function.

    public.make() =
        ...

        # Create a text area for the link
        label = $(GtkButton.new-with-label Back)
        status = $(GtkEntry.new)
        label.signal-connect-simple(event => released)
            back()
        status.signal-connect-simple(event => activate)
            text = $(status.get-text)
            follow-link($(text))
        hbox = $(GtkHBox.new false, 0)
        hbox.pack-start($(label), false, false, 0)
        hbox.pack-start($(status), true, true, 0)

        # Create a text area for the actual contents
        text-view = $(GtkTextView.new)
        text-view.set-wrap-mode($(GTK_WRAP_WORD))
        text-buffer = $(text-view.get-buffer)

        ################################################
        # Handle a click on the text window
        #
        text-view.signal-connect-event(info => event-after)
           private.event = $(info.event)
           if $(equal $(event.type), $(GDK_BUTTON_RELEASE))
               if $(equal $(event.button), 1)
                  p = $(text-view.window-to-buffer-coords $(GTK_TEXT_WINDOW_WIDGET), $(event.x), $(event.y))
                  iter = $(GtkTextIter.new)
                  text-view.get-iter-at-location($(iter), $(p.x), $(p.y))
                  follow-if-link($(iter))

        # Put the window in a scrollbuffer
        sw = $(GtkScrolledWindow.new $(NULL), $(NULL))
        sw.set-policy($(GTK_POLICY_AUTOMATIC), $(GTK_POLICY_AUTOMATIC))
        sw.add($(text-view))

        # Add the text area to the window
        vbox = $(GtkVBox.new false, 0)
        vbox.pack-start($(hbox), false, true, 0)
        vbox.pack-start($(sw), true, true, 0)
        window.add($(vbox))

        # Show everything
        window.show-all()
        return $(this)

Finally, to finish the application, the main loop must be called.

public.Browser. =
    class _Browser
    ...
    public.main() =
        dll-entering-main-loop()
        gtk-main()

18.5  Binding internals

Internally, the main tasks of the binding generator are the following.

  1. Wrap the C functions so that they can be called from OCaml.
  2. Generate structure “marshaling” code to convert betweem OCaml and C representations.

18.5.1  Simple function bindings

Consider a function like the following, where GtkWindowType is an enumeration describing what kind of window to create.

GtkWindow *gtk_window_new(GtkWindowType type);

The binder generates the following code. The word “marshal” is used for converting from OCaml values to C values, and the word “unmarshal” is used for the opposite direction.

value dll_gtk_window_new(value v_argv)
{ 
    GtkWindowType arg0; 
    GtkWidget *result; 
    value v_tmp; 

    /* Marshal the arguments */ 
    /* enum_enum163 */ 
    arg0 = (GtkWindowType) Nativeint_val(Field(v_argv, 0)); 

    /* Call the function */ 
    result = (GtkWidget *) gtk_window_new((GtkWindowType) arg0); 

    /* Assign referenced values (if any) */ 

    /* Return */ 
    v_tmp = dll_unmarshal_pointer((void *) (GtkWidget *) (result)); 
    return(v_tmp); 
}

The binding function perform the following steps.

  1. The arguments are “marshaled” to convert the OCaml valus to their C representations. In this case, there is one argument; it is an OCaml value of type nativeint.
  2. The function is called.
  3. The value is “unmarshaled” and returned. In this case, GtkWindow * has opaque type, so a pointer value is allocated and returned.

18.5.2  Function bindings with reference cells

Consider the function where the function arguments are references.

void gtk_init(int DLL_REF(argc), char** DLL_REF(argv));

Here is the code generated for the binding.

value dll_gtk_init(value v_argv)
{ 
    CAMLparam1(v_argv); 
    int arg0; 
    char **arg1; 
    value v_tmp; 

    /* Marshal the arguments */ 
    /* (t_int) ref */ 
    arg0 = (int) Nativeint_val(Field(Field(v_argv, 0), 0)); 
    /* (((t_char) dll_pointer) dll_pointer) ref */ 
    arg1 = (char **) dll_marshal_pointer(Field(Field(v_argv, 1), 0)); 

    /* Call the function */ 
    gtk_init((int *) &arg0, (char ***) &arg1); 

    /* Assign referenced values (if any) */ 
    v_tmp = copy_nativeint((int) arg0); 
    Store_field(Field(v_argv, 0), 0, v_tmp); 
    v_tmp = dll_unmarshal_pointer((void *) (char **) (arg1)); 
    Store_field(Field(v_argv, 1), 0, v_tmp); 

    /* Return */ 
    CAMLreturn(Val_unit); 
}

The main difference between this function and the simple function get_window_new is the code section with immediately after the comment /* Assign referenced values (if any) */. In this case, the values are “unmarshaled” and assigned to the original reference cells.

18.5.3  Structure marshaling

Structure marshaling is similar. Consider the struct GdkEventExport, which has the following C definition.

struct _GdkEventExpose
{
  GdkEventType type;
  GdkWindow *window;
  gint8 send_event;
  GdkRectangle area;
  GdkRegion *region;
  gint count;
};

Values of type GdkRectangle * are also auto-marshaled. Here is the code for the unmarshaler; the marshaler is similar.

static value dll_unmarshal__GdkEventExpose(GdkEventExpose *p)
{
    CAMLparam0();
    CAMLlocal1(v_object);

    v_object = 0;
    if(p) { 
        v_object = alloc_tuple(7);
        Field(v_object, 0) = Val_int(Dll_object_3__GdkEventExpose); 
        Store_field(v_object, 1, copy_nativeint((GdkEventType) p->type)); 
        Store_field(v_object, 2, dll_unmarshal_pointer((void *) (GdkDrawable *) (p->window))); 
        Store_field(v_object, 3, Val_int((char) p->send_event)); 
        Store_field(v_object, 4, dll_unmarshal__GdkRectangle((GdkRectangle *) &p->area)); 
        Store_field(v_object, 5, dll_unmarshal_pointer((void *) (GdkRegion *) (p->region))); 
        Store_field(v_object, 6, copy_nativeint((int) p->count)); 
    }
    CAMLreturn(v_object);
}

18.5.4  Callbacks

Finally, let's take a look at callbacks. The callback callback_simple was declared as follows.

__dll_callback int callback_simple(GtkWidget *widget, gpointer data);

The generated code is as follows. In most ways, a callback function is just the inverse of a normal function: first the values are unmarshaled (from C to OCaml representation), the callback is called, and then the result is marshaled (back to the C representation).

static int callback_simple(GtkWidget *widget, gpointer id)
{
    CAMLparam0();
    CAMLlocal2(v_object, v_result); 
    int result; 
        v_object = alloc_tuple(3);
        Field(v_object, 0) = Val_int(Dll_object_27_callback_simple); 
        Store_field(v_object, 1, dll_unmarshal_pointer((void *) (GtkWidget *) (widget))); 
        Store_field(v_object, 2, dll_unmarshal_pointer((void *) (void *) (id))); 
    v_result = callback(Field(dll_callback_handlers, Callback_callback_simple), v_object); 
    result = (int) Nativeint_val(v_result);
    CAMLreturnT(int, result); 
} 
Jump to:  OMake Home • Guide Home • Guide (single-page) • Contents (short) • Contents (long)
Index:  All • Variables • Functions • Objects • Targets • Options
This web site is published by Informatikbüro Gerd Stolpmann
Powered by Caml