sample.sml file:
val f = _import "f" : real * real -> real val x = (1.1, 2.2);
val y = f x;
print ("result : " ^ Real.toString y ^ "\n");
Execution:
# gcc -c sample.c
# smlsharp sample.sml sample.o
# a.out
result : 2.459675
Figure 10.1: Passing a tuple to user-defined C function
double modf(double, double *);
According to the manual of modf, the second argument of pointer type must specify the destination buffer of the result of this function. So we specify an interoperable type of a mutable value to the type of the second argument.
val modf = _import "modf" : (real, real ref) -> real
We need to pay special attention to pointers to char. In C, there are a lot of meaning a char pointer exactly means. For example, a char pointer usually means a null-terminated string. In the other case, a char pointer is used as a pointer to most generic type of binary data buffers. Suppose we try to import sprintf function. Its prototype is given below.
int sprintf(char *, const char *, ...);
The first char pointer is a pointer to a destination buffer, and the second one represents a null-terminated string. According to this difference of usage of two pointers, we give the following type annotation to the import declaration.
val sprintfInt = _import "sprintf" : (char array, string, ...(int)) -> int
We can import any user-defined C function to SML#, while we only use standard C library functions to describe how to import C functions so far. Figure 10.3 shows an example of importing an user-defined function to SML# and passing a structure from SML# to the C function.
10.4 Using dynamically linked libraries
The declaration
val id = _import "symbol " : type
is statically resolved to a C function having the name symbol . When SML# compiles a source file containing this declaration, the compiler generates an object file containing symbol as an external name.
When an executable program is build from these objects files, the system linker links these object files with C functions. This is also true in interactive mode, which is implemented by a simple iteration that performs separate compilation, linking and loading.
However for a library that is only available at runtime, such static resolution is impossible. To cope with those situation, SML# provide dynamic linking through the following module.
10.4. USING DYNAMICALLY LINKED LIBRARIES 57 samle.c file:
int f(int s) { return(s * 2);
} Execution:
$ gcc -shared -o sample.so sample.c
$ smlsharp
SML# version 1.00 (2012-04-02 JST) for x86-linux
# val lib = DynamicLink.dlopen "sample.so";
val lib = _ : lib
# val fptr = DynamicLink.dlsym(lib, "f");
val fptr = ptr : unit ptr
# val f = fptr : _import int -> int;
val f = _ : int -> int
# f 3;
val it = 6 : int
Figure 10.2: Using dynamic link library
structure DynamicLink : sig type lib
type codeptr
datatype scope = LOCAL | GLOBAL datatype mode = LAZY | NOW val dlopen : string -> lib
val dlopen’ : string * scope * mode -> lib val dlsym : lib * string -> codeptr
val dlclose : lib -> unit end
These functions provide the system services of the same names provide in a Unix-family OS.
• dlopen opens a shared library.
• dlopen’ takes one of those parameters to control dlopen. RTLD LOCAL, RTLD GLOBAL and RTLD LAZY, RTLD NOW. For its details, consult OS manual on dlopen.
• dlsym takes a library handle obtained by dlopen and a function name, and returns a C pointer to the function.
• dlclose closes the shared library.
The C pointer returned by dlsym can be converted to SML# function by the following expression.
exp : _import type
exp is a SML# expression of type codeptr. type specifies the type of C function as in import declaration.
Figure 10.2 shows an example.
Chapter 11
SML# feature: Multithread programming
Through the seamless and direct C interface, SML# supports multiple threads that run concurrently on multicore processors. SML# allows you to exploit two thread libraries directly in SML#: POSIX thread (Pthread) library, which is a part of operating systems; and MassiveThreads, a lightweight fine-grain thread library. In both libraries, you can create multiple threads in SML#, each of which executes a SML# routine and is scheduled to a CPU core by operating systems or the MassiveThreads’ thread scheduler. This chapter introduces multithread programming in SML#.
11.1 Programming with Pthreads
SML# provides the Pthread structure that is a direct binding of the Pthread library in SML#. The pthread create and pthread join functions, which creates and join a thread, respectively, are provided as the following SML# functions:
Pthread.Thread.create : (unit -> int) -> Pthread.thread Pthread.Thread.join : Pthread.thread -> int
The following is a simple example that computes fib 42 in a different thread.
# fun fib 0 = 0 | fib 1 = 1 | fib n = fib (n - 1) + fib (n - 2);
val fib = fn : int -> int
# val t = Pthread.Thread.create (fn _ => fib 42);
val t = ptr : Pthread.thread
# Pthread.Thread.join t;
val it = 267914296 : int
The SML# runtime system for direct C interface is carefully designed so that SML# functions can be passed to a C function as call-back functions and can be called back from a C function running in a thread that is different from the one that originally calls the C function. Using this features, the programmer can enjoy multi-thread programming simply by importing the Pthread library. To do this, we define the type
type pthread_t = unit ptr
for Pthread handles. τ ptr is a built-in type for C pointers; unit ptr corresponds to void* type in C.
The thread creation function, pthread create, is then imported by the following declaration.
val pthread_create =
_import "pthread_create"
: (pthread_t ref, unit ptr, unit ptr -> unit ptr, unit ptr) -> int Using this, we can write a function spawn that creates a thread as follows.
59
fun spawn f = let
val r = ref (Pointer.NULL ()) in
Pointer.NULL () return s the NULL pointer in C.
Similarly, pthread join is imported and used to define a function join that waits for the termination of a created thread.
val pthread_join =
_import "pthread_join"
: (pthread_t, unit ptr ref) -> int fun join t =
(pthread_join (t, ref (Pointer.NULL ())); ())
Using these functions, we can write the same program as shown at the beginning of this section.
# fun fib 0 = 0 | fib 1 = 1 | fib n = fib (n - 1) + fib (n - 2);
Note that it is dengerous to replace the line
# val t = spawn g;
with
# val t = spawn (fn () => r := fib 42);
because the garbage collector may collect the closure generated by the function expression before the function is called in another thread. See Section 29.2 for details.