lazy-ffi
Description
Another foreign function interface, based on libffi.
Author
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] #~STRINGRegisters 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] #~SYMBOLIdentifies 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
- 1.8.6 Initial port to CHICKEN 5
- 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
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.