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 allCsource code, an initialization fileinit.c, andMakevarsandMakevars.wintinyCRexample/inst/include: we need to create this new folder (if you don’t have previously). It holds header filestinyCRexample.handtinyCRexampleAPI.htinyCRexample/Rwe should already have this folder. We need to add someRscripts holding wrappers of our.Call()or.c()functionstinyCRexample/NAMESPACEwe should already have this, too. If we useroxygen2it should be generated automatically.
src
Include all
.cfiles here, and do not include any.oor.soor.dllfiles.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.
MakevarsandMakevars.winare 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