The ‘box’ package doesn’t have a built-in foreign function interface yet
but it is possible to integrate compiled code via R’s SHLIB
mechanism
for building shared libraries. In particular, this also works with
packages such as Rcpp.
For the time being, the following steps unfortunately require manual work:
This guide aims to describe all steps in sufficient detail to make them easy. In the long run, the plan is to automate all these steps.
To demonstrate these steps, we will use an example module named c
that
uses compiled code written in C. Here is the C code that we want to make
usable in an R module, which is saved in the file c/hello.c
:
#include "R.h" #include "Rdefines.h" #include <stdio.h> SEXP hello_world(SEXP name) { char const msg_template[] = "Hello from C, %s!"; char const *const c_name = CHAR(asChar(name)); char *const msg_buf = R_alloc(sizeof msg_template - 2 + strlen(c_name) + 1, 1); sprintf(msg_buf, msg_template, c_name); return mkString(msg_buf); }
In addition, compiled code often includes specific compilation
instructions. While this is unnecessary for this simple example, it’s
included anyway for completeness. For R, these compilation instructions
are contained in a file called
Makevars
:
``` c/makevars PKG_CFLAGS = -std=c11 -pedantic -Wall -Wextra -Werror
## Compile code To make code loadable and callable by R, it should be compiled via the `R CMD SHLIB` mechanism. This only needs to happen *once* for each module, when it is first loaded. By convention, such code should go into a submodule called `__setup__`. This convention makes it clear that this is a “special” module, and not intended for direct consumption of the module user. The module specifies which object files to compile, and invokes `R CMD SHLIB`: ```r build_shared_lib = function (object_names) { # Change working directory so R finds the Makevars. old_dir = setwd(box::file()) on.exit(setwd(old_dir)) exitcode = system2('R', c('CMD', 'SHLIB', paste0(object_names, '.c'))) stopifnot(exitcode == 0L) } build_shared_lib('hello')
In principle, only the last line in this file should need to be changed for other C projects.
With this in place, we can invoke the compilation by loading the
c/__setup__
submodule:
box::use(./c/`__setup__`)
The result of the compilation will be a single file, hello.so
(on Unix
and macOS) or hello.dll
(on Windows) which represents a shared
library file, and which we can load and use inside R.
Compiled code from a shared library is loaded in R using the dyn.load
command. This will happen inside the module that uses and/or exposes the
compiled code.
Since the name of the shared library file is platform dependent, we need a helper function that gives us this name:
libname = function (name) { box::file(paste0(name, .Platform$dynlib.ext)) }
Now our module can load the compiled code; since this code needs to be
executed every time the module is loaded, it goes into the .on_load
hook:
.on_load = function (ns) { ns$dll = dyn.load(libname('hello')) }
… and don’t forget to unload the dynamic library when the module is unloaded:
.on_unload = function (ns) { dyn.unload(libname('hello')) }
Finally, our module needs a way of calling the compiled code. This is
done via the R primitive .Call
:
#' @export hello_world = function (name) { .Call(dll$hello_world, name) }
To use the code, we load the module and call the hello_world
function:
box::use(./c) c$hello_world('Rthur')
## [1] "Hello from C, Rthur!"
Note that using dll$hello_world
causes a somewhat costly call to
getNativeSymbolInfo
every time the function is invoked. If this is
undesired, the value of dll$hello_world
should be stored in a variable
when loading the module.
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.