Jump to: | OMake Home • Guide Home • Guide (single-page) • Contents (short) • Contents (long) | |
Index: | All • Variables • Functions • Objects • Targets • Options |
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 #include
s 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).
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.
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).
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;
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.ml
and gtk.om
),
gtk_types.h
file,
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.
The file gtk_types.h
specifies the header files the contain the declarations for the binding.
Normally, this file can be short, it simply #include
s the files in the normal way. For
GTK, the first part is normal enough.
#include <gtk/gtk.h> #include <glib.h>
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.
Sometimes it is desireable to export #define
d constants as part of the binding. Since the
binding is created after runnning the C preprocessor, the binding generator has no knowledge
of #define
d 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.
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__ */
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
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);
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
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!
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()
Internally, the main tasks of the binding generator are the following.
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.
nativeint
.
GtkWindow *
has opaque type,
so a pointer value is allocated and returned.
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.
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); }
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 |