sbank tutorial
This tutorial assumes you have already installed sbank for your preferred Scheme implementation, and you've been able to run the test suite and some example, making sure everything is working. Please see the installation instructions for details.
GObject-Introspection
sbank
is a Scheme binding for
GObject Introspection,
which provides API metadata for GObject-based C libraries. This
metadata contains information about available functions, classes,
their methods, the argument and return types of the functions and
methods. It is stored in binary form in so-called .typelib
files. Using this metadata, sbank
provides you with instant Scheme
bindings -- you can access the C library (e.g. GTK+) without any
library-specific code, neither in your code, nor in sbank
.
To stress that: if a C library provides gobject-introspection
metadata, you should be able to use it via sbank
without further
ado.
However, while the metadata provided in the .typelib
file is
complete enough to yield good and "schemely" bindings, there might
still be some corners in the API that profit from some glossing over,
and sbank provides such glossing where and when such cases are
identified.
GObject
In this section, I'll try to give a very rough overview of the GObject
type system, as far as relevant in the context of sbank
. If you
already have a basic understanding of GObject, this will be quite
familiar to you, but read this section nevertheless, as it's written
from the perspective of an sbank user.
GObject is a C library that implements an object system for C, including support for (single) inheritance, interfaces and signals.
All classes in GObject are organized in a tree, rooted in the GObject class. The functionality provided by this root of the inheritance hierarchy is hence available with all GObject-based classes, and for the most part consists of:
Lifecycle managment
: Using sbank
, you don't have to care about that, as all objects you
hold on to from Scheme are registered with your Scheme
implementation's garbage collector. This results in their underlying
C counterparts dying when you don't reference them from Scheme
anymore and the GC kicks in.
Signals
: Signals are a way for objects to broadcast information (for example state changes) to an unknown and possibly empty group of receivers. If you are interested in some signal emitted by an object, you register a handler for that specific object and signal. This handler (in sbank) is just a procedure that gets called with arguments specified by the emitter. Each signal has a particuliar argument list, with fixed length, argument types and semantics.
Properties
: Each object may have several properties, which are similiar to the fields in a Scheme record, but come with additional functionality: when a property of an object is changed, a special signal (called "notify") is emitted for that object, allowing interested parties to react.
Each class has a number of "methods" that may be called to interact
with the object. In C, these are exposed as plain functions. Take for
example, like gtk_widget_show
. This name can be broken down into
three components:
gtk
is the namespace of the library that the method resides in;widget
indicates the class the method belongs to,- and
show
is the actual name of the method.
This means you can call show
on any object which is derived from the
GtkWidget
class (i.e. most objects in GTK+). In sbank
, rather than
speaking of "invoking a method on an object", we talk about "sending a
message to an object", meaning the same thing.
Equipped with this background knowledge about GObject, let's turn to a first example.
An example using GTK+
The obligatory example in this case is of course a "Hello World!"-style program. The program presented in the following is semantically equivalent to the one in C from the GTK+ tutorial.
#!r6rs
(import (rnrs)
(sbank gtk))
;; For convinience
(define (println . args)
(for-each display args)
(newline))
;; Initialize GTK+ with the command-line arguments
(gtk-init (command-line))
(let ((window (send <gtk-window> (new 'toplevel)))
(button (send <gtk-button>
(new-with-label "Hello World!"))))
;; Send several messages to the `button' object
;; created above
(send button
(connect 'clicked (lambda (widget)
(println "Hello World!")))
(connect 'clicked (lambda (widget)
(send window (destroy))))
(show))
;; Ditto for `window'
(send window
(connect 'delete-event
(lambda (widget event)
(println "delete-event: " widget " " event)
;; returning #t to prevent further
;; propogation of this signal...
#t))
(connect 'destroy (lambda (widget) (gtk-main-quit)))
(set-border-width 10)
(add button)
(show)))
;; Launch the main loop
(gtk-main)
Messages
As you probably have noticed, send
plays an important role in sbank:
it is a macro that allows access to sbank
's object system, which is
a thin layer above GObject. The syntax of the macro is:
(send <object> (<message> <argument> ...) ...)
This will, for each (<message> ...)
clause, invoke the method of
<object>
identified by <message>
, passing it <argument> ...
. It
returns the value(s) returned by the last invocation.
The first two usages of send
in the above example send the messages
to class "objects", <gtk-window>
and <gtk-button>
. These respond
to the all of underlying GObject class's constructors and static
methods; in our case the constructors
gtk_window_new
and
gtk_button_new_with_label
are called. As you can see, sbank
takes care of all the binding
grunt work, like converting the Scheme symbol toplevel
to the right
C enumeration value
(GTK_WINDOW_TOPLEVEL
), in this case.
The two forms inside the let
again use send
, the first one issuing
a bunch of messages to button
, the second several to
window
.
Signals
Another thing to note is the use of the connect
message, which is
provided by <g-object>
, the root of GObject's inheritance
hierarchy. It takes a symbol identifying the signal name to connect
to, and a handler, which is a procedure that is invoked when the
corresponding signal is emitted.
As can be seen in the (send button ...)
expression, it is perfectly
legitimate to connect several handlers to the same signal (on the very
same object). All those handlers will be invoked when the signal is
emitted. As you can probably guess, these two handlers will be invoked
when the button is clicked. Order is significant, so first "Hello
World" will be printed on the terminal, then the destroy
message is
sent to the window.
In the (send window )
expression, we again connect two handlers,
this time for different signals -- delete-event
will be emitted when
the user requests the window to be closed; destroy
before it is
destroyed.
Summing up
There is little more about this small example:
-
Both the button and the window are sent the
show
message when we are otherwise finished dealing with them. -
The window gets a ten-pixel border and the is button added inside it.
-
In the last line, the GTK+ main loop is started. This will cause GTK+ to start handling mouse and keyboard events, allowing the user to interact with the application.
-
The main loop will run until it is terminated by
gtk-main-loop-quit
. This will happen in one of two ways: either the user clicks the button, which causes theclicked
signal to be emitted, which will in turn cause our second handler to send thedestroy
message towindow
. The other way is GTK+ itself destroying the top-level window upon request by the user (and ourdelete-event
handler will indicate permission to do so by returning#t
).
Name and value conversions
As there's not yet a reference manual for sbank, you have to refer to the original manuals written for C. To make sense of them, you have to know how sbank maps the C names and values to Scheme.
Names
The name conversions follows the following rules:
- Method names are split from their namespace and class prefixes, so
gtk_container_set_border_width
for example becomesset_border_width
, then underscores are replaced by hyphens, resulting inset-border-width
. - Field names are likewise hyphen-ized.
- Class names are converted from CamelCase to lower case by
inserting a hyphen at every in-word case boundary, and then wrapped
in angle brackets:
GtkToggleButton
becomes<gtk-toggle-button>
. - Constants like
GTK_PAPER_NAME_A4
are hyphenized, lowercased and wrapped in asterisks, leading to*gtk-paper-name-a4*
. Note that this does not apply to C enumeration values; for those see below.
Values
The values conversion rules are:
- Scheme strings are converted to UTF-8 encoded, null terminated C strings, and vice versa.
- Scheme numbers (integer, real) are converted to their C counterparts.
- Where the C API expects an array, you can pass a Scheme vector or
list with elements of the approriate type. For vectors of type
int8
anduint8
, bytevectors and strings are also accepted, strings are converted to UTF-8 before being passed to C. In the C->Scheme direction, arrays are always converted to vectors1. GList
andGSList
are mapped to Scheme lists.GHashTable
values are passed around as opaque objects.- C enumeration values are mapped to Scheme symbols; the symbols
correspond to the discriminative suffix of the C identifiers. For
example,
GTK_ANCHOR_CENTER
,GTK_ANCHOR_NORTH
,GTK_ANCHOR_NORTH_WEST
, ... map to the symbolscenter
,north
,north-west
, ....
Output arguments
As a C function can have only single return value (or none), functions returning multiple values are implemented using "output arguments". Those are mapped to multiple return values in Scheme: the first value returned is the return value of the C function (if present), followed by the output argument values, in the order they appear in the C function prototype.
Probably arrays of type uint8
and int8
should be
mapped to bytevectors.