{callme}
compiles C code to loadable dynamic libraries callable via
.Call()
. This is useful for rapid prototyping of C code.
Code is defined in a character string, and must be valid C code callable
via .Call()
.
Features:
- Simple. Purpose is to support
.Call()
only. - Straightforward. Requires user to submit complete C code - including
header
#include
directives. - Flexible. Includes explicit handling for
PKG_CPPFLAGS
andPKG_LIBS
for setting C pre-processor flags, and library linking flags so code can link to other libraries installed on the system. - Returned object includes auto-generated functions wrapping the C
functions in the code which are compatible with
.Call()
. Not strictly necessary, but nice to have! - Multiple functions allowed in a single code block.
callme(code, cpp_flags = NULL, ld_flags = NULL, verbose = FALSE)
compile and load the function into R.
- Warn when provided code does not appear to contain any
.Call()
compatible C functions.
- As required by
.Call()
- the C functions must only take
SEXP
arguments, and return aSEXP
value.
- the C functions must only take
You can install from GitHub with:
# install.package('remotes')
remotes::install_github('coolbutuseless/callme')
The following example compiles a code snippet into a C library and then
executes the function from R using .Call()
.
The function name in C is add_
, which means that this is the first
argument to .Call()
i.e. .Call("add_", ...)
.
The function name could be almost any valid C function name, but I prefer to use an underscore suffix to indicate that this a C function meant to be called from R.
library(callme)
code <- "
#include <R.h>
#include <Rdefines.h>
SEXP add_(SEXP val1_, SEXP val2_) {
return ScalarReal(asReal(val1_) + asReal(val2_));
}
"
# Keep reference to returned object.
# C library will be unloaded when 'dll' variable is garbage collected.
dll <- callme(code)
# Manual call
.Call("add_", 1, 2.5)
#> [1] 3.5
# Alternatively, use the rapper function automatically generated by 'callme()'
dll$add_(99.5, 0.5)
#> [1] 100
In this example we want to get the version of the zstd
library which
is (already) installed on the computer, and return it as a character
string.
We need to tell R when compiling the code:
- to look in the
/opt/homebrew/include
directory forzstd.h
. - to look for the actual
zstd
library in/opt/homebrew/lib
. - to link to the
zstd
library (-lzstd
)
Note: This if for zstd
installed via homebrew
on macOS. Paths will
be different for other operating systems.
code <- r"(
#include <R.h>
#include <Rdefines.h>
#include "zstd.h"
SEXP zstd_version_() {
return mkString(ZSTD_versionString());
}
)"
# Keep reference to returned object.
# C library will be unloaded when 'dll' variable is garbage collected.
dll <- callme(code, cpp_flags = "-I/opt/homebrew/include", ld_flags = "-L/opt/homebrew/lib -lzstd")
# Manual call
.Call("zstd_version_")
#> [1] "1.5.5"
# Alternatively, use the rapper function automatically generated by 'callme()'
dll$zstd_version_()
#> [1] "1.5.5"
knitr::knit_engines$set(callme = callme::callme_engine)
Possible configure variables at start of chunk:
dllname
the variable name to hold the results ofcallme()
. Default:dll
cpp_flags
the equivalent of the argumentcallme(cpp_flags = ...)
ld_flags
the equivalent of the argumentcallme(ld_flags = ...)
```{callme}
#| dllname: mydll
#include <R.h>
#include <Rdefines.h>
SEXP add_(SEXP val1_, SEXP val2_) {
return ScalarReal(asReal(val1_) + asReal(val2_));
}
SEXP mul_(SEXP val1_, SEXP val2_) {
return ScalarReal(asReal(val1_) * asReal(val2_));
}
```
#| dllname: mydll
#include <R.h>
#include <Rdefines.h>
SEXP add_(SEXP val1_, SEXP val2_) {
return ScalarReal(asReal(val1_) + asReal(val2_));
}
SEXP mul_(SEXP val1_, SEXP val2_) {
return ScalarReal(asReal(val1_) * asReal(val2_));
}
.Call('add_', 3, 2)
#> [1] 5
.Call('mul_', 3, 2)
#> [1] 6
mydll$add_(3, 2)
#> [1] 5
mydll$mul_(3, 2)
#> [1] 6
- R Core for developing and maintaining the language.
- CRAN maintainers, for patiently shepherding packages onto CRAN and maintaining the repository