lazy-ffi

  1. lazy-ffi
    1. Description
    2. Author
    3. Requirements
    4. Download
    5. Documentation
      1. Read syntax
    6. Examples
    7. Changelog
    8. License

Description

Another foreign function interface, based on libffi.

Author

felix winkelmann

Requirements

Download

lazy-ffi.egg

Documentation

To use this extension in compiled code, invoke the compiler with -extension lazy-ffi (in addition to do the require-extension). This is necessary because the extension defines a special read-syntax which has to be registered before the source code is read in. This is not needed in interpreted code.

A very easy to use foreign function interface, which provides a special read-syntax to call arbitrary functions in shared libraries. This facility uses libffi and dynamic loading (via dlopen(3)) to load shared libraries and construct foreign calls at runtime and in interpreted code.

Note: the libffi package is actively under development, but the website doesn't reflect the current work in progress. A snapshot of the current development version is available at http://www.call-with-current-continuation.org/libffi-2.tgz. The latter is more portable and robust than the version currently advertised at the libffi website.

Read syntax

The following read syntax is provided:

[read] #~STRING

Registers the shared library named STRING. Subsequent access to foreign symbols will try to find the required symbol in all libraries registered so far. The library name may also be #f, which allows looking up symbols in the current executable (this may require special linker options, depending on platform).

[read] #~SYMBOL

Identifies a foreign symbol, which will be looked up in the currently registered shared libraries. Returns a procedure that can be called like a normal Scheme procedure.

A special case is the syntax #~~. When used, an expression is expected preceding any further arguments that should evaluate to a foreign pointer object identifying the address of a C function.

[read] #~(ITEM ...)

Equivalent to (list #~ITEM ...). This can be used to register several shared libraries at once or pre-resolve foreign symbols.

A foreign procedure is called like a normal procedure, with argument values automatically converted to the appropriate foreign representation, using the following mapping of Scheme types to C types:

Scheme type C type
boolean int (1 or 0)
exact int
inexact double
char char
pointer or locative void *
string char *
symbol char *

Arguments of any other type will signal an error (see below for specifying specific argument conversions).

Additionally, the procedure can be called with a number of special keyword arguments:

return
TYPE

Specifies the result type. If not given, the result will be ignored. TYPE should be one of the following:

int:
char:
float:
double:
pointer:
string:
symbol:
bool:
void:
scheme-object:
safe
BOOLEAN

If given, then the call may call back into Scheme (for example by passing a pointer to a callback function). If not given, then call may not invoke any Scheme callbacks, or bad things will happen.

TYPE VALUE

To force a specific argument type conversion (and to allow slightly better argument type checking), a type specifier may also be provided as a keyword, followed by the actual argument. Valid type specifiers are:

int: exact number
float: double: inexact number
pointer: pointer object
bool: boolean (actually any Scheme object), will be passed as 1 or 0
char: char
string: string
symbol: string (the name of the symbol)
scheme-object: any Scheme value
scheme-pointer: any non-immediate Scheme value (a pointer to the data-section will be passed)

(The type specifiers scheme-pointer: and scheme-object: are mainly intended for advanced uses of this extension)

Examples

#~"libc.so.6" 
(#~printf "%d -> %g, ok: %s\n" 123 45.67 "hello")
(#~sleep 1)

#~"libm.so.6"

(#~sin 33.4 return: double:)    ==> 0.915809602890819
(#~tolower #\A return: char:)   ==> #\a

(let* ([box (f64vector 0)]
       [r (#~modf 123.456 box return: double:)] )
  (list r box) )   ==> (0.456 #f64(123.0))

Here the "Hello, world" example from the GTK 2.0 tutorial:

;; Compile like this:
;
; $ csc -R lazy-ffi.scm gtkhello.scm

(use lazy-ffi)

#~"libgtk-x11-2.0.so"
#~"libglib-2.0.so"
#~"libgobject-2.0.so"

(define GTK_WINDOW_TOPLEVEL 0)

(define-external (hello (c-pointer widget) (c-pointer data)) void
  (print "Hello, world") )

(define-external (delete_event (c-pointer widget)
			       (c-pointer event)
			       (c-pointer data) )
  bool
  (print "delete event occurred")
  #t)

(define-external (destroy (c-pointer widget) (c-pointer data)) void
  (#~gtk_main_quit) )

(define (g_signal_connect a b c d)
  (#~g_signal_connect_data a b c pointer: d pointer: #f 0) )

(#~gtk_init (foreign-value "&C_main_argc" c-pointer) (foreign-value "&C_main_argv" c-pointer))

(define window (#~gtk_window_new GTK_WINDOW_TOPLEVEL return: pointer:))
    
(g_signal_connect window "delete_event" #$delete_event #f)  
(g_signal_connect window "destroy" #$destroy #f)

(#~gtk_container_set_border_width window 10)
    
(define button (#~gtk_button_new_with_label "Hello World" return: pointer:))
    
(g_signal_connect button "clicked" #$hello #f)

(define-external (close_all (c-pointer widget)) void
  (#~gtk_widget_destroy window) )

(g_signal_connect button "clicked" #$close_all #f)
(#~gtk_container_add window button)
(#~gtk_widget_show button)  
(#~gtk_widget_show window)
(#~gtk_main safe: #t)

Calling a pointer directly:

#~"libdl.so.2"

(define atof (#~dlsym pointer: #f "atof" return: pointer:)) ; lookup in current module

(print atof)    ; ==> "#<pointer 1077736272.0>"

(#~~ atof "99" return: double:)    ; ==> 99.0

; Taking it to the extreme...

(use lolevel)

(#~~ (address->pointer 1077736272) "42.1" return: double:)    ; ==> 42.1

Changelog

License

 Copyright (c) 2005, Felix L. Winkelmann
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following
 conditions are met:
 
   Redistributions of source code must retain the above copyright notice, this list of conditions and the following
     disclaimer. 
   Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
     disclaimer in the documentation and/or other materials provided with the distribution. 
   Neither the name of the author nor the names of its contributors may be used to endorse or promote
     products derived from this software without specific prior written permission. 
 
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 POSSIBILITY OF SUCH DAMAGE.