The Xen package provides macros and procedures making it possible for the same C code to support several different embedded (or extension) languages. Currently supported fully are Guile and no language; Ruby is usable, but not completely implemented.
Here's a complete program that defines a function (named "fnc" in the extension language) that takes an integer argument and increments it, a variable (named "var" in the extension language) that is initialized to 32, a constant (named "twelve") that has the value 12, then places you in a read-eval-print loop:
#include#include "xen.h" static XEN orig_function(XEN argument) { XEN_ASSERT_TYPE(XEN_INTEGER_P(argument), argument, XEN_ONLY_ARG, "fnc", "an integer"); fprintf(stdout, "argument is %d\n", XEN_TO_C_INT(argument)); return(C_TO_XEN_INT(XEN_TO_C_INT(argument) + 1)); } #ifdef ARGIFY_1 XEN_NARGIFY_1(function, orig_function); #else #define function orig_function #endif static XEN variable; int main(int argc, char **argv) { xen_initialize(); XEN_DEFINE_VARIABLE("var", variable, C_TO_XEN_INT(32)); XEN_DEFINE_CONSTANT("twelve", 12, "this is 12"); XEN_DEFINE_PROCEDURE("fnc", function, 1, 0, 0, "this is our function"); fprintf(stdout, "we're running: %s\n", xen_version()); xen_repl(argc, argv); return(0); }
The "ARGIFY" step is needed for those languages that assume one calling sequence for a C-defined function; we have to wrap up the actual call in whatever sequence the extension language wants.
Currently constants are assumed to be integers. Type checks are handled by macros such as XEN_INTEGER_P; type conversions by macros such as XEN_TO_C_INT or C_TO_XEN_INT.
If you use Xen, you'll need to deal with the various compile-time switches that configure it for the current version of the extension language. The simplest approach would be to copy the relevant portions of Snd's or sndlib's configure.ac. This is an autoconf (2.50 or later) input file that generates the script that runs the configuration checks. Its output is guided by config.h.in (which has the various macros) and makefile.in (the makefile template). The Ruby portion is:
AC_ARG_WITH(ruby-prefix,[ --with-ruby-prefix=PFX where Ruby is installed], ruby_prefix="$withval", ruby_prefix="") AC_ARG_WITH(ruby, [ --with-ruby try to use Ruby as the extension language], if test "$with_ruby" = yes ; then AC_MSG_CHECKING([for Ruby]) RUBY_VERSION=`ruby -e 'puts RUBY_VERSION'` if test "$RUBY_VERSION" > "0" ; then AC_DEFINE(HAVE_RUBY) AC_DEFINE(HAVE_EXTENSION_LANGUAGE) AC_MSG_RESULT($RUBY_VERSION) if test x$ruby_prefix != x ; then GUILE_CFLAGS=" -I$ruby_prefix" GUILE_LIBS="-L$ruby_prefix" else RUBY_LOC=`ruby -e [['puts \$:[0]']]` # good grief... GUILE_CFLAGS="-I$RUBY_LOC" GUILE_LIBS="-L$RUBY_LOC" RUBY_LOC=`ruby -e [['puts \$:[1]']]` if test "$RUBY_LOC" != nil && test "$RUBY_LOC" != "."; then GUILE_CFLAGS="$GUILE_CFLAGS -I$RUBY_LOC" GUILE_LIBS="$GUILE_LIBS -L$RUBY_LOC" RUBY_LOC=`ruby -e [['puts \$:[2]']]` if test "$RUBY_LOC" != nil && test "$RUBY_LOC" != "."; then GUILE_CFLAGS="$GUILE_CFLAGS -I$RUBY_LOC" GUILE_LIBS="$GUILE_LIBS -L$RUBY_LOC" RUBY_LOC=`ruby -e [['puts \$:[3]']]` if test "$RUBY_LOC" != nil && test "$RUBY_LOC" != "."; then GUILE_CFLAGS="$GUILE_CFLAGS -I$RUBY_LOC" GUILE_LIBS="$GUILE_LIBS -L$RUBY_LOC" RUBY_LOC=`ruby -e [['puts \$:[4]']]` if test "$RUBY_LOC" != nil && test "$RUBY_LOC" != "."; then GUILE_CFLAGS="$GUILE_CFLAGS -I$RUBY_LOC" GUILE_LIBS="$GUILE_LIBS -L$RUBY_LOC" RUBY_LOC=`ruby -e [['puts \$:[5]']]` if test "$RUBY_LOC" != nil && test "$RUBY_LOC" != "."; then GUILE_CFLAGS="$GUILE_CFLAGS -I$RUBY_LOC" GUILE_LIBS="$GUILE_LIBS -L$RUBY_LOC" RUBY_LOC=`ruby -e [['puts \$:[6]']]` if test "$RUBY_LOC" != nil && test "$RUBY_LOC" != "."; then GUILE_CFLAGS="$GUILE_CFLAGS -I$RUBY_LOC" GUILE_LIBS="$GUILE_LIBS -L$RUBY_LOC" fi fi fi fi fi fi fi GUILE_LIBS="$GUILE_LIBS -lruby -ldl -lcrypt" AC_CHECK_LIB(readline, readline, [AC_DEFINE(HAVE_READLINE) GUILE_LIBS="$GUILE_LIBS -lreadline -lncurses"], ,"-lncurses") AC_SUBST(GUILE_LIBS) AC_SUBST(GUILE_CFLAGS) else AC_MSG_WARN([can't find Ruby!]) fi fi)
The endless RUBY_LOC foolishness is trying to make a list of all possible locations of the ruby header and library files. (It's using the name GUILE_LIBS for historical reasons). The corresponding Guile code is:
if test "$with_ruby" != yes ; then GUILE_LIBS="" GUILE_CFLAGS="" GUILE_CONFIG_path="" GUILE_LIB_path="" if test "$with_no_guile" = yes ; then AC_DEFINE(HAVE_GUILE, 0) AC_DEFINE(HAVE_EXTENSION_LANGUAGE, 0) else AC_CHECK_FILE(/usr/lib/snd/bin/guile-config,[ GUILE_CONFIG_path=/usr/lib/snd/bin/ GUILE_LIB_path=/usr/lib/snd/lib ]) AC_MSG_CHECKING(for Guile) if (${GUILE_CONFIG_path}guile-config link > /dev/null) 2>&1; then GUILE_CONFIG_works=yes else GUILE_CONFIG_works=no AC_MSG_RESULT(no) fi if test $GUILE_CONFIG_works = yes; then GUILE_CFLAGS="`${GUILE_CONFIG_path}guile-config compile`" if test "$GUILE_LIB_path" != "" ; then GUILE_LIBS="-Xlinker -rpath -Xlinker $GUILE_LIB_path `${GUILE_CONFIG_path}guile-config link`" else GUILE_LIBS="`${GUILE_CONFIG_path}guile-config link`" fi guile_version="`${GUILE_CONFIG_path}guile -c '(display (version))'`" AC_MSG_RESULT($guile_version) if test "`${GUILE_CONFIG_path}guile -c '(display (string>=? (version) "1.3.4"))'`" != "#t"; then AC_MSG_WARN(Snd needs Guile 1.3.4 or later) AC_DEFINE(HAVE_GUILE,0) AC_DEFINE(HAVE_EXTENSION_LANGUAGE, 0) else AC_SUBST(GUILE_CFLAGS) AC_SUBST(GUILE_LIBS) AC_DEFINE(HAVE_GUILE) AC_DEFINE(HAVE_EXTENSION_LANGUAGE) OLD_LIBS="$LIBS" LIBS="$GUILE_LIBS" OLD_CFLAGS="$CFLAGS" CFLAGS="$GUILE_CFLAGS" AC_CHECK_LIB(guile, scm_create_hook, [AC_DEFINE(HAVE_SCM_CREATE_HOOK)]) AC_CHECK_LIB(guile, scm_set_smob_apply, [AC_DEFINE(HAVE_APPLICABLE_SMOB)]) AC_CHECK_LIB(guile, scm_remember_upto_here, [AC_DEFINE(HAVE_SCM_REMEMBER_UPTO_HERE)]) AC_CHECK_LIB(guile, scm_make_real, [AC_DEFINE(HAVE_SCM_MAKE_REAL)]) AC_CHECK_LIB(guile, scm_strport_to_string, [AC_DEFINE(HAVE_SCM_STRPORT_TO_STRING)]) AC_CHECK_LIB(guile, scm_object_to_string, [AC_DEFINE(HAVE_SCM_OBJECT_TO_STRING)]) AC_CHECK_LIB(guile, scm_num2long_long, [AC_DEFINE(HAVE_SCM_NUM2LONG_LONG)]) AC_CHECK_LIB(guile, scm_num2int, [AC_DEFINE(HAVE_SCM_NUM2INT)]) AC_CHECK_LIB(guile, scm_c_make_vector, [AC_DEFINE(HAVE_SCM_C_MAKE_VECTOR)]) AC_CHECK_LIB(guile, scm_c_define, [AC_DEFINE(HAVE_SCM_C_DEFINE)]) AC_CHECK_LIB(guile, scm_c_define_gsubr, [AC_DEFINE(HAVE_SCM_C_DEFINE_GSUBR)]) AC_CHECK_LIB(guile, scm_c_eval_string, [AC_DEFINE(HAVE_SCM_C_EVAL_STRING)]) AC_CHECK_LIB(guile, scm_list_n, [AC_DEFINE(HAVE_SCM_LIST_N)]) AC_CHECK_LIB(guile, scm_str2symbol, [AC_DEFINE(HAVE_SCM_STR2SYMBOL)]) AC_CHECK_TYPE(scm_t_catch_body, [AC_DEFINE(HAVE_SCM_T_CATCH_BODY)], , [#include]) LIBS="$OLD_LIBS" CFLAGS="$OLD_CFLAGS" if test "`${GUILE_CONFIG_path}guile -c '(display (string<=? (version) "1.3.4"))'`" = "#t"; then echo found old out-of-date Guile library fi fi else AC_DEFINE(HAVE_GUILE,0) AC_DEFINE(HAVE_EXTENSION_LANGUAGE, 0) fi fi fi
Guile has changed a lot over the years; these macrors are trying to pick out which version is currently available. The /usr/lib/snd/bin stuff is specific to CCRMA.
The corresponding portion of config.h.in is
#undef HAVE_GUILE #undef HAVE_RUBY #undef HAVE_EXTENSION_LANGUAGE #undef HAVE_APPLICABLE_SMOB #undef HAVE_SCM_REMEMBER_UPTO_HERE #undef HAVE_SCM_MAKE_REAL #undef HAVE_SCM_CREATE_HOOK #undef HAVE_SCM_STRPORT_TO_STRING #undef HAVE_SCM_OBJECT_TO_STRING #undef HAVE_SCM_NUM2LONG_LONG #undef HAVE_SCM_C_MAKE_VECTOR #undef HAVE_SCM_C_DEFINE #undef HAVE_SCM_C_DEFINE_GSUBR #undef HAVE_SCM_C_EVAL_STRING #undef HAVE_SCM_NUM2INT #undef HAVE_SCM_LIST_N #undef HAVE_SCM_STR2SYMBOL #undef HAVE_SCM_T_CATCH_BODY
And the corresponding portion of makefile.in:
GUILE_LIBS = @GUILE_LIBS@ GUILE_CFLAGS = @GUILE_CFLAGS@
I hope the xen.h names are largely self-explanatory; see the Snd sources for many examples.