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:

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: