Tumbleweed Smalltalk goes Native

One of the first things I added to Tumbleweed was the ability to call out to native code directly from within Smalltalk. The FFI (Foreign Function Interface) functionality of Tumbleweed is quite simple to use, and very flexible. It is based on the libffi library. A recent commit extends the support to include arbitrary ‘C’ structures as parameters to external functions, on top of the already supported list of common ‘C’ language types.

Basic Usage

The FFI interface allows direct calling of functions in shared libraries, and provides a simple but flexible method for specifying the signature of the function. As an example, consider the following simple C function in a shared library, exported as a “C” function from a shared library called simple.

int foo(char* string, float value)

The following Smalltalk code will call that function, I’ll explain what it does afterwards.

simple <- FFI new: 'simple'.
args <- Array new: 2.
args at: 1 put: 'Hello'; at: 2 put: 0.1.
result <- simple call: #foo returning: #int 
    taking: #(string double) 
    as: args.
(result at: 1) print

The first line creates a new FFI class, opening the simple shared library. The FFI class holds a handle to the shared library, and behind the scenes manages a cache of functions already queried, so when you call a function with the same signature again it’ll already have a handle to it.

The ‘args’ array holds the arguments to the function, it must be at least as big as the number of expected arguments for the function, it can be bigger though, allowing re-use of a single arguments array. Each argument is just an object of appropriate type. The FFI code will take care of automatically marshalling the values from Smalltalk objects into the matching native types, to do this it needs to know what argument types the function is expecting. The message call:returning:taking:as: does the actual call. The returning parameter describes the return type, it is a symbol from the list of recognised types. The taking parameter expects an array of symbols describing the argument types for the function, and args is the array of argument values built earlier.

The way FFI handles returns and in/out parameters is interesting. The result returned from the call:returning:taking:as message is an array containing 1 + the number of arguments entries. The first entry in the array is the return value, marshalled out to the right Smalltalk type. The others are the arguments, however, they are re-marshalled after the call. For arguments that are passed into the function by value, the resulting value will be the same. For out argument types, they may have been modified by the native code, the result copy will reflect this.

Output Parameters

Output parameters are reflected in the native function signature as pointers, for example:

int foo2(char* string, int* inoutval);

To communicate to the FFI framework that the parameter is in/out, and that it’s value may change, use the out variants of parameter type names:

simple call: #foo2 returning: #int taking: #(string intOut)
    as: args

The use of intOut means that the value will be passed using a pointer, and if the native code modifies the memory at that address, the value in the result array will reflect the change. Note that the function signature must match this assumption, a similar function taking an int, rather than an int* will not match.

Summary (for now)

This provides a very powerful and flexible mechanism for calling out to native code from Smalltalk. The FFI framework also includes support for callbacks, calling back to Smalltalk from native, and the latest changes to the FFI code support passing in and out ‘C’ structs. I’ll go into how these features work in another post, watch this space…

This entry was posted in Blog and tagged , . Bookmark the permalink.

Leave a Reply