Smiling computer with pliers

Using fflib.fs with gforth

By Cedric Shock

Introduction

fflib.fs allows you to use C routines in your gforth code.

Getting, Compiling

Get and compile ffcall before getting and compiling gforth.

Using

First you need to load fflib into your interpreter:
require fflib.fs

To use a C library, use the following word:
library name objectfile

fflib.fs uses compiled so objects. To use the object it must be in your library path.

For example, to use the standard C library this would be:
library libc libc.so.6

This makes a new word, libc, that we will use to define procedures that we can call in libc. They will look like this:
libc forth-name argument-types (return-type) c-name

Here are the currently supported argument types:
TypeDescription
intinteger
sfsingle precission floating point
dfdouble precission floating point
llonglong long
ptrpointer

Here are the currently supported return types:
TypeDescription
voidvoid
intinteger
sfsingle precission floating point
fpdouble precission floating point
llonglong long
ptrpointer

For example, the libc command sleep is defined in c with the signature:

unsigned sleep(unsigned seconds);

We could import it into forth with this line of code:
libc sleep int (int) sleep
This will define a new word in forth called sleep. It will consume one integer from the stack, sleep for that many seconds, then put a zero on the stack.

Using with Strings

Here's another example that uses strings. This is a little more difficult because c and forth use different representations for strings. C uses an address to an array of characters terminated with a null character. Forth uses an address to an array of characters, and a length of the list. These two routines convert between c and forth representations of strings. Note that going from a forth string to a c string requires a copy to be made to add the extra null byte.

 
 : ctof ( addr -- addr u )
	0 ( addr 0 )
	begin
		over over ( addr n addr n )
		chars + ( addr n addr+n*chars )
		c@ ( addr n char ) 0> ( addr n truth ) while ( addr n )
			1 + ( addr n+1 )
	repeat ;
	
