lazy-ffi

  1. lazy-ffi
    1. Description
    2. Author
    3. Repository
    4. Requirements
    5. Usage
    6. Documentation
      1. Read syntax
      2. Use from Emacs and Quack
    7. Examples
    8. A final note
    9. Changelog
    10. License

Description

Another foreign function interface, based on libffi.

Author

felix winkelmann

Repository

This egg is hosted on the CHICKEN Subversion repository:

https://anonymous@code.call-cc.org/svn/chicken-eggs/release/5/lazy-ffi

If you want to check out the source code repository of this egg and you are not familiar with Subversion, see this page.

Requirements

Usage

(import lazy-ffi)

This extension defines the module lazy-ffi.

Documentation

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.

To use this extension in compiled code, invoke the compiler with -X lazy-ffi. 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 required for interpreted code.

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 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)

Use from Emacs and Quack

Programmers used to send code from an Emacs Window to the REPL using C-x C-e will notice that it will not work for the #~ syntax, since the whole line #~"..." is neither an S-expression nor a Scheme atom, and Emacs will just send the string "..." alone to the REPL. It may be useful to wrap the library loading with a begin:

 (begin
   #~"libc.so.6"
   #~"libm.so.6")

Then sending the S-expression to the REPL should work.

Examples

(cond-expand
  (mingw32 #~"msvcrt")
  (else  #~"libc.so.6"))

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

(cond-expand
  ((not mingw32) #~"libm.so.6")
  (else))

(#~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 -X lazy-ffi gtkhello.scm

(import lazy-ffi)
(import (chicken foreign))

#~"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)

A final note

This extension is, without doubt, a rather adventurous hack. Nevertheless it is interesting and may be useful in certain situations where a more robust interface to native code is neither needed nor worth the effort.

Changelog

License

 Copyright (c) 2005-2021, 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.