Another foreign function interface, based on libffi.
This extension defines the module lazy-ffi.
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 -extension 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.
The following read syntax is provided:
- #~STRING read
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).
- #~SYMBOL read
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.
- #~(ITEM ...) read
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:
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:
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.
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)
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.
(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 (use lazy-ffi) (import 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)
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
- 1.8.5 MacOS X support (ffi/ffi.h) kon lovett
- 1.8.4 added missing requirement for srfi-69 (reported by Mehmet Kose)
- 1.8.3 ported to windows (mingw32)
- 1.8.2 Ported to chicken4
- 1.8 Removed use of ___callback
- 1.7 Uses externalized easyffi extension, uses more modern CHICKEN features
- 1.6 Fixed silly bug in f32vector handling
- 1.5 Added basic argument and result handling for unsigned ints; allows #f as module name
- 1.4 Changed read-syntax to #~ and removed SRFI-4 type-specifiers (transformed automatically)
- 1.3 Fixed bug (hash-table related, of course)
- 1.2 Adapted to SRFI-69-compatible hash-tables
- 1.1 Added ~~
- 1.0 Initial release
Copyright (c) 2005-2011, 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.