Introduction

Introduction

{callme} is a package for easily compiling inline C code for use within R.

Complied C code can be used to improve the speed of critical sections of code e.g. tight loops of numeric operations.

In this introductory vignette, some common elements are described for C code which operates with R objects

Code Layout in Vignettes

The C code chunks in these vignettes is streamlined for display purposes.

In general when using {callme} you must:

  1. Define the C code
    • in a string (usually called code in examples in this package)
    • in a .c file
  2. Call callme::compile(code) or callme::compile("file.c")

This standard way of compiling the code in R is shown below:

code <- r"(
SEXP print_with_c(SEXP string) {
   Rprintf("Printing in C: '%s'\n", CHAR(asChar(string)));
   return R_NilValue;
}
)"

callme::compile(code, invisible = TRUE)
print_with_c("hello")

In order to focus on the actual C code (with C code syntax highlighting), C code will simply be shown in a blue box. Assigning the code to a string, and calling callme::compile(code) are hidden by default (Click to show R code will reveal this code).

#include <R.h>
#include <Rinternals.h>

SEXP print_with_c(SEXP string) {
   Rprintf("Printing in C: '%s'\n", CHAR(asChar(string)));
   return R_NilValue;
}
Click to show R code
code = r"(
#include <R.h>
#include <Rinternals.h>

SEXP print_with_c(SEXP string) {
   Rprintf("Printing in C: '%s'\n", CHAR(asChar(string)));
   return R_NilValue;
}
)"

callme::compile(code)
print_with_c("hello")
#> Printing in C: 'hello'

Example: Add two vectors of floating point numbers

The following code adds two vectors of floating point values and returns the result (i.e. a + b).

#include <R.h>
#include <Rinternals.h>

SEXP add(SEXP a, SEXP b) {
  
  // Sanity checks
  if (length(a) != length(b)) {
    error("'a' and 'b' must be the same length");
  }
  
  // Get a pointer to the actual numeric data in 'a' and 'b'
  double *ap = REAL(a);
  double *bp = REAL(b);
  
  // Allocate a new R object 'res' and protect it from garbage collection
  int N = length(a);
  SEXP res = PROTECT(allocVector(REALSXP, N));
  
  // Get a pointer to the actual numeric data in 'res'
  double *resp = REAL(res);
  
  // Add elements of two arrays in C
  for (int i = 0; i < N; i++) {
    resp[i] = ap[i] + bp[i];
  }
  
  // Unwind any protection and return the R result
  UNPROTECT(1);
  return res;
}
Click to show R code
code = r"(
#include <R.h>
#include <Rinternals.h>

SEXP add(SEXP a, SEXP b) {
  
  // Sanity checks
  if (length(a) != length(b)) {
    error("'a' and 'b' must be the same length");
  }
  
  // Get a pointer to the actual numeric data in 'a' and 'b'
  double *ap = REAL(a);
  double *bp = REAL(b);
  
  // Allocate a new R object 'res' and protect it from garbage collection
  int N = length(a);
  SEXP res = PROTECT(allocVector(REALSXP, N));
  
  // Get a pointer to the actual numeric data in 'res'
  double *resp = REAL(res);
  
  // Add elements of two arrays in C
  for (int i = 0; i < N; i++) {
    resp[i] = ap[i] + bp[i];
  }
  
  // Unwind any protection and return the R result
  UNPROTECT(1);
  return res;
}
)"

callme::compile(code)
add(c(1, 2, 3), c(4, 5, 6))
#> [1] 5 7 9

Elements to note in the example

The following elements highlighted here are described in more detail in other vignettes within this package.

Function signature

Function signatures must be of the format SEXP funcname(SEXP arg1, SEXP arg2, ... SEXP argn)

Sanity checking

There is a much greater need for checking for sane arguments in C compared to R. In R, an out-of-bounds memory access might only result in an NA value, but in C such a bad memory access can cause memory corruption and crashes.

In the example above, the lengths of the two input vectors were checked as automatic vector recyling does not happen in C like it does in R.

Unpack R objects into C equivalents

All R objects are of type SEXP and are a combination of metadata and the actual dta useful to C.

The C compatible data must be extraced from the SEXP e.g. find the pointer to the array of doubles using:

double *ap = REAL(a);

Allocte new R objects within C

New R objects can be created within C using allocVector() and related functions.

It is important to PROTECT() any R objects created within C - otherwise R’s garbage collection will consider them unused and try to free the memory in which they store data.

Return object from C to R

The final returned object must also be of type SEXP. This object may have been created with a call to allocVector() but there are convenience functions for creating and returning single values e.g. ScalarInteger()