[Alice]

HowToInterface

How to interface C from Alice

This page describes how to interface to foreign C code from Alice, by implementing so-called native components, i.e. components written in C/C++ that are usable as Alice components.

Rough overview

To write a component X which wraps C code you should write four files:

X-sig.aml

Signature of the final component.

X.aml

Everything that can be implemented in Alice ML should go here.

NativeX.asig

Signature of those parts which are not implemented in Alice ML, but in C++.

NativeX.cc

C++ implementation of those native functions (on the SEAM Virtual Machine).

Learning by example

Let's assume that we want to write a new component Time, which most importantly should have a function now : unit -> time. First of all we would write it's signature TIME-sig.aml:

signature TIME =
sig
    type time

    val fromSeconds : LargeInt.int -> time
    val fromMilliseconds : LargeInt.int -> time

    val toSeconds : time -> LargeInt.int
    val toMilliseconds : time -> LargeInt.int

    val now : unit -> time
end

A suitable internal representation are big integers LargeInt.int. We can implement all of the routines above in Alice ML, except for Time.now, which is a native function declared in NativeTime.asig:

signature NATIVE_TIME_COMPONENT =
sig
    structure NativeTime :
    sig
        (* return current time in milliseconds since the epoch *)
        val now : unit -> LargeInt.int
    end
end

Please note that the signature should never have any type specifications! Alice ML implements dynamic typing (for packages), which implies that all type declarations have a dynamic representation. Believe me, you don't want to implement any dynamic type representation in C code.

So if you have abstract types, you cannot use them in the native signature. Instead, you give more polymorphic types there (it's unsafe anyway), and rely on the wrapper component to safely restrict the types.


Given such a native procedure, writing Time.aml is straightforward: alice/lib/system/Time.aml:


import signature TIME       from "TIME-sig"
import structure NativeTime from "NativeTime"

tructure Time : TIME =
struct
  type time = LargeInt.int

  fun fromMilliseconds ms = ms
  
  fun toMilliseconds t = t

  fun fromSeconds s = ms * 1000

  fun toSeconds t = t div 1000

  val now = NativeTime.now 
end

This component can be compiled as usual, but is not yet functional, because the imported NativeTime is still missing.

Native Components in SEAM

SEAM is written in C++, so that is the language you have to use for the native component, NativeTime.cc:

/* you always have to include this: */
#include "alice/Authoring.hh"

/* in our example we use ftime to implement Time.now, so we need
   to include: */
#include <sys/timeb.h>

/* naming convention:  Separate ComponentName and procedure name
   by a single underscore character '_'.
 
   Note that there are several DEFINEn macros, where n is the
   number of arguments. That is number of elements of the 
   argument tuple. 

   If we had arguments, then these would be named x0...x(n-1) inside
   the DEFINEn macro. For instance, by writing
     DECLARE_INT(i, x0);
   inside the body we declare a C int variable `i' that obtains
   the ML int value from the first argument.
 */

DEFINE0(NativeTime_now) {
  struct timeb tb;
  ftime (&tb);
  BigInt *res = BigInt::new ((double)tb.time);
  /* Note: BigInt are implemented using the GMP.
     BigInt::big returns GMP value.
   */
  /* multiply number of seconds by 1000 */
  mpz_mul_ui (res->big (), res->big (), 1000UL);
  /* add the milliseconds */
  mpz_add_ui (res->big (), res->big (), tm.millitm);

  /* return the value, never forget to return a valid alice value.
     If you implement a function which returns unit use
     the RETURN_UNIT macro.
   */
  RETURN_INTINF(res);
} END /* <- do not forget this END, unless you are interested in 
            strange error messages */


/* You must also write a function which creates the component
   itself.  This should always look more or less like this:
 */

AliceDll word NativeTime() {
  Record *record = Record::New (1);
  INIT_STRUCTURE(record, "NativeTime", "now", NativeTime_now, 1);
  RETURN_STRUCTURE("NativeTime$", record);
}

We have to compile this file into a dynamic library (DLL). Together with the .asig file, such a DLL can be imported by an Alice ML component as if it were an Alice component as well (this is not supported in the interactive toplevel, though).

You first have to compile the C++ source file. To build the DLL, it's probably best to use alicetool (which is installed with Alice, but currently undocumented):

  alicetool link NativeTime.o -o NativeTime.dll

If your DLL has to link against other dynamic libraries, you have to add them before the -o option.

That's it, we are done. You can now use your new Time component.

Further examples

Please have a look at the libraries in the Alice CVS for further examples. In particular, lib/sqlite provides a less trivial, but still simple example of a foreign binding.

New built-in components

In rare cases you might need to extend the Alice VM itself with so-called built-in components. This works in the same way as before, except that you do not import the native component, but hardwire it into the VM's table of so-called 'unsafe' components.

Such components have to be registered in alice/vm-seam/AliceMain.cc. There is a table called nativeComponents to which one entry must be added:

...
static NativeComponent nativeComponents[] = {
  {"lib/system/UnsafeConfig",       UnsafeConfig},
  {"lib/system/UnsafeIODesc",       UnsafeIODesc},
  {"lib/system/UnsafeOS",           UnsafeOS},
  {"lib/system/UnsafeUnix",         UnsafeUnix},
  {"lib/system/UnsafeCommandLine",  UnsafeCommandLine},
  {"lib/system/UnsafeComponent",    UnsafeComponent},
  {"lib/system/UnsafeDebug",        UnsafeDebug},
#if DEBUGGER
  {"lib/system/UnsafeDebugger",     UnsafeDebugger},
#endif
  {"lib/system/UnsafeForeign",      UnsafeForeign},
  {"lib/system/UnsafeSignal",       UnsafeSignal},
  {"lib/system/UnsafeSocket",       UnsafeSocket},
  {"lib/system/UnsafeRand",         UnsafeRand},
  {"lib/system/UnsafeValue",        UnsafeValue},
  {"lib/system/UnsafeReflect",      UnsafeReflect},
  {"lib/system/UnsafeTime",         UnsafeTime},        /* <-- like this */
  {"lib/utility/UnsafeCell",        UnsafeCell},
  {"lib/utility/UnsafeAddr",        UnsafeAddr},
  {"lib/distribution/UnsafeRemote", UnsafeRemote},
  {NULL, NULL}
};
...

All that remains is to register our work in a few Makefiles:

alice/vm-seam/Makefile.bootstrap.in::

alice/vm-seam/alice/lib/system/Makefile.am::