As a simple example, if you wanted to wrap a C API that contained only one function with a prototype like this:
int frob(int x, double y);
a complete G-Wrap specification would look like this:
(define-module (my-wrapset) #:use-module (oop goops) #:use-module (g-wrap) ;; the core of G-Wrap #:use-module (g-wrap guile) ;; defines `<gw-guile-wrapset>' #:use-module (g-wrap guile ws standard) ;; the `standard' wrapset #:export (<my-wrapset>)) ;; Define a wrapset for Guile, and specify the wrapsets it depends on. (define-class <my-wrapset> (<gw-guile-wrapset>) #:id 'my-wrapset #:dependencies '(standard)) (define-method (initialize (ws <my-wrapset>) initargs) (next-method) ;; Populate the wrapset. (wrap-function! ws #:name 'frob #:returns 'int #:arguments '((int x) (double y)) #:c-name "frob" #:description "Return the result of frobbing x and y."))
You can see that the specification is encapsulated in a Guile module
that exports a class -
<my-wrapset> derived from
<gw-wrapset>. A wrapset is the G-Wrap counterpart to some API;
it describes how the API looks in C and provides additional
information for the target language. The wrapset above doesn’t yet
provide any such information; it is abstract and can be refined
for every target language.
define-class statement, the wrapset defines its ID, a
globally unique identifier, and declares a dependency on the
standard wrapset, which provides the basic C types. In the
initialize method, the
frob function is wrapped,
providing all relevant data such as return and argument types.
To refine the above wrapset for Guile, you derive from it and add the necessary information:
(define-module (my-guile-wrapset) #:use-module (oop goops) #:use-module (g-wrap) #:use-module (g-wrap guile) #:use-module (g-wrap guile ws standard) #:export (<my-guile-wrapset>)) (define-class <my-guile-wrapset> (<my-wrapset> <gw-guile-wrapset>)) (define-method (initialize (ws <my-guile-wrapset>) initargs) ;; Tell parent methods the name the generated Guile module ;; should have. (next-method ws (append '(#:module (my-module)) initargs)))
We derive our Guile-specific wrapset from the generic wrapset
<my-wrapset> and the base class for all Guile wrapsets
initialize, we tell G-Wrap that
the wrapset should eventually reside in the Guile module
Once G-Wrap has seen this specification, the code for the wrappers can be generated with this code:
(use-modules (g-wrap) (g-wrap guile) (my-guile-wrapset)) ;; Generate wrapper code. (generate-wrapset 'guile 'my-wrapset "my-wrapset")
This will produce the C file my-wrapset.c, that can be compiled
into a shared library which will, when loaded by Guile, define a
Scheme function named
frob which you can call as expected:
(frob 4 2.3)
When it comes to defining how C types should be handled, G-Wrap is very flexible. G-Wrap provides a fairly generic underlying infrastructure which can then be customized for particular purposes by teaching it how to generate code for a given type or wrapset. You can take explicit control over what code is generated in the wrapper module to handle arguments and return values of a given type (both for their initialization and cleanup), what code is generated to handle the wrapper module’s global initialization, and what code is generated to provide global declarations.
At the lowest level, there is a "wrapping protocol" for types, which you can plug into to describe how a type is converted from and to the target language as well as other aspects of the type. G-Wrap comes with a few of these more types pre-defined. This set should cover most of the common cases, but you can extend it if needed.
The wrapper types currently available by default include:
wrapper types to handle C types which can be represented most
appropriately on the Scheme side by a conversion directly to a native
Scheme type, e.g.
a wrapper type to handle "non-native" objects, that is, C types which can not, or should not be represented on the Scheme side by a conversion to a native Scheme representation, or types for which preserving the C-side pointer equivalency is important. Instances of this wrapper type are represented at runtime by a Guile SMOB containing the actual C pointer.
a wrapper type to handle C enumerations which automatically grabs the right C-side values at runtime.
Furthermore, G-Wrap allows you to define types in one wrapper module
that can then be used by other wrapper modules. So as an example, you
should be able to define a GLib wrapper module that provides wrapper
GList*’s that other wrapper modules can then
import and use in their own wrapper function prototypes for argument
and result types. The goal is for this to allow different wrapper
modules to be able to safely exchange data among their wrapped
functions when they share common wrapped types.
As mentioned, G-Wrap itself is implemented as purely Scheme-code Guile modules. This means that you can wrap functions for multiple modules on the fly from any invocation of Guile.