Extending OCaml in C++ - Boost.Date Time example
Specifically, we're going to look at the minimal amount of machinery that will enable us to call the Boost.Date_time Gregorian function day_clock::local_day ()
from OCaml.
struct tm
so we can opt for taking advantage of the existing OCaml Unix
module type tm
to represent OCaml dates.
We start with the C wrapper for day_clock::local_day ()
suitable for use as an external
in an OCaml module. This is file bdate_c.cpp.
#include <caml/mlvalues.h> #include <caml/memory.h> #include <caml/alloc.h> /* Reflect Boost.Date_time boost::gregorian::date. */ #include <boost/date_time/gregorian/gregorian.hpp> /* Use Unix.tm for intermediate representation type tm = { tm_sec : int; (* Seconds 0..60 *) tm_min : int; (* Minutes 0..59 *) tm_hour : int; (* Hours 0..23 *) tm_mday : int; (* Day of month 1..31 *) tm_mon : int; (* Month of year 0..11 *) tm_year : int; (* Year - 1900 *) tm_wday : int; (* Day of week (Sunday is 0) *) tm_yday : int; (* Day of year 0..365 *) tm_isdst : bool; (* Daylight time savings in effect *) } */ extern "C" value caml_boost_gregorian_day_clock_local_day () { value res=0; struct caml__roots_block blk, *caml__frame=caml_local_roots; blk.next = caml_local_roots; blk.nitems = 1; blk.ntables = 1; blk.tables [0] = &res; caml_local_roots = &blk; struct tm t = boost::gregorian::to_tm ( boost::gregorian::day_clock::local_day ()); res = caml_alloc (9, 0); Store_field (res, 0, Val_int (t.tm_sec)); Store_field (res, 1, Val_int (t.tm_min)); Store_field (res, 2, Val_int (t.tm_hour)); Store_field (res, 3, Val_int (t.tm_mday)); Store_field (res, 4, Val_int (t.tm_mon)); Store_field (res, 5, Val_int (t.tm_year)); Store_field (res, 6, Val_int (t.tm_wday)); Store_field (res, 7, Val_int (t.tm_yday)); Store_field (res, 8, Val_bool (false)); //t.tm_isddst is always -1 value tmp = res; caml_local_roots = caml__frame; return tmp; }We can turn that source file into a regular static archive libbdate_c.a with the commands shown in this bash fragment.
echo Building libbdate.a... g++ -c -I/home/fletch/project/boost_1_55_0 \ -I/home/fletch/.opam/4.00.1/lib/ocaml \ bdate_c.cpp ar rvs libbdate_c.a bdate_c.o(of course, if following along you'll need to adjust the paths in the above according to your needs). The equivalent Windows commands are like this.
echo Building libbdate.a... cl /nologo /EHsc /c /Fo /MD /Ic:/project/boost_1_55_0/ \ /IC:/ocamlms64/lib \ /DBOOST_ALL_NO_LIB=1 \ bdate_c.cpp lib /NOLOGO /OUT:libbdate_c.lib bdate_c.obj
Now we can turn attention to the Bdate
OCaml module. First a module type
. This file is bdate_sig.mli.
(**[Bdate] module interface*) (** A set of date-time libraries based on generic programming concepts. See {{:http://www.boost.org/doc/libs/1_55_0/doc/html/date_time.html} Boost.Date_time} *) module type S = sig (** {2 Types}*) type t (** The type of a date*) val string_of_date : t -> string (** @return a string representation of a date*) (**{2 Functions}*) val local_day : unit -> t (** Get the local day based on the time zone settings of the computer*) endThe next file bdate.mli just "passes through" the
module type S
defined above.
include Bdate_sig.S
And now we implement the module in file bdate.ml.
type t=Unix.tm external boost_gregorian_day_clock_local_day : unit -> t = "caml_boost_gregorian_day_clock_local_day" let local_day () = boost_gregorian_day_clock_local_day () let string_of_date tm = Printf.sprintf "%04d-%02d-%02d" (tm.Unix.tm_year+1900) (tm.Unix.tm_mon+1) (tm.Unix.tm_mday)
The following bit of bash will create a compiled archive bdate.cmxa and compiled module interface bdate.cmi of the above OCaml files.
echo Compiling bdate.cmxa... ocamlopt.opt -c bdate_sig.mli bdate.mli bdate.ml ocamlopt.opt -a -o bdate.cmxa bdate.cmx
OK, we've got all we need to write a test OCaml program. This is bdate_test.ml.
let _ = let t = Bdate.local_day () in Printf.printf "The current date is %s\n" (Bdate.string_of_date t)This program when run, will print the current date. Here's the bash to build it.
echo Compiling bdate_test.opt... ocamlopt.opt -c -I . bdate_test.ml #Take care to get the ordering right here ocamlopt.opt -verbose -cclib -lstdc++ \ -o bdate_test \ unix.cmxa libbdate_c.a bdate.cmxa bdate_test.cmxBuilding this on Windows is no problem either. In this case the commands look like the following.
echo Compiling bdate_test.opt... ocamlopt.opt -c -I . bdate_test.ml ocamlopt.opt -verbose \ -o bdate_test.exe \ unix.cmxa libbdate_c.lib bdate.cmxa bdate_test.cmx(That is, we don't need to provide any additional link libraries.)
We expect to see output like
The current date is 2014-02-01when we run this program (of course, the date printed will substituted with the current date).
Literate programming is the business. Here's a final set of bash commands for generating the Bdate
module documentation.
echo Generating documentation... mkdir -p doc ocamldoc -intro intro -d doc -html -colorize-code -stars -sort \ bdate_sig.mli bdate.mli bdate.mlThat assumes existence of a file called intro containing markup for the module documentation "header". Here's one that will do.
{1 Bdate} The [Bdate] package. A set of date-time libraries based on generic programming concepts. See {{:http://www.boost.org/doc/libs/1_55_0/doc/html/date_time.html} Boost.Date_time} {2 Index} {!indexlist} {2 Modules} {!modules:Bdate_sig}
Addendum
#!/bin/bash echo Cleaning up intermediate files... rm *.obj *.lib *.opt *.cmx *.cmxa *.cmi echo Compiling target libbdate.a... cl /nologo /EHsc /c /Fo /MD /Ic:/project/boost_1_55_0/ \ /IC:/ocamlms64/lib \ /DBOOST_ALL_NO_LIB=1 \ bdate_c.cpp lib /NOLOGO /OUT:libbdate_c.lib bdate_c.obj echo Compiling target bdate_ocaml.obj... ocamlopt.opt -c bdate_sig.mli bdate.mli bdate.ml bdate_test.ml ocamlopt.opt -output-obj -o bdate_ocaml.obj unix.cmxa bdate.cmx bdate_test.cmx std_exit.cmx # bdet_ocaml.obj: bdate.ml bdate_test.ml # ocamlopt.opt -output-obj -o bdate_ocaml.obj \ # bdate.ml bdate_test.ml unix.cmxa std_exit.cmx # .DEFAULT: bdet_ocaml.obj echo Compiling target bdate_test.exe... cl /Febdate_test.exe \ /EHsc /MD /nologo driver.c bdate_ocaml.obj \ libbdate_c.lib \ c:/ocamlms64/lib/libunix.lib \ c:/ocamlms64/lib/libasmrun.lib \ ws2_32.lib # bdate_test.exe : libbdate_c.lib bdate_ocaml.obj driver.c # cl /Febdate_test.exe \ # /EHsc /MD /nologo driver.c bdate_ocaml.obj \ # libbdate_c.lib \ # c:/ocamlms64/lib/libasmrun.lib \ # c:/ocamlms64/lib/libunix.lib \ # ws2_32.lib # .DEFAULT: bdate_test.exe # .DEFAULT: $(CProgramCopy _, $(BIN_DIR), bdate_test) echo Generating documentation... mkdir -p doc ocamldoc -intro intro -d doc -html -colorize-code -stars -sort \ bdate_sig.mli bdate.mli bdate.ml(the commands contained in the comments are equivalent omake diretives).
void caml_main (char**); int main (int argc, char** argv) { (void)argc; caml_main (argv); return 0; } void flexdll_dump_exports(void* u){(void)u;} void *flexdll_dlopen(char const* file, int mode){(void)file; (void)mode; return (void*)0;} void flexdll_dlclose(void* u){(void)u;} void* flexdll_dlsym(void* u, const char * n) {(void)u; (void)n; return (void*)0;} char* flexdll_dlerror(){static char* flexdll_error_buffer = "flexdll is not availble"; return flexdll_error_buffer;}