: ftoc ( c-addr u -- 'c-addr ) here dup >r over allot swap cmove 0 c, align r> ;

These are the definitions of some routines for working with times in libc:

void gettime(struct time *);

time_t time(time_t *t);

struct tm *localtime(const time_t *tod);

size_t strftime(char *buf, size_t n, const char *format,
                const struct tm *time_info);

We can import them into forth with the following commands. I chose to import time with the name libctime.

libc libctime ptr (int) time
libc localtime ptr (ptr) localtime
libc strftime ptr int ptr ptr (int) strftime

Here is an example of using these commands. First we create one cell in memory for the time_t structure, then a cell that holds 12 characters as a buffer.

create timet 1 cells allot
create buf 12 chars allot
buf 12 ( address of buf, 12 )
s" %A" ( add string address and length )
ftoc ( make a c style string from them )
timet libctime drop ( get the time into memory, drop the result )
timet localtime ( make the time information, returns a pointer )
strftime ( format the time into the buffer buf )
buf ctof ( convert c string to a forth string, adds character count )
type ( show the string )

Callbacks

Callbacks are defined in two parts. First we must describe the type of the callback. This is done via:
callback callback-type-name (return-type) argument-types callback;
This will produce a new word, callback-type-name which will take an address of a word on the stack, and the name for the callback and will make a callback with that name:
address-of-word callback-type-name callback-name

For example, we could define a callback that takes three numbers and returns one. We'll call it 3to1 callback 3to1 (int) int int int callback;
3to1 is now a word that takes an address to a word, and a name, and makes a callback with that name. First we'll need a word that matches this description of a callback. Here is a word that does that: : timesplus * + ;
Then we hook our function into the callback like this:
' timesplus 3to1 *+

To demonstrate callbacks we'll need to use a library that utilizes them.

require fflib.fs

\ We need to make some C strings, so we include the forth to c routine from above:
: ftoc ( c-addr u -- 'c-addr ) here dup >r over allot swap cmove 0 c, align r> ;

library libgtk libgtk-x11-2.0.so.0

libgtk gtk_init ptr ptr (void) gtk_init
libgtk gtk_button_new_with_label ptr (ptr) gtk_button_new_with_label
libgtk gtk_container_add ptr ptr (void) gtk_container_add
libgtk gtk_main (void) gtk_main
libgtk gtk_main_quit (void) gtk_main_quit
libgtk gtk_widget_show ptr (void) gtk_widget_show
libgtk gtk_window_new int (ptr) gtk_window_new

libgtk g_print ptr (void) g_print

libgtk g_signal_connect_data ptr ptr ptr ptr ptr int (int) g_signal_connect_data
\ Define a few other things to match the way gtk has them defined
: g_signal_connect 0 0 g_signal_connect_data ;
: g_signal_connect_after 0 1 g_signal_connect_data ;
: g_signal_connect_swapped 0 2 g_signal_connect_data ;

\ gtk callback
callback libgtkcallback (void) ptr ptr callback;
\ gtk signal handler callback
callback libgtksignalhandler (int) ptr ptr ptr callback;

\Example callback made from nameless word
:noname drop drop s" Hello World " ftoc g_print ; libgtkcallback hello

\ Example making a callback with a name
\ We return 0 so that it closes
\ If we returned 1, it wouldn't
: delete_event drop drop drop s" delete event occured " ftoc g_print 0 ;
' delete_event libgtksignalhandler de

\ Another example callback
:noname drop drop gtk_main_quit ; libgtkcallback destroy

\ Make variables for a button and a window
variable button
variable window

\ Initialize gtk
0 0 gtk_init

\ Create a window
0 gtk_window_new window !

\ Connect the window to delete event
window @ s" delete_event" ftoc de 0 g_signal_connect drop

\ Connect the destroy handler
window @ s" destroy" ftoc destroy 0 g_signal_connect drop

\ Create a button labeled hello world
s" Hello World" ftoc gtk_button_new_with_label button !

\ Connect the button to the hello callback
button @ s" clicked" ftoc hello 0 g_signal_connect drop

\ Add the button to the window
window @ button @ gtk_container_add

\ Show the buton and the window
button @ gtk_widget_show
window @ gtk_widget_show

\ Run the main procedure
gtk_main

More Topics

fflib.fs makes use of two previously undocumented words. These are open-lib and lib-sym.

open-lib takes an address of a forth string, and its length off of the stack, tries to open and load a library by that name, and places the address of the loaded library onto the stack. If it fails it places 0 on the stack instead. For example:
s" libc.so.6" open-lib

lib-sym takes the address of a forth string, its length, and the address of a loaded library off of the stack. It then tries to locate the symbol of that name in the library and places its address on the stack. If it fails it places 0 on the stack instead. For example:
s" sleep" s" libc.so.6" open-lib lib-sym

These can be useful to pass a pointer to a c function as an argument to a c function. This could have significant performance advantages over a callback to a forth word that calls the c function. Here is our previous example, modified so that the button sends a destroy event, using gtk_widget_destroy:

require fflib.fs

\ We need to make some C strings, so we include the forth to c routine from above:
: ftoc ( c-addr u -- 'c-addr ) here dup >r over allot swap cmove 0 c, align r> ;

library libgtk libgtk-x11-2.0.so.0

libgtk gtk_init ptr ptr (void) gtk_init
libgtk gtk_button_new_with_label ptr (ptr) gtk_button_new_with_label
libgtk gtk_container_add ptr ptr (void) gtk_container_add
libgtk gtk_main (void) gtk_main
libgtk gtk_main_quit (void) gtk_main_quit
libgtk gtk_widget_show ptr (void) gtk_widget_show
libgtk gtk_window_new int (ptr) gtk_window_new

libgtk g_print ptr (void) g_print

libgtk g_signal_connect_data ptr ptr ptr ptr ptr int (int) g_signal_connect_data
\ Define a few other things to match the way gtk has them defined
: g_signal_connect 0 0 g_signal_connect_data ;
: g_signal_connect_after 0 1 g_signal_connect_data ;
: g_signal_connect_swapped 0 2 g_signal_connect_data ;

\ gtk callback
callback libgtkcallback (void) ptr ptr callback;
\ gtk signal handler callback
callback libgtksignalhandler (int) ptr ptr ptr callback;

\Example callback made from nameless word
:noname drop drop s" Hello World " ftoc g_print ; libgtkcallback hello

\ Example making a callback with a name
\ We return 0 so that it closes
\ If we returned 1, it wouldn't
: delete_event drop drop drop s" delete event occured " ftoc g_print 0 ;
' delete_event libgtksignalhandler de

\ Another example callback
:noname drop drop gtk_main_quit ; libgtkcallback destroy

\ Make variables for a button and a window
variable button
variable window

\ Initialize gtk
0 0 gtk_init

\ Create a window
0 gtk_window_new window !

\ Connect the window to delete event
window @ s" delete_event" ftoc de 0 g_signal_connect drop

\ Connect the destroy handler
window @ s" destroy" ftoc destroy 0 g_signal_connect drop

\ Create a button labeled hello world
s" Hello World" ftoc gtk_button_new_with_label button !

\ Connect the button to the hello callback
button @ s" clicked" ftoc hello 0 g_signal_connect drop

\ Connect the button to an existing c routine
button @ s" clicked" ftoc
  s" gtk_widget_destroy" s" libgtk-x11-2.0.so.0" open-lib lib-sym
  window @ g_signal_connect_swapped

\ Add the button to the window
window @ button @ gtk_container_add

\ Show the buton and the window
button @ gtk_widget_show
window @ gtk_widget_show

\ Run the main procedure
gtk_main