Intro
This is a short summary of how to add C
source code into an R
package. I wrote this a year ago as a course material summary (STAT 580) and believed that I would remember all the basic details forever. Obviously, I was wrong. So I’m not 100 percent sure about the following contents and can only hope that this is still helpful to, I don’t know, at least myself in the future :) Enjoy!
Assume we already have an R
package called tinyCRexample
that is working and willing to be added some C
source code. There are four things that we need to pay attention to:
tinyCRexample/src
: we need to create this new folder. It holds allC
source code, an initialization fileinit.c
, andMakevars
andMakevars.win
tinyCRexample/inst/include
: we need to create this new folder (if you don’t have previously). It holds header filestinyCRexample.h
andtinyCRexampleAPI.h
tinyCRexample/R
we should already have this folder. We need to add someR
scripts holding wrappers of our.Call()
or.c()
functionstinyCRexample/NAMESPACE
we should already have this, too. If we useroxygen2
it should be generated automatically.
src
Include all
.c
files here, and do not include any.o
or.so
or.dll
files.Create an initialization file
init.c
An example of init.c
#include "tinyCRexample.h"
#include <Rconfig.h>
#include <Rinternals.h>
#include <R_ext/Rdynload.h>
static const
R_CallMethodDef callMethods[] = {
{"add", (DL_FUNC) &add_, 2},
{"compute_cross_corr", (DL_FUNC) &COMPUTE_CROSS_CORR_, 3},
{"na_trim_cmps", (DL_FUNC) &NA_TRIM_, 1},
{"local_max_cmps", (DL_FUNC) &LOCAL_MAX_, 2},
{NULL, NULL, 0}
};
void R_init_CMPS(DllInfo *info)
{
R_registerRoutines(info,
NULL,
callMethods,
NULL,
NULL);
R_useDynamicSymbols(info, TRUE);
/* used by external packages linking to internal xxx code from C */
R_RegisterCCallable("CMPS","add", (DL_FUNC) &add_);
R_RegisterCCallable("CMPS","compute_cross_corr", (DL_FUNC) &COMPUTE_CROSS_CORR_);
R_RegisterCCallable("CMPS","na_trim_cmps", (DL_FUNC) &NA_TRIM_);
R_RegisterCCallable("CMPS","local_max_cmps", (DL_FUNC) &LOCAL_MAX_);
}
[TODO] Some explanation
Note that #include "tinyCRexample.h"
is only needed for init.c
.
Makevars
andMakevars.win
are used to set compile flags. The minimum we would need is
// Makevars
PKG_CPPFLAGS = -I../inst/include
// Makevars.win
PKG_CPPFLAGS = -I../inst/include
inst/include
tinyCRexample.h
#include <R.h>
#include <Rinternals.h>
SEXP add_(SEXP x_, SEXP y_);
SEXP COMPUTE_CROSS_CORR_(SEXP xx_in, SEXP yy_in, SEXP minoverlap_in);
SEXP NA_TRIM_(SEXP seq_in);
SEXP LOCAL_MAX_(SEXP seq_in, SEXP MAX_MIN_in);
tinyCRexampleAPI.h
#include <tinyCRexample.h>
#include <R_ext/Rdynload.h>
#include <R.h>
#include <Rinternals.h>
SEXP add_(SEXP x, SEXP y) {
static SEXP(*fun)(SEXP, SEXP) = NULL;
if (fun == NULL)
fun = (SEXP(*)(SEXP, SEXP)) R_GetCCallable("CMPS", "add");
return fun(x, y);
}
SEXP COMPUTE_CROSS_CORR_(SEXP xx_in, SEXP yy_in, SEXP minoverlap_in) {
static SEXP(*fun)(SEXP, SEXP, SEXP) = NULL;
if (fun == NULL)
fun = (SEXP(*)(SEXP, SEXP, SEXP)) R_GetCCallable("CMPS", "compute_cross_corr");
return fun(xx_in, yy_in, minoverlap_in);
}
SEXP _NA_TRIM(SEXP seq_in) {
static SEXP(*fun)(SEXP) = NULL;
if (fun == NULL)
fun = (SEXP(*)(SEXP)) R_GetCCallable("CMPS", "na_trim_cmps");
return fun(seq_in);
}
SEXP LOCAL_MAX_(SEXP seq_in, SEXP MAX_MIN_in) {
static SEXP(*fun)(SEXP, SEXP) = NULL;
if (fun == NULL)
fun = (SEXP(*)(SEXP, SEXP)) R_GetCCallable("CMPS", "local_max_cmps");
return fun(seq_in, MAX_MIN_in);
}
[TODO] Provide package API of exportable functions (main idea: others can include this API header file and use these exported functions )
tinyCRexample/R
Create wrapper functions and include them in the R
folder
Note that we used roxygen2
, it will automatically edit NAMESPACE
.
#' @useDynLib tinyCRexample COMPUTE_CROSS_CORR_
compute_cross_corr <- function(x, y, min.overlap) .Call(COMPUTE_CROSS_CORR_, x, y, min.overlap)
#' @useDynLib tinyCRexample NA_TRIM_
na_trim_cmps <- function(x) .Call(NA_TRIM_, x)
NAMESPACE
If we ever gave a wrong name in #' @useDynLib tinyCRexample COMPUTE_CROSS_CORR_
, for example
#' @useDynLib ABC COMPUTE_CROSS_CORR_
compute_cross_corr <- function(x, y, min.overlap) .Call(COMPUTE_CROSS_CORR_, x, y, min.overlap)
and did a package check, NAMESPACE
would be edited and become:
useDynLib(ABC,COMPUTE_CROSS_CORR_) # this is wrong
useDynLib(CMPS,LOCAL_MAX_)
useDynLib(CMPS,NA_TRIM_)
Then many errors would pop out since there is no shared library object called ABC
The solution is to delete NAMESPACE
and regenerate it by using roxygen2::roxygenise()
after we have corrected the package name in #' @useDynLib